diff --git a/lib/lexer/Lexer.js b/lib/lexer/Lexer.js index fefcb7aa..5a4ba845 100644 --- a/lib/lexer/Lexer.js +++ b/lib/lexer/Lexer.js @@ -38,6 +38,26 @@ function valueHasVar(value) { return hasVar; } +// check node is \0 or \9 hack +function isHack(node) { + return node.type === 'Identifier' && /^\\[09]/.test(node.name); +} + +// white spaces, comments and some hacks can to be ignored at the end of value +function isNextMayToBeIgnored(cursor) { + while (cursor !== null) { + if (cursor.data.type !== 'WhiteSpace' && + cursor.data.type !== 'Comment' && + !isHack(cursor.data)) { + return false; + } + + cursor = cursor.next; + } + + return true; +} + function matchSyntax(lexer, syntax, value) { var result; @@ -65,7 +85,7 @@ function matchSyntax(lexer, syntax, value) { } } - if (result.next) { + if (result.next && !isNextMayToBeIgnored(result.next)) { lexer.lastMatchError = new MatchError('Uncomplete match', lexer, syntax.syntax, value, result.badNode || unwrapNode(result.next)); return null; } diff --git a/lib/lexer/match.js b/lib/lexer/match.js index bb8dd4a3..83ffa210 100644 --- a/lib/lexer/match.js +++ b/lib/lexer/match.js @@ -312,9 +312,15 @@ module.exports = function match(syntax, syntaxNode, node) { if (node.data.type === 'Identifier') { var keyword = names.keyword(node.data.name); + var keywordName = keyword.name; var name = syntaxNode.name.toLowerCase(); - if (name !== keyword.vendor + keyword.name) { + // drop \0 and \9 hack from keyword name + if (keywordName.indexOf('\\') !== -1) { + keywordName = keywordName.replace(/\\[09].*$/, ''); + } + + if (name !== keyword.vendor + keywordName) { break mismatch; } } else { diff --git a/lib/syntax/scope/default.js b/lib/syntax/scope/default.js index d6304517..8b85755f 100644 --- a/lib/syntax/scope/default.js +++ b/lib/syntax/scope/default.js @@ -13,6 +13,7 @@ var COMMA = TYPE.Comma; var SOLIDUS = TYPE.Solidus; var ASTERISK = TYPE.Asterisk; var PERCENTSIGN = TYPE.PercentSign; +var BACKSLASH = TYPE.Backslash; var U = 117; // 'u'.charCodeAt(0) module.exports = function defaultRecognizer(context) { @@ -46,7 +47,12 @@ module.exports = function defaultRecognizer(context) { return this.Percentage(); case IDENTIFIER: - return this.Dimension(); + // edge case: number with folowing \0 and \9 hack shouldn't to be a Dimension + if (cmpChar(this.scanner.source, this.scanner.tokenEnd, BACKSLASH)) { + return this.Number(); + } else { + return this.Dimension(); + } default: return this.Number(); diff --git a/test/fixture/parse/value/Value.json b/test/fixture/parse/value/Value.json index afc3ff26..0b5eb301 100644 --- a/test/fixture/parse/value/Value.json +++ b/test/fixture/parse/value/Value.json @@ -144,6 +144,40 @@ ] } }, + "should parse number with following \\0 or \\9 hack as Number": [ + { + "source": "1\\0", + "ast": { + "type": "Value", + "children": [ + { + "type": "Number", + "value": "1" + }, + { + "type": "Identifier", + "name": "\\0" + } + ] + } + }, + { + "source": "1\\9", + "ast": { + "type": "Value", + "children": [ + { + "type": "Number", + "value": "1" + }, + { + "type": "Identifier", + "name": "\\9" + } + ] + } + } + ], "should parse a custom property value when parseCustomProperty is true": { "options": { "property": "--var", diff --git a/test/fixture/syntax/edgecases.json b/test/fixture/syntax/edgecases.json index 978378af..9ac5e2b3 100644 --- a/test/fixture/syntax/edgecases.json +++ b/test/fixture/syntax/edgecases.json @@ -12,5 +12,37 @@ "invalid:Mismatch": [ "300" ] + }, + "\\9 hack": { + "syntax": { + "generic": true, + "properties": { + "test": "foo | | " + } + }, + "valid": [ + "foo\\9", + "foo \\9", + "0\\9", + "0 \\9", + "1px\\9", + "1px \\9" + ] + }, + "\\0 hack": { + "syntax": { + "generic": true, + "properties": { + "test": "foo | | " + } + }, + "valid": [ + "foo\\0", + "foo \\0", + "0\\0", + "0 \\0", + "1px\\0", + "1px \\0" + ] } }