Skip to content

Commit

Permalink
make csstree stable for base classes extensions (fixes #58)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Sep 11, 2017
1 parent 8dce690 commit b5017af
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 61 deletions.
21 changes: 16 additions & 5 deletions lib/lexer/structure.js
@@ -1,4 +1,5 @@
var List = require('../utils/list');
var hasOwnProperty = Object.prototype.hasOwnProperty;

function isValidNumber(value) {
// Number.isInteger(value) && value >= 0
Expand Down Expand Up @@ -26,6 +27,10 @@ function createNodeStructureChecker(type, fields) {
}

for (var key in node) {
if (hasOwnProperty.call(node, key) === false) {
continue;
}

if (key === 'type') {
if (node.type !== type) {
warn('Wrong node type `' + node.type + '` but expected `' + type + '`');
Expand Down Expand Up @@ -93,6 +98,10 @@ function processStructure(name, nodeType) {
};

for (var key in structure) {
if (hasOwnProperty.call(structure, key) === false) {
continue;
}

var docsTypes = [];
var fieldTypes = fields[key] = Array.isArray(structure[key])
? structure[key].slice()
Expand Down Expand Up @@ -128,12 +137,14 @@ module.exports = {

if (config.node) {
for (var name in config.node) {
var nodeType = config.node[name];
if (hasOwnProperty.call(config.node, name)) {
var nodeType = config.node[name];

if (nodeType.structure) {
structure[name] = processStructure(name, nodeType);
} else {
throw new Error('Missed `structure` field in `' + name + '` node type definition');
if (nodeType.structure) {
structure[name] = processStructure(name, nodeType);
} else {
throw new Error('Missed `structure` field in `' + name + '` node type definition');
}
}
}
}
Expand Down
43 changes: 29 additions & 14 deletions lib/syntax/config/mix.js
@@ -1,3 +1,4 @@
var hasOwnProperty = Object.prototype.hasOwnProperty;
var shape = {
generic: true,
types: {},
Expand All @@ -17,7 +18,9 @@ function copy(value) {
if (isObject(value)) {
var res = {};
for (var key in value) {
res[key] = value[key];
if (hasOwnProperty.call(value, key)) {
res[key] = value[key];
}
}
return res;
} else {
Expand All @@ -27,19 +30,27 @@ function copy(value) {

function extend(dest, src) {
for (var key in src) {
if (isObject(dest[key])) {
extend(dest[key], copy(src[key]));
} else {
dest[key] = copy(src[key]);
if (hasOwnProperty.call(src, key)) {
if (isObject(dest[key])) {
extend(dest[key], copy(src[key]));
} else {
dest[key] = copy(src[key]);
}
}
}
}

function mix(dest, src, shape) {
for (var key in shape) {
if (hasOwnProperty.call(shape, key) === false) {
continue;
}

if (shape[key] === true) {
if (key in src) {
dest[key] = copy(src[key]);
if (hasOwnProperty.call(src, key)) {
dest[key] = copy(src[key]);
}
}
} else if (shape[key]) {
if (isObject(shape[key])) {
Expand All @@ -54,17 +65,21 @@ function mix(dest, src, shape) {
return s;
}, {});
for (var name in dest[key]) {
res[name] = {};
if (dest[key] && dest[key][name]) {
mix(res[name], dest[key][name], innerShape);
if (hasOwnProperty.call(dest[key], name)) {
res[name] = {};
if (dest[key] && dest[key][name]) {
mix(res[name], dest[key][name], innerShape);
}
}
}
for (var name in src[key]) {
if (!res[name]) {
res[name] = {};
}
if (src[key] && src[key][name]) {
mix(res[name], src[key][name], innerShape);
if (hasOwnProperty.call(src[key], name)) {
if (!res[name]) {
res[name] = {};
}
if (src[key] && src[key][name]) {
mix(res[name], src[key][name], innerShape);
}
}
}
dest[key] = res;
Expand Down
61 changes: 35 additions & 26 deletions lib/walker/create.js
@@ -1,5 +1,7 @@
'use strict';

var hasOwnProperty = Object.prototype.hasOwnProperty;

function walkRules(node, item, list) {
switch (node.type) {
case 'StyleSheet':
Expand Down Expand Up @@ -142,14 +144,17 @@ function getWalkersFromStructure(name, nodeType) {
var walkers = [];

for (var key in structure) {
if (hasOwnProperty.call(structure, key) === false) {
continue;
}

var fieldTypes = structure[key];
var walker = {
name: key,
type: false,
nullable: false
};

var fieldTypes = structure[key];

if (!Array.isArray(structure[key])) {
fieldTypes = [structure[key]];
}
Expand Down Expand Up @@ -185,15 +190,17 @@ function getTypesFromConfig(config) {

if (config.node) {
for (var name in config.node) {
var nodeType = config.node[name];

if (nodeType.structure) {
var walkers = getWalkersFromStructure(name, nodeType);
if (walkers !== null) {
types[name] = walkers;
if (hasOwnProperty.call(config.node, name)) {
var nodeType = config.node[name];

if (nodeType.structure) {
var walkers = getWalkersFromStructure(name, nodeType);
if (walkers !== null) {
types[name] = walkers;
}
} else {
throw new Error('Missed `structure` field in `' + name + '` node type definition');
}
} else {
throw new Error('Missed `structure` field in `' + name + '` node type definition');
}
}
}
Expand Down Expand Up @@ -223,22 +230,24 @@ module.exports = function createWalker(config) {
var walkers = {};

for (var name in types) {
var config = types[name];
walkers[name] = Function('node', 'context', 'walk',
(config.context ? 'var old = context.' + config.context + ';\ncontext.' + config.context + ' = node;\n' : '') +
config.fields.map(function(field) {
var line = field.type === 'list'
? 'node.' + field.name + '.each(walk);'
: 'walk(node.' + field.name + ');';

if (field.nullable) {
line = 'if (node.' + field.name + ') {\n ' + line + '}';
}

return line;
}).join('\n') +
(config.context ? '\ncontext.' + config.context + ' = old;' : '')
);
if (hasOwnProperty.call(types, name)) {
var config = types[name];
walkers[name] = Function('node', 'context', 'walk',
(config.context ? 'var old = context.' + config.context + ';\ncontext.' + config.context + ' = node;\n' : '') +
config.fields.map(function(field) {
var line = field.type === 'list'
? 'node.' + field.name + '.each(walk);'
: 'walk(node.' + field.name + ');';

if (field.nullable) {
line = 'if (node.' + field.name + ') {\n ' + line + '}';
}

return line;
}).join('\n') +
(config.context ? '\ncontext.' + config.context + ' = old;' : '')
);
}
}

return {
Expand Down
27 changes: 27 additions & 0 deletions test/common.js
@@ -1,6 +1,7 @@
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var csstree = require('../lib');
var parse = require('../lib').parse;
var walk = require('../lib').walk;
var stringify = require('./helpers/stringify.js');
Expand Down Expand Up @@ -51,4 +52,30 @@ describe('Common', function() {
types.sort()
);
});

describe('extension in base classes should not cause to exception', function() {
beforeEach(function() {
Object.prototype.objectExtraField = function() {};
Array.prototype.arrayExtraField = function() {};
});
afterEach(function() {
delete Object.prototype.objectExtraField;
delete Array.prototype.arrayExtraField;
});

it('fork()', function() {
assert.doesNotThrow(function() {
csstree.fork({
node: {
Test: {
structure: {
foo: 'Rule',
bar: [['Rule']]
}
}
}
});
});
});
});
});
8 changes: 4 additions & 4 deletions test/fixture/parse/index.js
Expand Up @@ -44,9 +44,9 @@ function forEachTest(factory, errors) {
for (var filename in tests) {
var file = tests[filename];

for (var key in file[testType]) {
Object.keys(file[testType]).forEach(function(key) {
factory(file[testType][key].name, file[testType][key], file.scope);
}
});
};
}

Expand Down Expand Up @@ -92,7 +92,7 @@ var tests = fs.readdirSync(__dirname).reduce(function(result, scope) {
var tests = {};
var errors = {};

for (var key in origTests) {
Object.keys(origTests).forEach(function(key) {
if (Array.isArray(origTests[key])) {
origTests[key].forEach(function(test, idx) {
test.name = locator.get(key, idx);
Expand All @@ -108,7 +108,7 @@ var tests = fs.readdirSync(__dirname).reduce(function(result, scope) {
});
processTest(origTests[key], key, key);
}
}
});

result[filename] = {
scope: scope,
Expand Down
4 changes: 2 additions & 2 deletions test/fixture/syntax/index.js
Expand Up @@ -36,9 +36,9 @@ var testFiles = fs.readdirSync(__dirname).reduce(function(result, fn) {
var tests = require(filename);
var locator = new JsonLocator(filename);

for (var key in tests) {
Object.keys(tests).forEach(function(key) {
tests[key].name = locator.get(key);
}
});

result[filename] = tests;
}
Expand Down
4 changes: 2 additions & 2 deletions test/grammar.js
Expand Up @@ -97,9 +97,9 @@ describe('grammar', function() {

describe('parse/translate', function() {
['properties', 'types'].forEach(function(section) {
for (var name in data[section]) {
Object.keys(data[section]).forEach(function(name) {
createParseTest(section + '/' + name, data[section][name]);
}
});
});
});

Expand Down
16 changes: 8 additions & 8 deletions test/walk.js
Expand Up @@ -22,13 +22,13 @@ function expectedWalk(ast, right, checker) {
}

stack.push(node);
for (var key in node) {
Object.keys(node).forEach(function(key) {
if (Array.isArray(node[key])) {
node[key].forEach(walk);
} else if (node[key] && typeof node[key] === 'object') {
walk(node[key]);
}
}
});
stack.pop();

if (!right && checker(stack, node)) {
Expand Down Expand Up @@ -162,25 +162,25 @@ describe('AST traversal', function() {

describe('walk ruleset', function() {
testWithRules.forEach(function(file) {
for (var name in file.tests) {
Object.keys(file.tests).forEach(function(name) {
createWalkRulesTest(file.tests[name], file.scope, walkRules);
}
});
});
});

describe('walk rulesetRight', function() {
testWithRules.forEach(function(file) {
for (var name in file.tests) {
Object.keys(file.tests).forEach(function(name) {
createWalkRulesTest(file.tests[name], file.scope, walkRulesRight);
}
});
});
});

describe('walk declarations', function() {
testWithRules.forEach(function(file) {
for (var name in file.tests) {
Object.keys(file.tests).forEach(function(name) {
createWalkDeclarationsTest(file.tests[name], file.scope, walkDeclarations);
}
});
});
});
});

0 comments on commit b5017af

Please sign in to comment.