Repository: postcss/postcss-selector-parser
Pull request #72 (2016-06-28 19:11:48)

Better handling `quoted` inside attribute selector.
https://github.com/postcss/postcss-selector-parser/issues/71
Maybe we should rewrite `attribute` selector parsing, inside `attribute` and `value` have trimmed value. And add `raws.spaceBeforeAttribute`, `raws.spaceAfterAttribute`, `raws.spaceBeforeOperator`, `raws.spaceAfterOperator`, `raws.spaceBeforeValue` and `raws.spaceAfterValue`. If you say yes on this i will help with this.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/6788475/badge)](https://coveralls.io/builds/6788475)

Coverage remained the same at 98.621% when pulling **0a2a8d4b89dc47eab1617add1693b3a647f471be on evilebottnawi:issue-71** into **e7866161c38fd63d3b273ff6df9863a12588b763 on postcss:master**.

evilebottnawi: /cc @ben-eb 
ben-eb: I think I would prefer rewriting the parsing of attribute selectors; right now this isn't handled as well as it could be. Do we need to specify whitespace before & after or can we get away with something like this?

```js
// [href= "test" ]
const node = {
  type: 'attribute',
  value: '"test"',
  attribute: 'href',
  raws: {
    value: ' "test" '
  }
};
```
evilebottnawi: @ben-eb right now is good, but in future be good rewrite as i was wrote above(`spaceBeforeOperator`, `spaceAfterOperator` and etc). It would help us (`stylelint`) a lot more convenient to handle attribute selectors.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 3fa2e1c..25b5383 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,14 @@
 sudo: false
 
 language: node_js
-node_js:
-  - '5'
-  - '4'
-  - '0.12'
+matrix:
+  include:
+    - node_js: '5'
+    - node_js: '4'
+    - node_js: '0.12'
+      env: NO_ESLINT=true
+
+script: "[[ $NO_ESLINT == true ]] && npm run test-012 || npm test"
 
 after_success:
   - './node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls'
diff --git a/package.json b/package.json
index 5dcde40..8a3a808 100644
--- a/package.json
+++ b/package.json
@@ -10,13 +10,16 @@
     "babel-preset-es2015": "^6.3.13",
     "babel-preset-es2015-loose": "^7.0.0",
     "babel-preset-stage-0": "^6.3.13",
+    "babel-register": "^6.9.0",
     "coveralls": "^2.11.6",
     "del-cli": "^0.2.0",
-    "eslint": "^2.1.0",
-    "eslint-config-cssnano": "^2.0.0",
+    "eslint": "^3.0.0",
+    "eslint-config-cssnano": "^3.0.0",
+    "eslint-plugin-babel": "^3.3.0",
+    "eslint-plugin-import": "^1.10.2",
     "glob": "^7.0.3",
     "minimist": "^1.2.0",
-    "nyc": "^6.0.0"
+    "nyc": "^7.0.0"
   },
   "main": "dist/index.js",
   "files": [
@@ -29,8 +32,8 @@
     "pretest": "eslint src",
     "prepublish": "del-cli dist && BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/",
     "report": "nyc report --reporter=html",
-    "test": "nyc node batchTests.js 'src/__tests__/*.js'",
-    "test-fast": "nyc ava src/__tests__/*.js"
+    "test": "nyc ava src/__tests__/*.js",
+    "test-012": "nyc ava src/__tests__/*.js"
   },
   "dependencies": {
     "flatten": "^1.0.2",
@@ -46,8 +49,8 @@
   },
   "repository": "postcss/postcss-selector-parser",
   "ava": {
-    "require": "babel-core/register",
-    "serial": true
+    "require": "babel-register",
+    "concurrency": 5
   },
   "nyc": {
     "exclude": [
diff --git a/src/index.js b/src/index.js
index 8650018..3198f24 100644
--- a/src/index.js
+++ b/src/index.js
@@ -13,7 +13,7 @@ import Tag        from './selectors/tag';
 import Universal  from './selectors/universal';
 import * as types from './selectors/types';
 
-let parser = processor => new Processor(processor);
+const parser = processor => new Processor(processor);
 
 parser.attribute = opts => new Attribute(opts);
 parser.className = opts => new ClassName(opts);
@@ -32,7 +32,7 @@ Object.keys(types).forEach(type => {
     if (type === '__esModule') {
         return;
     }
-    parser[type] = types[type];
+    parser[type] = types[type]; // eslint-disable-line
 });
 
 export default parser;
diff --git a/src/parser.js b/src/parser.js
index 602e8a6..c14ddb8 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -1,3 +1,7 @@
+import flatten from 'flatten';
+import indexesOf from 'indexes-of';
+import uniq from 'uniq';
+
 import Root from './selectors/root';
 import Selector from './selectors/selector';
 import ClassName from './selectors/className';
@@ -14,10 +18,6 @@ import Nesting from './selectors/nesting';
 import sortAsc from './sortAscending';
 import tokenize from './tokenize';
 
-import flatten from 'flatten';
-import indexesOf from 'indexes-of';
-import uniq from 'uniq';
-
 import * as types from './selectors/types';
 
 export default class Parser {
@@ -55,14 +55,14 @@ export default class Parser {
             source: {
                 start: {
                     line: startingToken[2],
-                    column: startingToken[3]
+                    column: startingToken[3],
                 },
                 end: {
                     line: this.currToken[2],
-                    column: this.currToken[3]
-                }
+                    column: this.currToken[3],
+                },
             },
-            sourceIndex: startingToken[4]
+            sourceIndex: startingToken[4],
         };
         if (namespace.length > 1) {
             if (namespace[0] === '') {
@@ -82,8 +82,9 @@ export default class Parser {
                 attr.insensitive = true;
                 attr.raws.insensitive = insensitive[1];
             }
-            attr.quoted = attr.value[0] === '\'' || attr.value[0] === '"';
-            attr.raws.unquoted = (attr.quoted) ? attr.value.slice(1, -1) : attr.value;
+            let trimmedValue = attr.value.trim();
+            attr.quoted = trimmedValue[0] === '\'' || trimmedValue[0] === '"';
+            attr.raws.unquoted = (attr.quoted) ? trimmedValue.slice(1, -1) : trimmedValue;
         }
         this.newNode(attr);
         this.position++;
@@ -98,14 +99,14 @@ export default class Parser {
             source: {
                 start: {
                     line: this.currToken[2],
-                    column: this.currToken[3]
+                    column: this.currToken[3],
                 },
                 end: {
                     line: this.currToken[2],
-                    column: this.currToken[3]
-                }
+                    column: this.currToken[3],
+                },
             },
-            sourceIndex: this.currToken[4]
+            sourceIndex: this.currToken[4],
         });
         while ( this.position < this.tokens.length && this.currToken &&
                 (this.currToken[0] === 'space' ||
@@ -145,14 +146,14 @@ export default class Parser {
             source: {
                 start: {
                     line: this.currToken[2],
-                    column: this.currToken[3]
+                    column: this.currToken[3],
                 },
                 end: {
                     line: this.currToken[4],
-                    column: this.currToken[5]
-                }
+                    column: this.currToken[5],
+                },
             },
-            sourceIndex: this.currToken[6]
+            sourceIndex: this.currToken[6],
         });
         this.newNode(node);
         this.position++;
@@ -187,14 +188,14 @@ export default class Parser {
             source: {
                 start: {
                     line: this.currToken[2],
-                    column: this.currToken[3]
+                    column: this.currToken[3],
                 },
                 end: {
                     line: this.currToken[2],
-                    column: this.currToken[3]
-                }
+                    column: this.currToken[3],
+                },
             },
-            sourceIndex: this.currToken[4]
+            sourceIndex: this.currToken[4],
         }));
         this.position ++;
     }
