From 73f0ee7143afef71ff82e1a846fcc0dc9fc18dc5 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Thu, 12 Oct 2017 02:42:37 +0300 Subject: [PATCH] extend Declaration validator to check for known property name --- lib/syntax/node/Declaration.js | 122 ++++++++++++++++++--------------- test/lexer.js | 13 ++++ 2 files changed, 79 insertions(+), 56 deletions(-) diff --git a/lib/syntax/node/Declaration.js b/lib/syntax/node/Declaration.js index 67bd3fc3..f26e9c0c 100644 --- a/lib/syntax/node/Declaration.js +++ b/lib/syntax/node/Declaration.js @@ -1,5 +1,5 @@ +var resolveProperty = require('../../utils/names').property; var TYPE = require('../../tokenizer').TYPE; - var IDENTIFIER = TYPE.Identifier; var COLON = TYPE.Colon; var EXCLAMATIONMARK = TYPE.ExclamationMark; @@ -11,9 +11,60 @@ var SEMICOLON = TYPE.Semicolon; var PLUSSIGN = TYPE.PlusSign; var NUMBERSIGN = TYPE.NumberSign; +function isCustomProperty(name) { + return name.length >= 2 && + name.charCodeAt(0) === HYPHENMINUS && + name.charCodeAt(1) === HYPHENMINUS; +} + +function consumeProperty() { + var start = this.scanner.tokenStart; + var prefix = 0; + + // hacks + switch (this.scanner.tokenType) { + case ASTERISK: + case DOLLARSIGN: + case PLUSSIGN: + case NUMBERSIGN: + prefix = 1; + break; + + // TODO: not sure we should support this hack + case SOLIDUS: + prefix = this.scanner.lookupType(1) === SOLIDUS ? 2 : 1; + break; + } + + if (this.scanner.lookupType(prefix) === HYPHENMINUS) { + prefix++; + } + + if (prefix) { + this.scanner.skip(prefix); + } + + this.scanner.eat(IDENTIFIER); + + return this.scanner.substrToCursor(start); +} + +// ! sc* important +function consumeImportant(scanner) { + scanner.eat(EXCLAMATIONMARK); + scanner.skipSC(); + + var important = scanner.consume(IDENTIFIER); + + // store original value in case it differ from `important` + // for better original source restoring and hacks like `!ie` support + return important === 'important' ? true : important; +} + function consumeValueRaw(startToken) { return this.Raw(startToken, EXCLAMATIONMARK, SEMICOLON, false, true); } + function consumeCustomPropertyRaw(startToken) { return this.Raw(startToken, EXCLAMATIONMARK, SEMICOLON, false, false); } @@ -29,7 +80,7 @@ module.exports = { parse: function() { var start = this.scanner.tokenStart; var startToken = this.scanner.currentToken; - var property = readProperty.call(this); + var property = consumeProperty.call(this); var customProperty = isCustomProperty(property); var parseValue = customProperty ? this.parseCustomProperty : this.parseValue; var consumeRaw = customProperty ? consumeCustomPropertyRaw : consumeValueRaw; @@ -60,7 +111,7 @@ module.exports = { } if (this.scanner.tokenType === EXCLAMATIONMARK) { - important = getImportant(this.scanner); + important = consumeImportant(this.scanner); this.scanner.skipSC(); } @@ -95,59 +146,18 @@ module.exports = { } }, validate: function(node, warn) { - var match = this.matchDeclaration(node); - if (match.error) { - warn(node, match.error); + var property = resolveProperty(node.property); + var name = node.property.substr(property.hack.length); // TODO: replace for property.name, see https://github.com/csstree/csstree/issues/63 + if (name.toLowerCase() in this.defs.Declaration === false) { + warn(node, new SyntaxError('Unknown property `' + name + '`')); + } else { + var match = this.matchDeclaration(node); + if (match.error) { + if (match.error.name === 'SyntaxMatchError' && + match.error.name === 'SyntaxReferenceError') { + warn(node, match.error); + } + } } } }; - -function isCustomProperty(name) { - return name.length >= 2 && - name.charCodeAt(0) === HYPHENMINUS && - name.charCodeAt(1) === HYPHENMINUS; -} - -function readProperty() { - var start = this.scanner.tokenStart; - var prefix = 0; - - // hacks - switch (this.scanner.tokenType) { - case ASTERISK: - case DOLLARSIGN: - case PLUSSIGN: - case NUMBERSIGN: - prefix = 1; - break; - - // TODO: not sure we should support this hack - case SOLIDUS: - prefix = this.scanner.lookupType(1) === SOLIDUS ? 2 : 1; - break; - } - - if (this.scanner.lookupType(prefix) === HYPHENMINUS) { - prefix++; - } - - if (prefix) { - this.scanner.skip(prefix); - } - - this.scanner.eat(IDENTIFIER); - - return this.scanner.substrToCursor(start); -} - -// ! ws* important -function getImportant(scanner) { - scanner.eat(EXCLAMATIONMARK); - scanner.skipSC(); - - var important = scanner.consume(IDENTIFIER); - - // store original value in case it differ from `important` - // for better original source restoring and hacks like `!ie` support - return important === 'important' ? true : important; -} diff --git a/test/lexer.js b/test/lexer.js index d9ca7510..d03a70ce 100644 --- a/test/lexer.js +++ b/test/lexer.js @@ -275,6 +275,19 @@ describe('lexer', function() { assert.equal(errors[0].node, ast.children.last()); assert.equal(errors[0].error.message, 'Unknown unit `toString`'); }); + + it('Declaration', function() { + // using toString as bad name we check 2 things: bad name matching and + // false positive matching b/c of wrong search for name existance in dict + var ast = parseCss('color: red; Color: red; //color: red; //-vendor-color: red; toString: red', { context: 'declarationList' }); + var errors = syntax.lexer.checkValidity(ast); + + assert.equal(errors.length, 2); + assert.equal(errors[0].node, ast.children.tail.prev.data); + assert.equal(errors[0].error.message, 'Unknown property `-vendor-color`'); + assert.equal(errors[1].node, ast.children.last()); + assert.equal(errors[1].error.message, 'Unknown property `toString`'); + }); }); describe('matchProperty()', function() {