less 3.0.0-pre.2 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/.eslintrc.json +88 -0
  2. package/.github/stale.yml +17 -0
  3. package/.project +17 -0
  4. package/.settings/org.eclipse.buildship.core.prefs +2 -0
  5. package/.travis.yml +22 -3
  6. package/.vscode/launch.json +14 -0
  7. package/CHANGELOG.md +27 -0
  8. package/Gruntfile.js +191 -86
  9. package/README.md +5 -7
  10. package/appveyor.yml +9 -10
  11. package/bin/lessc +37 -50
  12. package/dist/less.js +1357 -1269
  13. package/dist/less.min.js +7 -7
  14. package/lib/less/contexts.js +20 -16
  15. package/lib/less/default-options.js +65 -0
  16. package/lib/less/environment/abstract-file-manager.js +13 -14
  17. package/lib/less/environment/abstract-plugin-loader.js +45 -36
  18. package/lib/less/environment/environment.js +5 -0
  19. package/lib/less/functions/boolean.js +15 -0
  20. package/lib/less/functions/color.js +54 -24
  21. package/lib/less/functions/data-uri.js +5 -2
  22. package/lib/less/functions/function-registry.js +1 -1
  23. package/lib/less/functions/index.js +2 -1
  24. package/lib/less/functions/number.js +2 -2
  25. package/lib/less/functions/string.js +2 -2
  26. package/lib/less/functions/svg.js +4 -4
  27. package/lib/less/import-manager.js +41 -29
  28. package/lib/less/index.js +28 -3
  29. package/lib/less/less-error.js +112 -13
  30. package/lib/less/parse.js +18 -15
  31. package/lib/less/parser/parser-input.js +4 -4
  32. package/lib/less/parser/parser.js +208 -114
  33. package/lib/less/plugin-manager.js +8 -33
  34. package/lib/less/render.js +6 -2
  35. package/lib/less/source-map-builder.js +3 -0
  36. package/lib/less/source-map-output.js +13 -8
  37. package/lib/less/transform-tree.js +2 -2
  38. package/lib/less/tree/anonymous.js +4 -1
  39. package/lib/less/tree/atrule.js +7 -6
  40. package/lib/less/tree/call.js +25 -3
  41. package/lib/less/tree/color.js +2 -2
  42. package/lib/less/tree/debug-info.js +1 -1
  43. package/lib/less/tree/declaration.js +6 -5
  44. package/lib/less/tree/dimension.js +4 -1
  45. package/lib/less/tree/extend.js +2 -2
  46. package/lib/less/tree/import.js +17 -9
  47. package/lib/less/tree/index.js +3 -7
  48. package/lib/less/tree/js-eval-node.js +1 -1
  49. package/lib/less/tree/media.js +3 -14
  50. package/lib/less/tree/mixin-definition.js +3 -3
  51. package/lib/less/tree/node.js +9 -6
  52. package/lib/less/tree/operation.js +1 -1
  53. package/lib/less/tree/property.js +70 -0
  54. package/lib/less/tree/quoted.js +11 -10
  55. package/lib/less/tree/ruleset.js +115 -71
  56. package/lib/less/tree/selector.js +38 -23
  57. package/lib/less/tree/unit.js +1 -1
  58. package/lib/less/tree/value.js +6 -1
  59. package/lib/less/tree/{ruleset-call.js → variable-call.js} +5 -5
  60. package/lib/less/tree/variable.js +6 -6
  61. package/lib/less/utils.js +40 -0
  62. package/lib/less/visitors/extend-visitor.js +14 -14
  63. package/lib/less/visitors/import-visitor.js +2 -2
  64. package/lib/less/visitors/join-selector-visitor.js +1 -1
  65. package/lib/less/visitors/to-css-visitor.js +51 -84
  66. package/lib/less/visitors/visitor.js +15 -15
  67. package/lib/less-browser/bootstrap.js +21 -5
  68. package/lib/less-browser/cache.js +2 -2
  69. package/lib/less-browser/error-reporting.js +5 -5
  70. package/lib/less-browser/file-manager.js +27 -35
  71. package/lib/less-browser/index.js +12 -17
  72. package/lib/less-browser/plugin-loader.js +8 -45
  73. package/lib/less-browser/utils.js +1 -1
  74. package/lib/less-node/file-manager.js +99 -69
  75. package/lib/less-node/fs.js +1 -1
  76. package/lib/less-node/image-size.js +2 -2
  77. package/lib/less-node/index.js +7 -57
  78. package/lib/less-node/lessc-helper.js +38 -38
  79. package/lib/less-node/plugin-loader.js +18 -79
  80. package/lib/less-node/url-file-manager.js +1 -1
  81. package/lib/less-rhino/index.js +29 -44
  82. package/package.json +16 -9
  83. package/test/browser/common.js +54 -18
  84. package/test/browser/css/plugin/plugin.css +3 -0
  85. package/test/browser/jasmine-jsreporter.js +4 -4
  86. package/test/browser/less/plugin/plugin.js +7 -0
  87. package/test/browser/less/plugin/plugin.less +4 -0
  88. package/test/browser/less.js +11685 -13
  89. package/test/browser/runner-browser-options.js +6 -1
  90. package/test/browser/runner-filemanagerPlugin-options.js +3 -2
  91. package/test/browser/runner-legacy-options.js +2 -1
  92. package/test/browser/runner-main-options.js +0 -1
  93. package/test/browser/runner-modify-vars-spec.js +2 -2
  94. package/test/browser/test-runner-template.tmpl +38 -14
  95. package/test/copy-bom.js +1 -1
  96. package/test/css/calc.css +5 -0
  97. package/test/css/comments2.css +1 -1
  98. package/test/css/compression/compression.css +1 -1
  99. package/test/css/css-grid.css +11 -0
  100. package/test/css/debug/linenumbers-all.css +0 -1
  101. package/test/css/debug/linenumbers-comments.css +0 -1
  102. package/test/css/debug/linenumbers-mediaquery.css +0 -1
  103. package/test/css/functions.css +16 -3
  104. package/test/css/import-module.css +9 -0
  105. package/test/css/import.css +0 -1
  106. package/test/css/legacy/legacy.css +1 -1
  107. package/test/css/merge.css +5 -4
  108. package/test/css/plugin-module.css +1 -0
  109. package/test/css/plugin.css +20 -0
  110. package/test/css/property-accessors.css +49 -0
  111. package/test/css/{css.css → strict-math/css.css} +0 -0
  112. package/test/css/{mixins-args.css → strict-math/mixins-args.css} +0 -0
  113. package/test/css/{parens.css → strict-math/parens.css} +0 -0
  114. package/test/import-module/one/1.less +3 -0
  115. package/test/import-module/one/two/2.less +3 -0
  116. package/test/import-module/one/two/three/3.less +3 -0
  117. package/test/import-module/package.json +11 -0
  118. package/test/index.js +55 -49
  119. package/test/less/calc.less +7 -0
  120. package/test/less/css-grid.less +15 -0
  121. package/test/less/detached-rulesets.less +2 -2
  122. package/test/less/errors/color-invalid-hex-code.less +1 -0
  123. package/test/less/errors/color-invalid-hex-code.txt +1 -1
  124. package/test/less/errors/color-invalid-hex-code2.less +1 -0
  125. package/test/less/errors/color-invalid-hex-code2.txt +1 -1
  126. package/test/less/errors/import-missing.txt +1 -1
  127. package/test/less/errors/plugin/plugin-error-2.js +5 -0
  128. package/test/less/errors/plugin/plugin-error-3.js +5 -0
  129. package/test/less/errors/plugin/plugin-error.js +1 -0
  130. package/test/less/errors/plugin-1.less +1 -0
  131. package/test/less/errors/plugin-1.txt +2 -0
  132. package/test/less/errors/plugin-2.less +1 -0
  133. package/test/less/errors/plugin-2.txt +5 -0
  134. package/test/less/errors/plugin-3.less +1 -0
  135. package/test/less/errors/plugin-3.txt +5 -0
  136. package/test/less/functions.less +18 -0
  137. package/test/less/import-module.less +3 -0
  138. package/test/less/import.less +0 -2
  139. package/test/less/legacy/legacy.less +1 -1
  140. package/test/less/media.less +2 -2
  141. package/test/less/merge.less +6 -3
  142. package/test/less/plugin/plugin-collection.js +9 -0
  143. package/test/less/plugin/plugin-local.js +2 -2
  144. package/test/less/plugin/plugin-scope1.js +3 -0
  145. package/test/less/plugin/plugin-scope2.js +3 -0
  146. package/test/less/plugin/plugin-simple.js +7 -0
  147. package/test/less/plugin/plugin-tree-nodes.js +33 -31
  148. package/test/less/plugin-module.less +7 -0
  149. package/test/less/plugin.less +33 -3
  150. package/test/less/property-accessors.less +67 -0
  151. package/test/less/root-registry/file.less +1 -0
  152. package/test/less/root-registry/root.less +3 -0
  153. package/test/less/{css.less → strict-math/css.less} +0 -0
  154. package/test/less/{mixins-args.less → strict-math/mixins-args.less} +0 -0
  155. package/test/less/{parens.less → strict-math/parens.less} +0 -0
  156. package/test/less-bom/calc.less +7 -0
  157. package/test/less-bom/css-grid.less +15 -0
  158. package/test/less-bom/detached-rulesets.less +2 -2
  159. package/test/less-bom/errors/color-invalid-hex-code.less +1 -0
  160. package/test/less-bom/errors/color-invalid-hex-code.txt +1 -1
  161. package/test/less-bom/errors/color-invalid-hex-code2.less +1 -0
  162. package/test/less-bom/errors/color-invalid-hex-code2.txt +1 -1
  163. package/test/less-bom/errors/import-missing.txt +1 -1
  164. package/test/less-bom/errors/plugin/plugin-error-2.js +5 -0
  165. package/test/less-bom/errors/plugin/plugin-error-3.js +5 -0
  166. package/test/less-bom/errors/plugin/plugin-error.js +1 -0
  167. package/test/less-bom/errors/plugin-1.less +1 -0
  168. package/test/less-bom/errors/plugin-1.txt +2 -0
  169. package/test/less-bom/errors/plugin-2.less +1 -0
  170. package/test/less-bom/errors/plugin-2.txt +5 -0
  171. package/test/less-bom/errors/plugin-3.less +1 -0
  172. package/test/less-bom/errors/plugin-3.txt +5 -0
  173. package/test/less-bom/functions.less +18 -0
  174. package/test/less-bom/import-module.less +3 -0
  175. package/test/less-bom/import.less +0 -2
  176. package/test/less-bom/legacy/legacy.less +1 -1
  177. package/test/less-bom/media.less +2 -2
  178. package/test/less-bom/merge.less +6 -3
  179. package/test/less-bom/plugin/plugin-collection.js +9 -0
  180. package/test/less-bom/plugin/plugin-local.js +2 -2
  181. package/test/less-bom/plugin/plugin-scope1.js +3 -0
  182. package/test/less-bom/plugin/plugin-scope2.js +3 -0
  183. package/test/less-bom/plugin/plugin-simple.js +7 -0
  184. package/test/less-bom/plugin/plugin-tree-nodes.js +33 -31
  185. package/test/less-bom/plugin-module.less +7 -0
  186. package/test/less-bom/plugin.less +33 -3
  187. package/test/less-bom/property-accessors.less +67 -0
  188. package/test/less-bom/root-registry/file.less +1 -0
  189. package/test/less-bom/root-registry/root.less +3 -0
  190. package/test/less-bom/{css.less → strict-math/css.less} +0 -0
  191. package/test/less-bom/{mixins-args.less → strict-math/mixins-args.less} +0 -0
  192. package/test/less-bom/{parens.less → strict-math/parens.less} +0 -0
  193. package/test/less-test.js +75 -17
  194. package/test/modify-vars.js +1 -1
  195. package/test/plugins/visitor/index.js +1 -1
  196. package/.jscsrc +0 -73
  197. package/.jshintrc +0 -11
  198. package/lib/less/tree/alpha.js +0 -28
  199. package/lib/less/tree/directive.js +0 -12
  200. package/lib/less/tree/rule.js +0 -12
  201. package/test/less/errors/functions-2-alpha.less +0 -2
  202. package/test/less/errors/functions-2-alpha.txt +0 -3
  203. package/test/less-bom/errors/functions-2-alpha.less +0 -2
  204. package/test/less-bom/errors/functions-2-alpha.txt +0 -3
  205. package/test/sourcemaps/maps/import-map.map +0 -1