@@ -266,14 +267,14 @@ export default class Parser {
                     source: {
                         start: {
                             line: startingToken[2],
-                            column: startingToken[3]
+                            column: startingToken[3],
                         },
                         end: {
                             line: this.currToken[4],
-                            column: this.currToken[5]
-                        }
+                            column: this.currToken[5],
+                        },
                     },
-                    sourceIndex: startingToken[4]
+                    sourceIndex: startingToken[4],
                 });
                 this.newNode(pseudo);
                 if (length > 1 && this.nextToken && this.nextToken[0] === '(') {
@@ -306,14 +307,14 @@ export default class Parser {
             source: {
                 start: {
                     line: token[2],
-                    column: token[3]
+                    column: token[3],
                 },
                 end: {
                     line: token[4],
-                    column: token[5]
-                }
+                    column: token[5],
+                },
             },
-            sourceIndex: token[6]
+            sourceIndex: token[6],
         }));
         this.position++;
     }
@@ -329,14 +330,14 @@ export default class Parser {
             source: {
                 start: {
                     line: this.currToken[2],
-                    column: this.currToken[3]
+                    column: this.currToken[3],
                 },
                 end: {
                     line: this.currToken[2],
-                    column: this.currToken[3]
-                }
+                    column: this.currToken[3],
+                },
             },
