Fixes tokenization on extra close brace.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 6 Dec 2016 08:06:48 +0000 (09:06 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Fri, 16 Dec 2016 10:49:33 +0000 (11:49 +0100)
Why:

* It should be ignored when at the end of tokenized string, but
  preserved when in the middle.

lib/optimizer/basic.js
lib/optimizer/merge-adjacent.js
lib/optimizer/merge-non-adjacent-by-body.js
lib/optimizer/tidy-rules.js
lib/tokenizer/tokenize.js
test/integration-test.js
test/tokenizer/tokenize-test.js

index 248106a..7a42daa 100644 (file)
@@ -546,7 +546,7 @@ function basicOptimize(tokens, context) {
         optimizeComment(token, options);
         break;
       case Token.RULE:
-        token[1] = tidyRules(token[1], !ie7Hack, adjacentSpace);
+        token[1] = tidyRules(token[1], !ie7Hack, adjacentSpace, context.warnings);
         optimizeBody(token[2], context);
         afterRules = true;
         break;
index 622902b..988e376 100644 (file)
@@ -27,7 +27,7 @@ function mergeAdjacent(tokens, context) {
       token[2] = [];
     } else if (lastToken[0] == Token.RULE && stringifyBody(token[2]) == stringifyBody(lastToken[2]) &&
         !isSpecial(options, stringifyRules(token[1])) && !isSpecial(options, stringifyRules(lastToken[1]))) {
-      lastToken[1] = tidyRules(lastToken[1].concat(token[1]), false, adjacentSpace);
+      lastToken[1] = tidyRules(lastToken[1].concat(token[1]), false, adjacentSpace, context.warnings);
       token[2] = [];
     } else {
       lastToken = token;
index 2549cce..b5eda74 100644 (file)
@@ -50,7 +50,7 @@ function mergeNonAdjacentByBody(tokens, context) {
     var oldToken = candidates[candidateBody];
     if (oldToken && !isSpecial(options, stringifyRules(token[1])) && !isSpecial(options, stringifyRules(oldToken[1]))) {
       token[1] = token[2].length > 0 ?
-        tidyRules(oldToken[1].concat(token[1]), false, adjacentSpace) :
+        tidyRules(oldToken[1].concat(token[1]), false, adjacentSpace, context.warnings) :
         oldToken[1].concat(token[1]);
 
       oldToken[2] = [];
index 5a38f14..d5c8a6d 100644 (file)
@@ -6,6 +6,41 @@ var WHITESPACE_PATTERN = /\s/;
 var STAR_PLUS_HTML_HACK = '*+html ';
 var STAR_FIRST_CHILD_PLUS_HTML_HACK = '*:first-child+html ';
 
+function hasInvalidCharacters(value) {
+  return value.indexOf(Marker.SINGLE_QUOTE) > -1 || value.indexOf(Marker.DOUBLE_QUOTE) > -1 ?
+    hasInvalidCharactersWithQuotes(value) :
+    hasInvalidCharactersWithoutQuotes(value);
+}
+
+function hasInvalidCharactersWithQuotes(value) {
+  var isEscaped;
+  var isInvalid = false;
+  var character;
+  var isQuote = false;
+  var i, l;
+
+  for (i = 0, l = value.length; i < l; i++) {
+    character = value[i];
+
+    if (isEscaped) {
+      // continue as always
+    } else if (character == Marker.SINGLE_QUOTE || character == Marker.DOUBLE_QUOTE) {
+      isQuote = !isQuote;
+    } else if (character == Marker.CLOSE_BRACE && !isQuote) {
+      isInvalid = true;
+      break;
+    }
+
+    isEscaped = character == Marker.BACK_SLASH;
+  }
+
+  return isInvalid;
+}
+
+function hasInvalidCharactersWithoutQuotes(value) {
+  return value.indexOf(Marker.CLOSE_BRACE) > -1;
+}
+
 function removeWhitespace(value) {
   var stripped = [];
   var character;
@@ -93,7 +128,7 @@ function ruleSorter(s1, s2) {
   return s1[0] > s2[0] ? 1 : -1;
 }
 
-function tidyRules(rules, removeUnsupported, adjacentSpace) {
+function tidyRules(rules, removeUnsupported, adjacentSpace, warnings) {
   var list = [];
   var repeated = [];
 
@@ -101,6 +136,11 @@ function tidyRules(rules, removeUnsupported, adjacentSpace) {
     var rule = rules[i];
     var reduced = rule[0];
 
+    if (hasInvalidCharacters(reduced)) {
+      warnings.push('Invalid selector \'' + rule[0] + '\' at line ' + rule[1][0][0] + ', column ' + rule[1][0][1] + '. Ignoring.');
+      continue;
+    }
+
     reduced = removeWhitespace(reduced);
     reduced = removeQuotes(reduced);
 
index ed9b73a..c652c7a 100644 (file)
@@ -290,10 +290,10 @@ function intoTokens(source, externalContext, internalContext, isNested) {
 
       level = levels.pop();
       seekingValue = false;
-    } else if (character == Marker.CLOSE_BRACE && level == Level.BLOCK && !isNested && position.index < source.length - 1) {
-      // stray close brace at block level, e.g. @media screen {...}}<--
-      externalContext.warnings.push('Extra \'}\' at line ' + position.line + ', column ' + position.column);
-      // noop
+    } else if (character == Marker.CLOSE_BRACE && level == Level.BLOCK && !isNested && position.index <= source.length - 1) {
+      // stray close brace at block level, e.g. a{color:red}color:blue}<--
+      externalContext.warnings.push('Unexpected \'}\' at line ' + position.line + ', column ' + position.column + '.');
+      buffer.push(character);
     } else if (character == Marker.CLOSE_BRACE && level == Level.BLOCK) {
       // close brace at block level, e.g. @media screen {...}<--
       break;
index 28bfe7d..199c87a 100644 (file)
@@ -2277,11 +2277,11 @@ vows.describe('integration tests')
     optimizerContext('invalid data tokenization', {
       'extra top-level closing brace': [
         'a{color:red}}p{width:auto}',
-        'a{color:red}p{width:auto}'
+        'a{color:red}'
       ],
       'extra top-level closing braces': [
         'a{color:red}}}}p{width:auto}',
-        'a{color:red}p{width:auto}'
+        'a{color:red}'
       ]
     })
   )
index e8a11f4..e69a08c 100644 (file)
@@ -3335,9 +3335,9 @@ vows.describe(tokenize)
             'rule',
             [
               [
-                'a',
+                '}a',
                 [
-                  [1, 16, undefined]
+                  [1, 15, undefined]
                 ]
               ]
             ],