Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix errors and improve error messaging #158

Merged
merged 22 commits into from
Oct 24, 2018
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions __tests__/__json_files/circular.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{

"a": "{b}",
"b": "{c}",
"c": "{d}",
"d": "{a}"
}
}
6 changes: 3 additions & 3 deletions __tests__/__json_files/circular_2.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"a": {
"b": {
"c": "{d}"
"c": "{j}"
}
},
"d": "{a.b.c}"
}
"j": "{a.b.c}"
}
12 changes: 5 additions & 7 deletions __tests__/__json_files/circular_3.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
{
"a": {
"b": {
"c": "{d.e.f}"
}
"b": "{c.d.e}"
},
"d": {
"e": {
"f": "{a.b.c}"
"c": {
"d": {
"e": "{a.b}"
}
}
}
}
16 changes: 9 additions & 7 deletions __tests__/__json_files/circular_4.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
{
"a": {
"b": {
"c": "{d.e.f}"
"c": {
"d": "{e.f.g}"
}
}
},
"d": {
"e": {
"f": "{g.h}"
"e": {
"f": {
"g": "{h.i}"
}
},
"g": {
"h": "{a.b.c}"
"h": {
"i": "{a.b.c.d}"
}
}
}
6 changes: 6 additions & 0 deletions __tests__/__json_files/circular_5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"k": "{l}",
"l": "{m}",
"m": "{l}",
"n": "{k}"
}
8 changes: 8 additions & 0 deletions __tests__/__json_files/not_circular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"prop1" : { "value": "test1 value" },
"prop2" : { "value": "test2 value" },
"prop3" : { "value": "{prop1.value}" },
"prop4" : { "value": "{prop3.value}" },
"prop12" : { "value": "{prop1.value}, {prop2.value} and some extra stuff" },
"prop124" : { "value": "{prop1.value}, {prop2.value} and {prop4.value}" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the examples!

}
2 changes: 1 addition & 1 deletion __tests__/extend.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ describe('extend', () => {
source: [__dirname + "/__properties/paddings.json", __dirname + "/__properties/paddings.json"],
log: 'error'
})
).toThrow('Collision detected at:');
).toThrow('Collisions detected');
});

it('should throw a warning if the collision is in source files and log is set to warn', () => {
Expand Down
89 changes: 64 additions & 25 deletions __tests__/utils/resolveObject.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

var resolveObject = require('../../lib/utils/resolveObject');
var helpers = require('../__helpers');
var GroupMessages = require('../../lib/utils/groupMessages');

var PROPERTY_REFERENCE_WARNINGS = GroupMessages.GROUP.PropertyReferenceWarnings;

describe('utils', () => {
describe('resolveObject', () => {
Expand Down Expand Up @@ -114,26 +117,58 @@ describe('utils', () => {
});

it('should gracefully handle circular references', () => {
expect(
resolveObject.bind(null,
helpers.fileToJSON(__dirname + '/../__json_files/circular.json')
)
).toThrow('Circular definition: a | d');
expect(
resolveObject.bind(null,
helpers.fileToJSON(__dirname + '/../__json_files/circular_2.json')
)
).toThrow('Circular definition: a.b.c | d');
expect(
resolveObject.bind(null,
helpers.fileToJSON(__dirname + '/../__json_files/circular_3.json')
)
).toThrow('Circular definition: a.b.c | d.e.f');
expect(
resolveObject.bind(null,
helpers.fileToJSON(__dirname + '/../__json_files/circular_4.json')
)
).toThrow('Circular definition: a.b.c | g.h');
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);

resolveObject(helpers.fileToJSON(__dirname + '/../__json_files/circular.json'));
expect(GroupMessages.count(PROPERTY_REFERENCE_WARNINGS)).toBe(1);
expect(JSON.stringify(GroupMessages.fetchMessages(PROPERTY_REFERENCE_WARNINGS))).toBe(JSON.stringify([
'Circular definition cycle: a, b, c, d, a'
]));
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);

resolveObject(helpers.fileToJSON(__dirname + '/../__json_files/circular_2.json'));
expect(GroupMessages.count(PROPERTY_REFERENCE_WARNINGS)).toBe(1);
expect(JSON.stringify(GroupMessages.fetchMessages(PROPERTY_REFERENCE_WARNINGS))).toBe(JSON.stringify([
'Circular definition cycle: a.b.c, j, a.b.c'
]));
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);

resolveObject(helpers.fileToJSON(__dirname + '/../__json_files/circular_3.json'));
expect(GroupMessages.count(PROPERTY_REFERENCE_WARNINGS)).toBe(1);
expect(JSON.stringify(GroupMessages.fetchMessages(PROPERTY_REFERENCE_WARNINGS))).toBe(JSON.stringify([
'Circular definition cycle: a.b, c.d.e, a.b'
]));
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);

resolveObject(helpers.fileToJSON(__dirname + '/../__json_files/circular_4.json'));
expect(GroupMessages.count(PROPERTY_REFERENCE_WARNINGS)).toBe(1);
expect(JSON.stringify(GroupMessages.fetchMessages(PROPERTY_REFERENCE_WARNINGS))).toBe(JSON.stringify([
'Circular definition cycle: a.b.c.d, e.f.g, h.i, a.b.c.d',
]));
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);

resolveObject(helpers.fileToJSON(__dirname + '/../__json_files/circular_5.json'));
expect(GroupMessages.count(PROPERTY_REFERENCE_WARNINGS)).toBe(1);
expect(JSON.stringify(GroupMessages.fetchMessages(PROPERTY_REFERENCE_WARNINGS))).toBe(JSON.stringify([
'Circular definition cycle: l, m, l',
]));
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);
});