-            sourceIndex: this.currToken[4]
+            sourceIndex: this.currToken[4],
         }), namespace);
         this.position ++;
     }
@@ -378,14 +379,14 @@ export default class Parser {
                     source: {
                         start: {
                             line: this.currToken[2],
-                            column: this.currToken[3] + ind
+                            column: this.currToken[3] + ind,
                         },
                         end: {
                             line: this.currToken[4],
-                            column: this.currToken[3] + (index - 1)
-                        }
+                            column: this.currToken[3] + (index - 1),
+                        },
                     },
-                    sourceIndex: this.currToken[6] + indices[i]
+                    sourceIndex: this.currToken[6] + indices[i],
                 });
             } else if (~hasId.indexOf(ind)) {
                 node = new ID({
@@ -393,14 +394,14 @@ export default class Parser {
                     source: {
                         start: {
                             line: this.currToken[2],
-                            column: this.currToken[3] + ind
+                            column: this.currToken[3] + ind,
                         },
                         end: {
                             line: this.currToken[4],
-                            column: this.currToken[3] + (index - 1)
-                        }
+                            column: this.currToken[3] + (index - 1),
+                        },
                     },
-                    sourceIndex: this.currToken[6] + indices[i]
+                    sourceIndex: this.currToken[6] + indices[i],
                 });
             } else {
                 node = new Tag({
@@ -408,14 +409,14 @@ export default class Parser {
                     source: {
                         start: {
                             line: this.currToken[2],
-                            column: this.currToken[3] + ind
+                            column: this.currToken[3] + ind,
                         },
                         end: {
                             line: this.currToken[4],
-                            column: this.currToken[3] + (index - 1)
-                        }
+                            column: this.currToken[3] + (index - 1),
+                        },
                     },
-                    sourceIndex: this.currToken[6] + indices[i]
+                    sourceIndex: this.currToken[6] + indices[i],
                 });
             }
             this.newNode(node, namespace);
diff --git a/src/processor.js b/src/processor.js
index b5e20cf..a6969e9 100644
--- a/src/processor.js
+++ b/src/processor.js
@@ -11,7 +11,7 @@ export default class Processor {
             css: selectors,
             error: (e) => {
                 throw new Error(e);
-            }
+            },
         });
         this.res = input;
         this.func(input);
diff --git a/src/selectors/attribute.js b/src/selectors/attribute.js
index 6a01786..65c370e 100644
--- a/src/selectors/attribute.js
+++ b/src/selectors/attribute.js
@@ -13,7 +13,7 @@ export default class Attribute extends Namespace {
             this.spaces.before,
             '[',
             this.ns,
-            this.attribute
+            this.attribute,
         ];
 
         if (this.operator) {
diff --git a/src/selectors/className.js b/src/selectors/className.js
index 2427f48..ac50924 100644
--- a/src/selectors/className.js
+++ b/src/selectors/className.js
@@ -12,7 +12,7 @@ export default class ClassName extends Namespace {
             this.spaces.before,
             this.ns,
             String('.' + this.value),
-            this.spaces.after
+            this.spaces.after,
         ].join('');
     }
 }
diff --git a/src/selectors/id.js b/src/selectors/id.js
index d1c02d1..2b9dc74 100644
--- a/src/selectors/id.js
+++ b/src/selectors/id.js
@@ -12,7 +12,7 @@ export default class ID extends Namespace {
             this.spaces.before,
             this.ns,
             String('#' + this.value),
-            this.spaces.after
+            this.spaces.after,
         ].join('');
     }
 }