@@ -2,7 +2,8 @@ var LessError = require('../less-error'),
2
2
  tree = require("../tree"),
3
3
  visitors = require("../visitors"),
4
4
  getParserInput = require("./parser-input"),
5
- utils = require("../utils");
5
+ utils = require("../utils"),
6
+ functionRegistry = require('../functions/function-registry');
6
7
 
7
8
  //
8
9
  // less.js - parser
@@ -35,8 +36,8 @@ var LessError = require('../less-error'),
35
36
  // Token matching is done with the `$` function, which either takes
36
37
  // a terminal string or regexp, or a non-terminal function to call.
37
38
  // It also takes care of moving all the indices forwards.
38
- //`
39
39
  //
40
+
40
41
  var Parser = function Parser(context, imports, fileInfo) {
41
42
  var parsers,
42
43
  parserInput = getParserInput();
@@ -80,11 +81,61 @@ var Parser = function Parser(context, imports, fileInfo) {
80
81
  };
81
82
  }
82
83
 
84
+ /**
85
+ * Used after initial parsing to create nodes on the fly
86
+ *
87
+ * @param {String} str - string to parse
88
+ * @param {Array} parseList - array of parsers to run input through e.g. ["value", "important"]
89
+ * @param {Number} currentIndex - start number to begin indexing
90
+ * @param {Object} fileInfo - fileInfo to attach to created nodes
91
+ */
92
+ function parseNode(str, parseList, currentIndex, fileInfo, callback) {
93
+ var result, returnNodes = [];
94
+ var parser = parserInput;
95
+
96
+ try {
97
+ parser.start(str, false, function fail(msg, index) {
98
+ callback({
99
+ message: msg,
100
+ index: index + currentIndex
101
+ });
102
+ });
103
+ for (var x = 0, p, i; (p = parseList[x]); x++) {
104
+ i = parser.i;
105
+ result = parsers[p]();
106
+ if (result) {
107
+ result._index = i + currentIndex;
108
+ result._fileInfo = fileInfo;
109
+ returnNodes.push(result);
110
+ }
111
+ else {
112
+ returnNodes.push(null);
113
+ }
114
+ }
115
+
116
+ var endInfo = parser.end();
117
+ if (endInfo.isFinished) {
118
+ callback(null, returnNodes);
119
+ }
120
+ else {
121
+ callback(true, null);
122
+ }
123
+ } catch (e) {
124
+ throw new LessError({
125
+ index: e.index + currentIndex,
126
+ message: e.message
127
+ }, imports, fileInfo.filename);
128
+ }
129
+ }
130
+
83
131
  //
