Fixes #462 - escaped apostrophes in selector rules.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 1 Mar 2015 16:49:01 +0000 (16:49 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 1 Mar 2015 18:07:53 +0000 (18:07 +0000)
QuoteScanner were not scanning correctly for escaped apostrophes.

History.md
lib/utils/quote-scanner.js
test/integration-test.js
test/selectors/tokenizer-test.js
test/utils/quote-scanner-test.js

index e3b51cb..414159f 100644 (file)
@@ -7,6 +7,7 @@
 ==================
 
 * Refixed issue [#471](https://github.com/jakubpawlowicz/clean-css/issues/471) - correct order after restructuring.
+* Fixes issue [#462](https://github.com/jakubpawlowicz/clean-css/issues/462) - escaped apostrophes in selectors.
 
 [3.1.1 / 2015-02-27](https://github.com/jakubpawlowicz/clean-css/compare/v3.1.0...v3.1.1)
 ==================
index 9e2c8dc..69980f5 100644 (file)
@@ -38,6 +38,19 @@ var findQuoteEnd = function (data, matched, cursor, oldCursor) {
   return cursor;
 };
 
+function findNext(data, mark, startAt) {
+  var escapeMark = '\\';
+  var candidate = startAt;
+
+  while (true) {
+    candidate = data.indexOf(mark, candidate + 1);
+    if (candidate == -1)
+      return -1;
+    if (data[candidate - 1] != escapeMark)
+      return candidate;
+  }
+}
+
 QuoteScanner.prototype.each = function (callback) {
   var data = this.data;
   var tempData = [];
@@ -50,8 +63,8 @@ QuoteScanner.prototype.each = function (callback) {
   var dataLength = data.length;
 
   for (; nextEnd < data.length;) {
-    var nextStartSingle = data.indexOf(singleMark, nextEnd + 1);
-    var nextStartDouble = data.indexOf(doubleMark, nextEnd + 1);
+    var nextStartSingle = findNext(data, singleMark, nextEnd);
+    var nextStartDouble = findNext(data, doubleMark, nextEnd);
 
     if (nextStartSingle == -1)
       nextStartSingle = dataLength;
index adccfdf..fdc9262 100644 (file)
@@ -337,6 +337,14 @@ vows.describe('integration tests').addBatch({
     'escaped @ symbol in id': '#id\\@sm{padding:0}',
     'escaped slash': 'a{content:"\\\\"}',
     'escaped quote': 'a{content:"\\\""}',
+    'escaped quote in selector name': [
+      '.this-class\\\'s-got-an-apostrophe{color:red}a{color:#f00}',
+      '.this-class\\\'s-got-an-apostrophe,a{color:red}'
+    ],
+    'escaped quotes in selector name': [
+      '.this-class\\\"s-got-an-apostrophes\\\'{color:red}a{color:#f00}',
+      '.this-class\\\"s-got-an-apostrophes\\\',a{color:red}'
+    ],
     'escaped tab': 'a{content:"\\\t"}'
   }),
   'important comments - one': cssContext({
index 32d4480..cc04461 100644 (file)
@@ -101,6 +101,14 @@ vows.describe(Tokenizer)
           body: [{ value: 'color:red' }]
         }]
       ],
+      'a selector with escaped quote': [
+        '.this-class\\\'s-got-an-apostrophe{}',
+        [{
+          kind: 'selector',
+          value: [{ value: '.this-class\\\'s-got-an-apostrophe' }],
+          body: []
+        }]
+      ],
       'a double selector': [
         'a,\n\ndiv.class > p {color:red}',
         [{
index e3e5ab9..a1e4709 100644 (file)
@@ -73,6 +73,28 @@ vows.describe(QuoteScanner)
         assert.equal(index, 1);
       }
     },
+    'one open-ended quote': {
+      topic: '.this-class\\\'s-got-an-apostrophe {}',
+      iterator: function (topic) {
+        var index = 0;
+        new QuoteScanner(topic).each(function iterator() {
+          index++;
+        });
+
+        assert.equal(index, 0);
+      }
+    },
+    'many open-ended quotes': {
+      topic: '.this-class\\\'s-got-many\\\"-apostrophes\\\' {}',
+      iterator: function (topic) {
+        var index = 0;
+        new QuoteScanner(topic).each(function iterator() {
+          index++;
+        });
+
+        assert.equal(index, 0);
+      }
+    },
     'two quotes': {
       topic: 'text with "one \\"quote" and \'another one\'!',
       iterator: function (topic) {