it('should correctly replace multiple references without reference errors', function() {
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);

var obj = resolveObject(helpers.fileToJSON(__dirname + '/../__json_files/not_circular.json'));
expect(GroupMessages.count(PROPERTY_REFERENCE_WARNINGS)).toBe(0);
expect(JSON.stringify(obj)).toBe(JSON.stringify({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not about this ticket, but just an occasion to show you one of my perplexities, which is clearly visible here: I am still unsure why the "input" of the tests is stored in a separate file (__json_files/..) while the "output" is in the test itself. I think there must be a better way :) (but let's discuss in a separate future ticket)

prop1: { value: 'test1 value' },
prop2: { value: 'test2 value' },
prop3: { value: 'test1 value' },
prop4: { value: 'test1 value' },
prop12: { value: 'test1 value, test2 value and some extra stuff' },
prop124: { value: 'test1 value, test2 value and test1 value' }
}));
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);
});

describe('ignoreKeys', () => {
Expand Down Expand Up @@ -238,11 +273,15 @@ describe('utils', () => {
});

it('should collect multiple reference errors', () => {
expect(
resolveObject.bind(null,
helpers.fileToJSON(__dirname + '/../__json_files/multiple_reference_errors.json')
)
).toThrow('Failed due to 3 errors:');
GroupMessages.clear(PROPERTY_REFERENCE_WARNINGS);

resolveObject(helpers.fileToJSON(__dirname + '/../__json_files/multiple_reference_errors.json'));
expect(GroupMessages.count(PROPERTY_REFERENCE_WARNINGS)).toBe(3);
expect(JSON.stringify(GroupMessages.fetchMessages(PROPERTY_REFERENCE_WARNINGS))).toBe(JSON.stringify([
"Reference doesn't exist: a.b tries to reference b.a, which is not defined",
"Reference doesn't exist: a.c tries to reference b.c, which is not defined",
"Reference doesn't exist: a.d tries to reference d, which is not defined"
]));
});

});
Expand Down
15 changes: 13 additions & 2 deletions lib/exportPlatform.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@

var resolveObject = require('./utils/resolveObject'),
transformObject = require('./transform/object'),
transformConfig = require('./transform/config');
transformConfig = require('./transform/config'),
GroupMessages = require('./utils/groupMessages');

var PROPERTY_REFERENCE_WARNINGS = GroupMessages.GROUP.PropertyReferenceWarnings;