84
132
  // The Parser
85
133
  //
86
134
  return {
87
-
135
+ parserInput: parserInput,
136
+ imports: imports,
137
+ fileInfo: fileInfo,
138
+ parseNode: parseNode,
88
139
  //
89
140
  // Parse an input string into an abstract syntax tree,
90
141
  // @param str A string containing 'less' markup
@@ -130,9 +181,13 @@ var Parser = function Parser(context, imports, fileInfo) {
130
181
  }, imports);
131
182
  });
132
183
 
133
- root = new(tree.Ruleset)(null, this.parsers.primary());
184
+ tree.Node.prototype.parse = this;
185
+ root = new tree.Ruleset(null, this.parsers.primary());
186
+ tree.Node.prototype.rootNode = root;
134
187
  root.root = true;
135
188
  root.firstRoot = true;
189
+ root.functionRegistry = functionRegistry.inherit();
190
+
136
191
  } catch (e) {
137
192
  return callback(new LessError(e, imports, fileInfo.filename));
138
193
  }
@@ -261,7 +316,7 @@ var Parser = function Parser(context, imports, fileInfo) {
261
316
  }
262
317
 
263
318
  node = mixin.definition() || this.declaration() || this.ruleset() ||
264
- mixin.call() || this.rulesetCall() || this.entities.call() || this.atrule();
319
+ mixin.call() || this.variableCall() || this.entities.call() || this.atrule();
265
320
  if (node) {
266
321
  root.push(node);
267
322
  } else {
@@ -319,7 +374,7 @@ var Parser = function Parser(context, imports, fileInfo) {
319
374
  // black border-collapse
320
375
  //
321
376
  keyword: function () {
322
- var k = parserInput.$char("%") || parserInput.$re(/^[_A-Za-z-][_A-Za-z0-9-]*/);
377
+ var k = parserInput.$char("%") || parserInput.$re(/^\[?[_A-Za-z-][_A-Za-z0-9-]*\]?/);
323
378
  if (k) {
324
379
  return tree.Color.fromKeyword(k) || new(tree.Keyword)(k);
325
380
  }
@@ -330,13 +385,10 @@ var Parser = function Parser(context, imports, fileInfo) {
330
385
  //
331
386
  // rgb(255, 0, 255)
332
387
  //
333
- // We also try to catch IE's `alpha()`, but let the `alpha` parser
334
- // deal with the details.
335
- //
336
388
  // The arguments are parsed with the `entities.arguments` parser.
337
389
  //
338
390
  call: function () {
339
- var name, nameLC, args, alpha, index = parserInput.i;
391
+ var name, args, func, index = parserInput.i;
340
392
 
341
393
  // http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
342
394
  if (parserInput.peek(/^url\(/i)) {
@@ -346,70 +398,98 @@ var Parser = function Parser(context, imports, fileInfo) {
346
398
  parserInput.save();
347
399
 
348
400
  name = parserInput.$re(/^([\w-]+|%|progid:[\w\.]+)\(/);
349
- if (!name) { parserInput.forget(); return; }
401
+ if (!name) {
402
+ parserInput.forget();
403
+ return;
404
+ }
350
405
 
351
406
  name = name[1];
352
- nameLC = name.toLowerCase();
353
-
354
- if (nameLC === 'alpha') {
355
- alpha = parsers.alpha();
356
- if (alpha) {
407
+ func = this.customFuncCall(name);
408
+ if (func) {
409
+ args = func.parse();
410
+ if (args && func.stop) {
357
411
  parserInput.forget();
358
- return alpha;
412
+ return args;
359
413
  }
360
414
  }
361
415
 
362
- args = this.arguments();
416
+ args = this.arguments(args);
363
417
 
364
- if (! parserInput.$char(')')) {
418
+ if (!parserInput.$char(')')) {
365
419
  parserInput.restore("Could not parse call arguments or missing ')'");
366
420
  return;
367
421
  }
368
422
 
369
423
  parserInput.forget();
424
+
370
425
  return new(tree.Call)(name, args, index, fileInfo);
371
426
  },
372
- arguments: function () {
373
- var argsSemiColon = [], argsComma = [],
374
- expressions = [],
375
- isSemiColonSeparated, value, arg;
376
-
377
- parserInput.save();
427
+
428
+ //
429
+ // Parsing rules for functions with non-standard args, e.g.:
430
+ //
431
+ // boolean(not(2 > 1))
432
+ //
433
+ // This is a quick prototype, to be modified/improved when
434
+ // more custom-parsed funcs come (e.g. `selector(...)`)
435
+ //
378
436
 
379
- while (true) {
437
+ customFuncCall: function (name) {
438
+ /* Ideally the table is to be moved out of here for faster perf.,
439
+ but it's quite tricky since it relies on all these `parsers`
440
+ and `expect` available only here */
441
+ return {
442
+ alpha: f(parsers.ieAlpha, true),
443
+ boolean: f(condition),
444
+ 'if': f(condition)
445
+ }[name.toLowerCase()];
446
+
447
+ function f(parse, stop) {
448
+ return {
449
+ parse: parse, // parsing function
450
+ stop: stop // when true - stop after parse() and return its result,
451
+ // otherwise continue for plain args
452
+ };
453
+ }
454
+
455
+ function condition() {
456
+ return [expect(parsers.condition, 'expected condition')];
457
+ }
458
+ },
380
459
 
381
- arg = parsers.detachedRuleset() || this.assignment() || parsers.expression();
460
+ arguments: function (prevArgs) {
461
+ var argsComma = prevArgs || [],
462
+ argsSemiColon = [],
463
+ isSemiColonSeparated, value;
382
464
 
383
- if (!arg) {
384
- break;
385
- }
465
+ parserInput.save();
386
466
 
387
- value = arg;
467
+ while (true) {
468
+ if (prevArgs) {
469
+ prevArgs = false;
470
+ } else {
471
+ value = parsers.detachedRuleset() || this.assignment() || parsers.expression();
472
+ if (!value) {
473
+ break;
474
+ }
388
475
 
389
- if (arg.value && arg.value.length == 1) {
390
- value = arg.value[0];
391
- }
476
+ if (value.value && value.value.length == 1) {
477
+ value = value.value[0];
478
+ }
392
479
 
393
- if (value) {
394
- expressions.push(value);
480
+ argsComma.push(value);
395
481
  }
396
482
 
397
- argsComma.push(value);
398
-
399
483
  if (parserInput.$char(',')) {
400
484
  continue;
401
485
  }
402
486
 
403
487
  if (parserInput.$char(';') || isSemiColonSeparated) {
404
-
405
488
  isSemiColonSeparated = true;
406
-
407
- if (expressions.length > 1) {
408
- value = new(tree.Value)(expressions);
409
- }
489
+ value = (argsComma.length < 1) ? argsComma[0]
490
+ : new tree.Value(argsComma);
410
491
  argsSemiColon.push(value);
411
-
412
- expressions = [];
492
+ argsComma = [];
413
493
  }
414
494
  }
415
495
 
@@ -467,15 +547,17 @@ var Parser = function Parser(context, imports, fileInfo) {
467
547
  return;
468
548
  }
469
549
 
470
- value = this.quoted() || this.variable() ||
550
+ value = this.quoted() || this.variable() || this.property() ||
471
551
  parserInput.$re(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || "";
472
552
 
473
553
  parserInput.autoCommentAbsorb = true;
474
554
 
475
555
  expectChar(')');
476
556
 
477
- return new(tree.URL)((value.value != null || value instanceof tree.Variable) ?
478
- value : new(tree.Anonymous)(value), index, fileInfo);
557
+ return new(tree.URL)((value.value != null ||
558
+ value instanceof tree.Variable ||
559
+ value instanceof tree.Property) ?
560
+ value : new(tree.Anonymous)(value, index), index, fileInfo);
479
561
  },
480
562
 
481
563
  //
@@ -502,7 +584,27 @@ var Parser = function Parser(context, imports, fileInfo) {
502
584
  return new(tree.Variable)("@" + curly[1], index, fileInfo);
503
585
  }
504
586
  },
587
+ //
588
+ // A Property accessor, such as `$color`, in
589
+ //
590
+ // background-color: $color
591
+ //
592
+ property: function () {
593
+ var name, index = parserInput.i;
594
+
595
+ if (parserInput.currentChar() === '$' && (name = parserInput.$re(/^\$[\w-]+/))) {
596
+ return new(tree.Property)(name, index, fileInfo);
597
+ }
598
+ },
599
+
600
+ // A property entity useing the protective {} e.g. ${prop}
601
+ propertyCurly: function () {
602
+ var curly, index = parserInput.i;
505
603
 
604
+ if (parserInput.currentChar() === '$' && (curly = parserInput.$re(/^\$\{([\w-]+)\}/))) {
605
+ return new(tree.Property)("$" + curly[1], index, fileInfo);
606
+ }
607
+ },
506
608
  //
507
609
  // A Hexadecimal color
508
610
  //
@@ -612,15 +714,17 @@ var Parser = function Parser(context, imports, fileInfo) {
612
714
  },
613
715
 
614
716
  //
615
- // The variable part of a variable definition. Used in the `rule` parser
717
+ // Call a variable value
616
718
  //
617
- // @fink();
719
+ // @fink()
618
720
  //
619
- rulesetCall: function () {
721
+ variableCall: function () {
620
722
  var name;
621
723
 
622
- if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\(\s*\)\s*;/))) {
623
- return new tree.RulesetCall(name[1]);
724
+ if (parserInput.currentChar() === '@'
725
+ && (name = parserInput.$re(/^(@[\w-]+)\(\s*\)/))
726
+ && parsers.end()) {
727
+ return new tree.VariableCall(name[1]);
624
728
  }
625
729
  },
626
730
 
@@ -637,7 +741,7 @@ var Parser = function Parser(context, imports, fileInfo) {
637
741
  do {
638
742
  option = null;
639
743
  elements = null;
640
- while (! (option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
744
+ while (!(option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
641
745
  e = this.element();
642
746
  if (!e) {
643
747
  break;
@@ -756,7 +860,7 @@ var Parser = function Parser(context, imports, fileInfo) {
756
860
  .push({ variadic: true });
757
861
  break;
758
862
  }
759
- arg = entities.variable() || entities.literal() || entities.keyword();
863
+ arg = entities.variable() || entities.property() || entities.literal() || entities.keyword();
760
864
  }
761
865
 
762
866
  if (!arg) {
@@ -779,7 +883,7 @@ var Parser = function Parser(context, imports, fileInfo) {
779
883
  val = arg;
780
884
  }
781
885
 
782
- if (val && val instanceof tree.Variable) {
886
+ if (val && (val instanceof tree.Variable || val instanceof tree.Property)) {
783
887
  if (parserInput.$char(':')) {
784
888
  if (expressions.length > 0) {
785
889
  if (isSemiColonSeparated) {
@@ -925,7 +1029,7 @@ var Parser = function Parser(context, imports, fileInfo) {
925
1029
  var entities = this.entities;
926
1030
 
927
1031
  return this.comment() || entities.literal() || entities.variable() || entities.url() ||
928
- entities.call() || entities.keyword() || entities.javascript();
1032
+ entities.property() || entities.call() || entities.keyword() || entities.javascript();
929
1033
  },
930
1034
 
931
1035
  //
@@ -942,17 +1046,18 @@ var Parser = function Parser(context, imports, fileInfo) {
942
1046
  //
943
1047
  // alpha(opacity=88)
944
1048
  //
945
- alpha: function () {
1049
+ ieAlpha: function () {
946
1050
  var value;
947
1051
 
948
1052
  // http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
949
- if (! parserInput.$re(/^opacity=/i)) { return; }
1053
+ if (!parserInput.$re(/^opacity=/i)) { return; }
950
1054
  value = parserInput.$re(/^\d+/);
951
1055
  if (!value) {
952
- value = expect(this.entities.variable, "Could not parse alpha");
1056
+ value = expect(parsers.entities.variable, "Could not parse alpha");
1057
+ value = '@{' + value.name.slice(1) + '}';
953
1058
  }
954
1059
  expectChar(')');
955
- return new(tree.Alpha)(value);
1060
+ return new tree.Quoted('', 'alpha(opacity=' + value + ')');
956
1061
  },
957
1062
 
958
1063
  //
@@ -978,10 +1083,10 @@ var Parser = function Parser(context, imports, fileInfo) {
978
1083
  parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[\.#:](?=@)/) ||
979
1084
  this.entities.variableCurly();
980
1085
 
981
- if (! e) {
1086
+ if (!e) {
982
1087
  parserInput.save();
983
1088
  if (parserInput.$char('(')) {
984
- if ((v = this.selector()) && parserInput.$char(')')) {
1089
+ if ((v = this.selector(false)) && parserInput.$char(')')) {
985
1090
  e = new(tree.Paren)(v);
986
1091
  parserInput.forget();
987
1092
  } else {
@@ -1032,14 +1137,8 @@ var Parser = function Parser(context, imports, fileInfo) {
1032
1137
  }
1033
1138
  },
1034
1139
  //
1035
- // A CSS selector (see selector below)
1036
- // with less extensions e.g. the ability to extend and guard
1037
- //
1038
- lessSelector: function () {
1039
- return this.selector(true);
1040
- },
1041
- //
1042
1140
  // A CSS Selector
1141
+ // with less extensions e.g. the ability to extend and guard
1043
1142
  //
1044
1143
  // .class > div + h1
1045
1144
  // li a:hover
@@ -1048,7 +1147,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1048
1147
  //
1049
1148
  selector: function (isLess) {
1050
1149
  var index = parserInput.i, elements, extendList, c, e, allExtends, when, condition;
1051
-
1150
+ isLess = isLess !== false;
1052
1151
  while ((isLess && (extendList = this.extend())) || (isLess && (when = parserInput.$str("when"))) || (e = this.element())) {
1053
1152
  if (when) {
1054
1153
  condition = expect(this.conditions, 'expected condition');
@@ -1079,7 +1178,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1079
1178
  if (allExtends) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
1080
1179
  },
1081
1180
  attribute: function () {
1082
- if (! parserInput.$char('[')) { return; }
1181
+ if (!parserInput.$char('[')) { return; }
1083
1182
 
1084
1183
  var entities = this.entities,
1085
1184
  key, val, op;
@@ -1138,7 +1237,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1138
1237
  }
1139
1238
 
1140
1239
  while (true) {
1141
- s = this.lessSelector();
1240
+ s = this.selector();
1142
1241
  if (!s) {
1143
1242
  break;
1144
1243
  }
@@ -1151,7 +1250,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1151
1250
  if (s.condition && selectors.length > 1) {
1152
1251
  error("Guards are only currently allowed on a single selector.");
1153
1252
  }
1154
- if (! parserInput.$char(',')) { break; }
1253
+ if (!parserInput.$char(',')) { break; }
1155
1254
  if (s.condition) {
1156
1255
  error("Guards are only currently allowed on a single selector.");
1157
1256
  }
@@ -1169,7 +1268,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1169
1268
  parserInput.restore();
1170
1269
  }
1171
1270
  },
1172
- declaration: function (tryAnonymous) {
1271
+ declaration: function () {
1173
1272
  var name, value, startOfRule = parserInput.i, c = parserInput.currentChar(), important, merge, isVariable;
1174
1273
 
1175
1274
  if (c === '.' || c === '#' || c === '&' || c === ':') { return; }
@@ -1191,22 +1290,16 @@ var Parser = function Parser(context, imports, fileInfo) {
1191
1290
  // where each item is a tree.Keyword or tree.Variable
1192
1291
  merge = !isVariable && name.length > 1 && name.pop().value;
1193
1292
 
1194
- // prefer to try to parse first if its a variable or we are compressing
1195
- // but always fallback on the other one
1196
- var tryValueFirst = !tryAnonymous && (context.compress || isVariable);
1197
-
1198
- if (tryValueFirst) {
1199
- value = this.value();
1293
+ // Try to store values as anonymous
1294
+ // If we need the value later we'll re-parse it in ruleset.parseValue
1295
+ value = this.anonymousValue();
1296
+ if (value) {
1297
+ parserInput.forget();
1298
+ // anonymous values absorb the end ';' which is required for them to work
1299
+ return new (tree.Declaration)(name, value, false, merge, startOfRule, fileInfo);
1200
1300
  }
1301
+
1201
1302
  if (!value) {
1202
- value = this.anonymousValue();
1203
- if (value) {
1204
- parserInput.forget();
1205
- // anonymous values absorb the end ';' which is required for them to work
1206
- return new (tree.Declaration)(name, value, false, merge, startOfRule, fileInfo);
1207
- }
1208
- }
1209
- if (!tryValueFirst && !value) {
1210
1303
  value = this.value();
1211
1304
  }
1212
1305
 
@@ -1216,20 +1309,19 @@ var Parser = function Parser(context, imports, fileInfo) {
1216
1309
  if (value && this.end()) {
1217
1310
  parserInput.forget();
1218
1311
  return new (tree.Declaration)(name, value, important, merge, startOfRule, fileInfo);
1219
- } else {
1312
+ }
1313
+ else {
1220
1314
  parserInput.restore();
1221
- if (value && !tryAnonymous) {
1222
- return this.declaration(true);
1223
- }
1224
1315
  }
1225
1316
  } else {
1226
- parserInput.forget();
1317
+ parserInput.restore();
1227
1318
  }
1228
1319
  },
1229
1320
  anonymousValue: function () {
1230
- var match = parserInput.$re(/^([^@+\/'"*`(;{}-]*);/);
1321
+ var index = parserInput.i;
1322
+ var match = parserInput.$re(/^([^@\$+\/'"*`(;{}-]*);/);
1231
1323
  if (match) {
1232
- return new(tree.Anonymous)(match[1]);
1324
+ return new(tree.Anonymous)(match[1], index);
1233
1325
  }
1234
1326
  },
1235
1327
 
@@ -1272,13 +1364,13 @@ var Parser = function Parser(context, imports, fileInfo) {
1272
1364
  var o, options = {}, optionName, value;
1273
1365
 
1274
1366
  // list of options, surrounded by parens
1275
- if (! parserInput.$char('(')) { return null; }
1367
+ if (!parserInput.$char('(')) { return null; }
1276
1368
  do {
1277
1369
  o = this.importOption();
1278
1370
  if (o) {
1279
1371
  optionName = o;
1280
1372
  value = true;
1281
- switch(optionName) {
1373
+ switch (optionName) {
1282
1374
  case "css":
1283
1375
  optionName = "less";
1284
1376
  value = false;
@@ -1289,7 +1381,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1289
1381
  break;
1290
1382
  }
1291
1383
  options[optionName] = value;
1292
- if (! parserInput.$char(',')) { break; }
1384
+ if (!parserInput.$char(',')) { break; }
1293
1385
  }
1294
1386
  } while (o);
1295
1387
  expectChar(')');
@@ -1339,12 +1431,12 @@ var Parser = function Parser(context, imports, fileInfo) {
1339
1431
  e = this.mediaFeature();
1340
1432
  if (e) {
1341
1433
  features.push(e);
1342
- if (! parserInput.$char(',')) { break; }
1434
+ if (!parserInput.$char(',')) { break; }
1343
1435
  } else {
1344
1436
  e = entities.variable();
1345
1437
  if (e) {
1346
1438
  features.push(e);
1347
- if (! parserInput.$char(',')) { break; }
1439
+ if (!parserInput.$char(',')) { break; }
1348
1440
  }
1349
1441
  }
1350
1442
  } while (e);
@@ -1425,7 +1517,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1425
1517
  pluginArgs: function() {
1426
1518
  // list of options, surrounded by parens
1427
1519
  parserInput.save();
1428
- if (! parserInput.$char('(')) {
1520
+ if (!parserInput.$char('(')) {
1429
1521
  parserInput.restore();
1430
1522
  return null;
1431
1523
  }
@@ -1467,7 +1559,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1467
1559
  nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1);
1468
1560
  }
1469
1561
 
1470
- switch(nonVendorSpecificName) {
1562
+ switch (nonVendorSpecificName) {
1471
1563
  case "@charset":
1472
1564
  hasIdentifier = true;
1473
1565
  hasBlock = false;
@@ -1534,18 +1626,18 @@ var Parser = function Parser(context, imports, fileInfo) {
1534
1626
  // and before the `;`.
1535
1627
  //
1536
1628
  value: function () {
1537
- var e, expressions = [];
1629
+ var e, expressions = [], index = parserInput.i;
1538
1630
 
1539
1631
  do {
1540
1632
  e = this.expression();
1541
1633
  if (e) {
1542
1634
  expressions.push(e);
1543
- if (! parserInput.$char(',')) { break; }
1635
+ if (!parserInput.$char(',')) { break; }
1544
1636
  }
1545
1637
  } while (e);
1546
1638
 
1547
1639
  if (expressions.length > 0) {
1548
- return new(tree.Value)(expressions);
1640
+ return new(tree.Value)(expressions, index);
1549
1641
  }
1550
1642
  },
1551
1643
  important: function () {
@@ -1784,13 +1876,14 @@ var Parser = function Parser(context, imports, fileInfo) {
1784
1876
  operand: function () {
1785
1877
  var entities = this.entities, negate;
1786
1878
 
1787
- if (parserInput.peek(/^-[@\(]/)) {
1879
+ if (parserInput.peek(/^-[@\$\(]/)) {
1788
1880
  negate = parserInput.$char('-');
1789
1881
  }
1790
1882
 
1791
1883
  var o = this.sub() || entities.dimension() ||
1792
1884
  entities.color() || entities.variable() ||
1793
- entities.call() || entities.colorKeyword();
1885
+ entities.property() || entities.call() ||
1886
+ entities.colorKeyword();
1794
1887
 
1795
1888
  if (negate) {
1796
1889
  o.parensInOp = true;
@@ -1808,7 +1901,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1808
1901
  // @var * 2
1809
1902
  //
1810
1903
  expression: function () {
1811
- var entities = [], e, delim;
1904
+ var entities = [], e, delim, index = parserInput.i;
1812
1905
 
1813
1906
  do {
1814
1907
  e = this.comment();
@@ -1823,7 +1916,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1823
1916
  if (!parserInput.peek(/^\/[\/*]/)) {
1824
1917
  delim = parserInput.$char('/');
1825
1918
  if (delim) {
1826
- entities.push(new(tree.Anonymous)(delim));
1919
+ entities.push(new(tree.Anonymous)(delim, index));
1827
1920
  }
1828
1921
  }
1829
1922
  }
@@ -1861,7 +1954,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1861
1954
 
1862
1955
  match(/^(\*?)/);
1863
1956
  while (true) {
1864
- if (!match(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/)) {
1957
+ if (!match(/^((?:[\w-]+)|(?:[@\$]\{[\w-]+\}))/)) {
1865
1958
  break;
1866
1959
  }
1867
1960
  }
@@ -1877,10 +1970,11 @@ var Parser = function Parser(context, imports, fileInfo) {
1877
1970
  }
1878
1971
  for (k = 0; k < name.length; k++) {
1879
1972
  s = name[k];
1880
- name[k] = (s.charAt(0) !== '@') ?
1973
+ name[k] = (s.charAt(0) !== '@' && s.charAt(0) !== '$') ?
1881
1974
  new(tree.Keyword)(s) :
1882
- new(tree.Variable)('@' + s.slice(2, -1),
1883
- index[k], fileInfo);
1975
+ (s.charAt(0) === '@' ?
1976
+ new(tree.Variable)('@' + s.slice(2, -1), index[k], fileInfo) :
1977
+ new(tree.Property)('$' + s.slice(2, -1), index[k], fileInfo));
1884
1978
  }
1885
1979
  return name;
1886
1980
  }