Repository: shellscape/postcss-less (js)

Issue:
Spaces between variable name and `:` are not handled
<!--
  Please note: This template is not optional. Please fill in all fields and
  questions otherwise the issue may be closed. Please provide actual technical
  information about errors, if an error has occurred.
-->

* Node Version: 6.11.3
* NPM Version: 3.10.10
* postcss-less Version: 1.1.1

I pin pointed tokenize-at-rule.js#L36.
When declaration contains a space between variable name and :,
It makes state.cssPart one character to shorter to enter word definition bloc.
Test named `parses variables with whitespaces between name and ":"`, merged in #93, reproduces bug.

### LESS

```less
.navbar {
  @borderTopPx : 4px;
  .media {
    i.user {
      margin-top: @borderTopPx + 10px;
    }
  }
}
```

### JavaScript

Prettier/prettier#2925

### Expected Behavior

`@borderTopPx `to be handled like a _variable_.

### Actual Behavior

`@borderTopPx ` not handled like a _variable_.

### How can we reproduce the behavior?

I noticed this bug using [prettier](https://github.com/prettier/prettier) on Less pasted above.
It is described at Prettier/prettier#2925, where @lydell can provide more accurate details about parsing expectations.

Test named `parses variables with whitespaces between name and ":"` reproduces issue.

Thank for the good work

Pull request:
Handle spaces between variable name and colon
Fixes #92

<!-- PRs must be accompanied by related tests -->

Please check one:
- [x] New tests created for this change
- [x] Tests updated for this change

This PR:
- [ ] Adds new API
- [ ] Extends existing API, backwards-compatible
- [ ] Introduces a breaking change
- [x] Fixes a bug

---

<!-- add additional comments here -->


Patch:
diff --git a/lib/tokenizer/tokenize-at-rule.js b/lib/tokenizer/tokenize-at-rule.js
index c9af5a2..371b209 100644
--- a/lib/tokenizer/tokenize-at-rule.js
+++ b/lib/tokenizer/tokenize-at-rule.js
@@ -35,10 +35,15 @@ export default function tokenizeAtRule (state) {
   else {
     atEndPattern.lastIndex = state.pos + 1;
     atEndPattern.test(state.css);
+    const spaceColonPos = state.css.indexOf(' :', state.pos);
 
     if (atEndPattern.lastIndex === 0) {
       state.nextPos = state.css.length - 1;
     }
+    // for cases where there is extra whitespace between a variable name and ":" (#92)
+    else if (spaceColonPos > state.pos && state.css.slice(state.pos, state.pos + 5) !== "@page") {
+      state.nextPos = spaceColonPos + 1;
+    }
     else {
       state.nextPos = atEndPattern.lastIndex - 2;
     }


Test patch:
diff --git a/test/parser/variables.spec.js b/test/parser/variables.spec.js
index 28ba19c..0604117 100644
--- a/test/parser/variables.spec.js
+++ b/test/parser/variables.spec.js
@@ -20,6 +20,31 @@ describe('Parser', () => {
       expect(root.first.value).to.eql('42');
     });
 
+    it('parses variables with no whitespace between ":" and value', () => {
+      const root = parse('@var :42;');
+
+      expect(root.first.prop).to.eql('@var');
+      expect(root.first.value).to.eql('42');
+    });
+
+    it('parses mutliple variables with whitespaces between name and ":"', () => {
+      const root = parse('@foo  : 42; @bar : 35;');
+
+      expect(root.first.prop).to.eql('@foo');
+      expect(root.first.value).to.eql('42');
+      expect(root.nodes[1].prop).to.eql('@bar');
+      expect(root.nodes[1].value).to.eql('35');
+    });
+
+    it('parses multiple variables with no whitespace between ":" and value', () => {
+      const root = parse('@foo  :42; @bar :35');
+
+      expect(root.first.prop).to.eql('@foo');
+      expect(root.first.value).to.eql('42');
+      expect(root.nodes[1].prop).to.eql('@bar');
+      expect(root.nodes[1].value).to.eql('35');
+    });
+
     it('parses string variables', () => {
       const root = parse('@var: "test";');
 
diff --git a/test/tokenize.spec.js b/test/tokenize.spec.js
index 5d53106..0773203 100644
--- a/test/tokenize.spec.js
+++ b/test/tokenize.spec.js
@@ -169,5 +169,24 @@ describe('#tokenize()', () => {
         [')', ')', 1, 7]
       ]);
     });
+
+    it('tokenizes @page with extra whitespace', () => {
+      testTokens('@page   :left { margin: 0; }', [
+        [ 'at-word', '@page', 1, 1, 1, 5 ],
+        [ 'space', '   ' ],
+        [ ':', ':', 1, 9 ],
+        [ 'word', 'left', 1, 10, 1, 13 ],
+        [ 'space', ' ' ],
+        [ '{', '{', 1, 15 ],
+        [ 'space', ' ' ],
+        [ 'word', 'margin', 1, 17, 1, 22 ],
+        [ ':', ':', 1, 23 ],
+        [ 'space', ' ' ],
+        [ 'word', '0', 1, 25, 1, 25 ],
+        [ ';', ';', 1, 26 ],
+        [ 'space', ' ' ],
+        [ '}', '}', 1, 28 ]
+      ]);
+    });
   });
 });