/**
* Exports a properties object with applied
Expand Down Expand Up @@ -41,9 +44,17 @@ function exportPlatform(platform) {
// values like "1px solid {color.border.base}" we want to
// transform the original value (color.border.base) before
// replacing that value in the string.
return resolveObject(
var to_ret = resolveObject(
transformObject(this.properties, platformConfig)
);

if(GroupMessages.count(PROPERTY_REFERENCE_WARNINGS) > 0) {
var warnings = GroupMessages.flush(PROPERTY_REFERENCE_WARNINGS).join('\n');
console.log(`\n${PROPERTY_REFERENCE_WARNINGS}:\n${warnings}\n\n`);
throw new Error('Problems were found when trying to resolve property references');
}

return to_ret;
}


Expand Down
28 changes: 18 additions & 10 deletions lib/extend.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ var combineJSON = require('./utils/combineJSON'),
deepExtend = require('./utils/deepExtend'),
resolveCwd = require('resolve-cwd'),
_ = require('lodash'),
chalk = require('chalk');
chalk = require('chalk'),
GroupMessages = require('./utils/groupMessages');

var PROPERTY_VALUE_COLLISIONS = GroupMessages.GROUP.PropertyValueCollisions;

/**
* Either a string to a JSON file that contains configuration for the style dictionary or a plain Javascript object
Expand Down Expand Up @@ -113,18 +116,23 @@ function extend(opts) {
if (!_.isArray(options.source))
throw new Error('source must be an array');

var props = combineJSON(options.source, true, function Collision(opts) {
if (options.log) {
var str = `Collision detected at: ${opts.path.join('.')}! Original value: ${opts.target[opts.key]}, New value: ${opts.copy[opts.key]}`;
if (options.log === 'warn') {
console.warn(chalk.keyword('orange')(str));
} else if (options.log === 'error') {
throw new Error(str);
}
var props = combineJSON(options.source, true, function Collision(prop) {
GroupMessages.add(
PROPERTY_VALUE_COLLISIONS,
`Collision detected at: ${prop.path.join('.')}! Original value: ${prop.target[prop.key]}, New value: ${prop.copy[prop.key]}`
);
});

if(GroupMessages.count(PROPERTY_VALUE_COLLISIONS) > 0) {
var collisions = GroupMessages.flush(PROPERTY_VALUE_COLLISIONS).join('\n');
console.log(`\n${PROPERTY_VALUE_COLLISIONS}:\n${collisions}\n\n`);
if (options.log === 'error') {
throw new Error('Collisions detected');
}
}

});
to_ret.properties = deepExtend([{}, to_ret.properties, props]);

to_ret.source = null; // We don't want to carry over the source references
}

Expand Down
6 changes: 3 additions & 3 deletions lib/utils/deepExtend.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ function deepExtend(objects, collision, path) {
clone = src && _.isPlainObject(src) ? src : {};
}

path.push(name);
var nextPath = path.slice(0);
chazzmoney marked this conversation as resolved.
Show resolved Hide resolved
nextPath.push(name);

// Never move original objects, clone them
target[ name ] = deepExtend( [clone, copy], collision, path );
target[ name ] = deepExtend( [clone, copy], collision, nextPath );

// Don't bring in undefined values
} else if ( copy !== undefined ) {
Expand All @@ -74,7 +75,6 @@ function deepExtend(objects, collision, path) {
}
target[ name ] = copy;
}
path.pop();
}
}
}
Expand Down
49 changes: 49 additions & 0 deletions lib/utils/groupMessages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
var groupedMessages = {};

var GroupMessages = {
GROUP: {
PropertyReferenceWarnings: 'Property Reference Errors',
PropertyValueCollisions: 'Property Value Collisions',
},

flush: function (messageGroup) {
var messages = GroupMessages.fetchMessages(messageGroup);
GroupMessages.clear(messageGroup);
return messages;
},

add: function (messageGroup, message) {
if(messageGroup) {
if(!groupedMessages[messageGroup]) {
groupedMessages[messageGroup] = [];
}
groupedMessages[messageGroup].push(message);
}
},

count: function (messageGroup) {
return groupedMessages[messageGroup] ? groupedMessages[messageGroup].length : 0;
},

fetchMessages: function (messageGroup) {
return (messageGroup && groupedMessages[messageGroup]) || [];
},

clear: function (messageGroup) {
messageGroup && groupedMessages[messageGroup] && delete groupedMessages[messageGroup];
}
};

module.exports = GroupMessages;
Loading