diff --git a/src/selectors/namespace.js b/src/selectors/namespace.js
index 7f3b32e..30d031e 100644
--- a/src/selectors/namespace.js
+++ b/src/selectors/namespace.js
@@ -2,7 +2,7 @@ import Node from './node';
 
 export default class Namespace extends Node {
     get ns () {
-        let n = this.namespace;
+        const n = this.namespace;
         return n ? (typeof n === 'string' ? n : '') + '|' : '';
     }
 
@@ -11,7 +11,7 @@ export default class Namespace extends Node {
             this.spaces.before,
             this.ns,
             String(this.value),
-            this.spaces.after
+            this.spaces.after,
         ].join('');
     }
 };
diff --git a/src/selectors/node.js b/src/selectors/node.js
index 4bd16ec..b8cf4ee 100644
--- a/src/selectors/node.js
+++ b/src/selectors/node.js
@@ -72,7 +72,7 @@ export default class {
         return [
             this.spaces.before,
             String(this.value),
-            this.spaces.after
+            this.spaces.after,
         ].join('');
     }
 }
diff --git a/src/selectors/pseudo.js b/src/selectors/pseudo.js
index a92b926..0040b87 100644
--- a/src/selectors/pseudo.js
+++ b/src/selectors/pseudo.js
@@ -13,7 +13,7 @@ export default class Pseudo extends Container {
             this.spaces.before,
             String(this.value),
             params,
-            this.spaces.after
+            this.spaces.after,
         ].join('');
     }
 }
