From b5017afe1f58eeb2760dc8304d039f925ec799bd Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Mon, 11 Sep 2017 17:29:54 +0300 Subject: [PATCH] make csstree stable for base classes extensions (fixes #58) --- lib/lexer/structure.js | 21 ++++++++++--- lib/syntax/config/mix.js | 43 ++++++++++++++++--------- lib/walker/create.js | 61 +++++++++++++++++++++--------------- test/common.js | 27 ++++++++++++++++ test/fixture/parse/index.js | 8 ++--- test/fixture/syntax/index.js | 4 +-- test/grammar.js | 4 +-- test/walk.js | 16 +++++----- 8 files changed, 123 insertions(+), 61 deletions(-) diff --git a/lib/lexer/structure.js b/lib/lexer/structure.js index 3e27c27b..8fab378e 100644 --- a/lib/lexer/structure.js +++ b/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 @@ -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 + '`'); @@ -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() @@ -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'); + } } } } diff --git a/lib/syntax/config/mix.js b/lib/syntax/config/mix.js index 5abb6378..0d2e4a8a 100644 --- a/lib/syntax/config/mix.js +++ b/lib/syntax/config/mix.js @@ -1,3 +1,4 @@ +var hasOwnProperty = Object.prototype.hasOwnProperty; var shape = { generic: true, types: {}, @@ -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 { @@ -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])) { @@ -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; diff --git a/lib/walker/create.js b/lib/walker/create.js index f66426de..08092f09 100644 --- a/lib/walker/create.js +++ b/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': @@ -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]]; } @@ -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'); } } } @@ -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 { diff --git a/test/common.js b/test/common.js index ddb05922..9a5f3011 100644 --- a/test/common.js +++ b/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'); @@ -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']] + } + } + } + }); + }); + }); + }); }); diff --git a/test/fixture/parse/index.js b/test/fixture/parse/index.js index 1f887fb2..740f0fec 100644 --- a/test/fixture/parse/index.js +++ b/test/fixture/parse/index.js @@ -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); - } + }); }; } @@ -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); @@ -108,7 +108,7 @@ var tests = fs.readdirSync(__dirname).reduce(function(result, scope) { }); processTest(origTests[key], key, key); } - } + }); result[filename] = { scope: scope, diff --git a/test/fixture/syntax/index.js b/test/fixture/syntax/index.js index 0bf5564e..338831a8 100644 --- a/test/fixture/syntax/index.js +++ b/test/fixture/syntax/index.js @@ -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; } diff --git a/test/grammar.js b/test/grammar.js index 9419ffad..4436a7b5 100644 --- a/test/grammar.js +++ b/test/grammar.js @@ -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]); - } + }); }); }); diff --git a/test/walk.js b/test/walk.js index 3cab98a4..cfd5fae0 100644 --- a/test/walk.js +++ b/test/walk.js @@ -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)) { @@ -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); - } + }); }); }); });