diff --git a/src/tokenize.js b/src/tokenize.js
index d0e1e78..9b9199a 100644
--- a/src/tokenize.js
+++ b/src/tokenize.js
@@ -96,7 +96,7 @@ export default function tokenize (input) {
         case asterisk:
             tokens.push(['*', '*', line, pos - offset, pos]);
             break;
-        
+
         case ampersand:
             tokens.push(['&', '&', line, pos - offset, pos]);
             break;
@@ -149,7 +149,7 @@ export default function tokenize (input) {
             tokens.push(['string', css.slice(pos, next + 1),
                 line, pos  - offset,
                 line, next - offset,
-                pos
+                pos,
             ]);
             pos = next;
             break;
@@ -165,7 +165,7 @@ export default function tokenize (input) {
             tokens.push(['at-word', css.slice(pos, next + 1),
                 line, pos  - offset,
                 line, next - offset,
-                pos
+                pos,
             ]);
             pos = next;
             break;
@@ -189,7 +189,7 @@ export default function tokenize (input) {
             tokens.push(['word', css.slice(pos, next + 1),
                 line, pos  - offset,
                 line, next - offset,
-                pos
+                pos,
             ]);
             pos = next;
             break;
@@ -216,7 +216,7 @@ export default function tokenize (input) {
                 tokens.push(['comment', content,
                     line,     pos  - offset,
                     nextLine, next - nextOffset,
-                    pos
+                    pos,
                 ]);
 
                 offset = nextOffset;
@@ -235,7 +235,7 @@ export default function tokenize (input) {
                 tokens.push(['word', css.slice(pos, next + 1),
                     line, pos  - offset,
                     line, next - offset,
-                    pos
+                    pos,
                 ]);
                 pos = next;
             }


Test patch:
diff --git a/batchTests.js b/batchTests.js
deleted file mode 100644
index 3d019df..0000000
--- a/batchTests.js
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env node
-
-var glob = require('glob');
-var spawn = require('child_process').spawn;
-var files = require('minimist')(process.argv.slice(2))._[0];
-
-function throttlePromise (myArray, iterator, limit) {
-    var pickUpNextTask = function () {
-        if (myArray.length) {
-            return iterator(myArray.shift());
-        }
-    };
-  
-    function startChain () {
-        return Promise.resolve().then(function next () {
-            return pickUpNextTask().then(next);
-        });
-    }
-
-    var chains = [];
-    for (var k = 0; k < limit; k += 1) {
-        chains.push(startChain());
-    }
-    return Promise.all(chains);
-};
-
-function spawnAva (file) {
-    return new Promise(function (resolve, reject) {
-        var ps = spawn(process.execPath, ['node_modules/.bin/ava', file]);
-
-        ps.stdout.pipe(process.stdout);
-        ps.stderr.pipe(process.stderr);
-
-        ps.on('close', function (code) {
-            if (code === 0) {
-                return resolve(code);
-            }
-            return reject(code);
-        });
-    });
-}
-
-glob(files, function (err, tests) {
-    if (err) {
-        throw err;
-    }
-    return throttlePromise(tests, spawnAva, 2);
-});
diff --git a/src/__tests__/attributes.js b/src/__tests__/attributes.js
index e00f245..679580e 100644
--- a/src/__tests__/attributes.js
+++ b/src/__tests__/attributes.js
@@ -218,6 +218,8 @@ test('spaces in attribute selectors', 'h1[  href  *=  "test"  ]', (t, tree) => {
     t.deepEqual(tree.nodes[0].nodes[1].attribute, '  href  ');
     t.deepEqual(tree.nodes[0].nodes[1].operator, '*=');
     t.deepEqual(tree.nodes[0].nodes[1].value, '  "test"  ');
+    t.truthy(tree.nodes[0].nodes[1].quoted);
+    t.deepEqual(tree.nodes[0].nodes[1].raws.unquoted, 'test');
 });
 
 test('insensitive attribute selector 1', '[href="test" i]', (t, tree) => {
diff --git a/src/__tests__/container.js b/src/__tests__/container.js
index 19f1717..2113be1 100644
--- a/src/__tests__/container.js
+++ b/src/__tests__/container.js
@@ -1,6 +1,6 @@
 import test from 'ava';
+import parser from '..';
 import {parse} from './util/helpers';
-import parser from '../index';
 
 test('container#each', (t) => {
     let str = '';
@@ -136,7 +136,7 @@ test('container#map', (t) => {
 test('container#every', (t) => {
     parse('.one.two.three', (selectors) => {
         let allClasses = selectors.first.every((selector) => {
-            return selector.type = 'class';
+            return selector.type === 'class';
         });
         t.truthy(allClasses);
     });
@@ -145,7 +145,7 @@ test('container#every', (t) => {
 test('container#some', (t) => {
     parse('one#two.three', (selectors) => {
         let someClasses = selectors.first.some((selector) => {
-            return selector.type = 'class';
+            return selector.type === 'class';
         });
         t.truthy(someClasses);
     });
diff --git a/src/__tests__/node.js b/src/__tests__/node.js
index bf54ffa..688a4ef 100644
--- a/src/__tests__/node.js
+++ b/src/__tests__/node.js
@@ -1,6 +1,6 @@
 import test from 'ava';
+import parser from '..';
 import {parse} from './util/helpers';
-import parser from '../index';
 
 test('node#clone', (t) => {
     parse('[href="test"]', (selectors) => {
diff --git a/src/__tests__/parser.js b/src/__tests__/parser.js
index 0a112ca..3f69287 100644
--- a/src/__tests__/parser.js
+++ b/src/__tests__/parser.js
@@ -12,7 +12,7 @@ const nodeTypes = [
     ['pseudo',     '::before', {value: '::before'}],
     ['string',     '"wow"', {value: '"wow"'}],
     ['tag',        'button', {value: 'button'}],
-    ['universal',  '*']
+    ['universal',  '*'],
 ];
 
 nodeTypes.forEach(type => {
diff --git a/src/__tests__/util/helpers.js b/src/__tests__/util/helpers.js
index 7b6bacb..b91f352 100644
--- a/src/__tests__/util/helpers.js
+++ b/src/__tests__/util/helpers.js
@@ -1,15 +1,15 @@
-import parser from '../../index';
-import ava from 'ava';
 import util from 'util';
+import ava from 'ava';
+import parser from '../../index';
 
-export let parse = (input, transform) => {
+export const parse = (input, transform) => {
     return parser(transform).process(input).result;
 };
 
-export let test = (spec, input, callback) => {
+export const test = (spec, input, callback) => {
     let tree;
 
-    let result = parse(input, (selectors) => tree = selectors);
+    let result = parse(input, (selectors) => (tree = selectors));
 
     if (callback) {
         ava(`${spec} (tree)`, t => {
@@ -23,7 +23,7 @@ export let test = (spec, input, callback) => {
     });
 };
 
-export let throws = (spec, input) => {
+export const throws = (spec, input) => {
     ava(`${spec} (throws)`, t => {
         t.throws(() => parser().process(input).result);
     });