less 2.5.1 → 2.6.0

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +5 -1
  3. data/README.md +12 -0
  4. data/lib/less/js/.travis.yml +8 -0
  5. data/lib/less/js/CHANGELOG.md +22 -0
  6. data/lib/less/js/CONTRIBUTING.md +1 -1
  7. data/lib/less/js/Gruntfile.js +8 -0
  8. data/lib/less/js/README.md +5 -304
  9. data/lib/less/js/bin/lessc +16 -13
  10. data/lib/less/js/bower.json +2 -2
  11. data/lib/less/js/build/README.md +4 -303
  12. data/lib/less/js/build/build.yml +2 -0
  13. data/lib/less/js/dist/less-1.6.3.js +7627 -0
  14. data/lib/less/js/dist/less-1.6.3.min.js +16 -0
  15. data/lib/less/js/dist/less-1.7.0.js +7921 -0
  16. data/lib/less/js/dist/less-1.7.0.min.js +16 -0
  17. data/lib/less/js/dist/less-rhino-1.6.3.js +9020 -0
  18. data/lib/less/js/dist/less-rhino-1.7.0.js +9301 -0
  19. data/lib/less/js/dist/lessc-rhino-1.6.3.js +449 -0
  20. data/lib/less/js/dist/lessc-rhino-1.7.0.js +449 -0
  21. data/lib/less/js/lib/less/browser.js +22 -9
  22. data/lib/less/js/lib/less/functions.js +47 -16
  23. data/lib/less/js/lib/less/import-visitor.js +29 -5
  24. data/lib/less/js/lib/less/index.js +10 -3
  25. data/lib/less/js/lib/less/parser.js +139 -52
  26. data/lib/less/js/lib/less/to-css-visitor.js +26 -4
  27. data/lib/less/js/lib/less/tree/color.js +11 -1
  28. data/lib/less/js/lib/less/tree/detached-ruleset.js +20 -0
  29. data/lib/less/js/lib/less/tree/dimension.js +17 -6
  30. data/lib/less/js/lib/less/tree/directive.js +33 -29
  31. data/lib/less/js/lib/less/tree/import.js +8 -1
  32. data/lib/less/js/lib/less/tree/keyword.js +1 -0
  33. data/lib/less/js/lib/less/tree/media.js +3 -0
  34. data/lib/less/js/lib/less/tree/mixin.js +15 -11
  35. data/lib/less/js/lib/less/tree/rule.js +14 -4
  36. data/lib/less/js/lib/less/tree/ruleset-call.js +16 -0
  37. data/lib/less/js/lib/less/tree/ruleset.js +48 -19
  38. data/lib/less/js/package.json +13 -13
  39. data/lib/less/js/test/browser/css/postProcessor/postProcessor.css +4 -0
  40. data/lib/less/js/test/browser/less/postProcessor/postProcessor.less +4 -0
  41. data/lib/less/js/test/browser/runner-postProcessor-options.js +4 -0
  42. data/lib/less/js/test/browser/runner-postProcessor.js +3 -0
  43. data/lib/less/js/test/css/debug/linenumbers-all.css +2 -2
  44. data/lib/less/js/test/css/debug/linenumbers-comments.css +1 -1
  45. data/lib/less/js/test/css/debug/linenumbers-mediaquery.css +1 -1
  46. data/lib/less/js/test/css/detached-rulesets.css +71 -0
  47. data/lib/less/js/test/css/functions.css +21 -4
  48. data/lib/less/js/test/css/import-reference.css +23 -4
  49. data/lib/less/js/test/css/merge.css +8 -0
  50. data/lib/less/js/test/css/mixins-pattern.css +4 -0
  51. data/lib/less/js/test/css/scope.css +3 -0
  52. data/lib/less/js/test/css/variables-in-at-rules.css +18 -0
  53. data/lib/less/js/test/less/css-guards.less +3 -0
  54. data/lib/less/js/test/less/detached-rulesets.less +103 -0
  55. data/lib/less/js/test/less/errors/at-rules-undefined-var.less +4 -0
  56. data/lib/less/js/test/less/errors/at-rules-undefined-var.txt +4 -0
  57. data/lib/less/js/test/less/errors/detached-ruleset-1.less +6 -0
  58. data/lib/less/js/test/less/errors/detached-ruleset-1.txt +4 -0
  59. data/lib/less/js/test/less/errors/detached-ruleset-2.less +6 -0
  60. data/lib/less/js/test/less/errors/detached-ruleset-2.txt +4 -0
  61. data/lib/less/js/test/less/errors/detached-ruleset-3.less +4 -0
  62. data/lib/less/js/test/less/errors/detached-ruleset-3.txt +4 -0
  63. data/lib/less/js/test/less/errors/detached-ruleset-4.less +5 -0
  64. data/lib/less/js/test/less/errors/detached-ruleset-4.txt +3 -0
  65. data/lib/less/js/test/less/errors/detached-ruleset-5.less +4 -0
  66. data/lib/less/js/test/less/errors/detached-ruleset-5.txt +3 -0
  67. data/lib/less/js/test/less/errors/detached-ruleset-6.less +5 -0
  68. data/lib/less/js/test/less/errors/detached-ruleset-6.txt +4 -0
  69. data/lib/less/js/test/less/errors/mixin-not-visible-in-scope-1.less +9 -0
  70. data/lib/less/js/test/less/errors/mixin-not-visible-in-scope-1.txt +4 -0
  71. data/lib/less/js/test/less/errors/percentage-missing-space.less +3 -0
  72. data/lib/less/js/test/less/errors/percentage-missing-space.txt +4 -0
  73. data/lib/less/js/test/less/functions.less +25 -7
  74. data/lib/less/js/test/less/import-reference.less +7 -4
  75. data/lib/less/js/test/less/import/import-reference.less +8 -0
  76. data/lib/less/js/test/less/merge.less +20 -1
  77. data/lib/less/js/test/less/mixins-guards.less +7 -1
  78. data/lib/less/js/test/less/mixins-pattern.less +3 -0
  79. data/lib/less/js/test/less/scope.less +25 -0
  80. data/lib/less/js/test/less/variables-in-at-rules.less +20 -0
  81. data/lib/less/version.rb +1 -1
  82. metadata +39 -2
@@ -14,17 +14,19 @@ less.env = less.env || (location.hostname == '127.0.0.1' ||
14
14
  : 'production');
15
15
 
16
16
  var logLevel = {
17
+ debug: 3,
17
18
  info: 2,
18
19
  errors: 1,
19
20
  none: 0
20
21
  };
21
22
 
22
23
  // The amount of logging in the javascript console.
24
+ // 3 - Debug, information and errors
23
25
  // 2 - Information and errors
24
26
  // 1 - Errors
25
27
  // 0 - None
26
28
  // Defaults to 2
27
- less.logLevel = typeof(less.logLevel) != 'undefined' ? less.logLevel : logLevel.info;
29
+ less.logLevel = typeof(less.logLevel) != 'undefined' ? less.logLevel : (less.env === 'development' ? logLevel.debug : logLevel.errors);
28
30
 
29
31
  // Load styles asynchronously (default: false)
30
32
  //
@@ -57,7 +59,7 @@ var cache = null;
57
59
  var fileCache = {};
58
60
 
59
61
  function log(str, level) {
60
- if (less.env == 'development' && typeof(console) !== 'undefined' && less.logLevel >= level) {
62
+ if (typeof(console) !== 'undefined' && less.logLevel >= level) {
61
63
  console.log('less: ' + str);
62
64
  }
63
65
  }
@@ -159,6 +161,13 @@ function createCSS(styles, sheet, lastModified) {
159
161
  }
160
162
  }
161
163
 
164
+ function postProcessCSS(styles) {
165
+ if (less.postProcessor && typeof less.postProcessor === 'function') {
166
+ styles = less.postProcessor.call(styles, styles) || styles;
167
+ }
168
+ return styles;
169
+ }
170
+
162
171
  function errorHTML(e, rootHref) {
163
172
  var id = 'less-error-message:' + extractId(rootHref || "");
164
173
  var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
@@ -402,12 +411,12 @@ function pathDiff(url, baseUrl) {
402
411
  }
403
412
 
404
413
  function getXMLHttpRequest() {
405
- if (window.XMLHttpRequest) {
414
+ if (window.XMLHttpRequest && (window.location.protocol !== "file:" || !window.ActiveXObject)) {
406
415
  return new XMLHttpRequest();
407
416
  } else {
408
417
  try {
409
418
  /*global ActiveXObject */
410
- return new ActiveXObject("MSXML2.XMLHTTP.3.0");
419
+ return new ActiveXObject("Microsoft.XMLHTTP");
411
420
  } catch (e) {
412
421
  log("browser doesn't support AJAX.", logLevel.errors);
413
422
  return null;
@@ -422,7 +431,7 @@ function doXHR(url, type, callback, errback) {
422
431
  if (typeof(xhr.overrideMimeType) === 'function') {
423
432
  xhr.overrideMimeType('text/css');
424
433
  }
425
- log("XHR: Getting '" + url + "'", logLevel.info);
434
+ log("XHR: Getting '" + url + "'", logLevel.debug);
426
435
  xhr.open('GET', url, async);
427
436
  xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
428
437
  xhr.send(null);
@@ -575,7 +584,9 @@ function initRunningMode(){
575
584
  if (e) {
576
585
  error(e, sheet.href);
577
586
  } else if (root) {
578
- createCSS(root.toCSS(less), sheet, env.lastModified);
587
+ var styles = root.toCSS(less);
588
+ styles = postProcessCSS(styles);
589
+ createCSS(styles, sheet, env.lastModified);
579
590
  }
580
591
  });
581
592
  }
@@ -644,12 +655,14 @@ less.refresh = function (reload, modifyVars) {
644
655
  if (env.local) {
645
656
  log("loading " + sheet.href + " from cache.", logLevel.info);
646
657
  } else {
647
- log("parsed " + sheet.href + " successfully.", logLevel.info);
648
- createCSS(root.toCSS(less), sheet, env.lastModified);
658
+ log("parsed " + sheet.href + " successfully.", logLevel.debug);
659
+ var styles = root.toCSS(less);
660
+ styles = postProcessCSS(styles);
661
+ createCSS(styles, sheet, env.lastModified);
649
662
  }
650
663
  log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info);
651
664
  if (env.remaining === 0) {
652
- log("css generated in " + (new Date() - startTime) + 'ms', logLevel.info);
665
+ log("less has finished. css generated in " + (new Date() - startTime) + 'ms', logLevel.info);
653
666
  }
654
667
  endTime = new Date();
655
668
  }, reload, modifyVars);
@@ -95,6 +95,14 @@ tree.functions = {
95
95
  luma: function (color) {
96
96
  return new(tree.Dimension)(Math.round(color.luma() * color.alpha * 100), '%');
97
97
  },
98
+ luminance: function (color) {
99
+ var luminance =
100
+ (0.2126 * color.rgb[0] / 255)
101
+ + (0.7152 * color.rgb[1] / 255)
102
+ + (0.0722 * color.rgb[2] / 255);
103
+
104
+ return new(tree.Dimension)(Math.round(luminance * color.alpha * 100), '%');
105
+ },
98
106
  saturate: function (color, amount) {
99
107
  // filter: saturate(3.2);
100
108
  // should be kept as is, so check for color
@@ -218,25 +226,40 @@ tree.functions = {
218
226
  escape: function (str) {
219
227
  return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
220
228
  },
221
- '%': function (quoted /* arg, arg, ...*/) {
229
+ replace: function (string, pattern, replacement, flags) {
230
+ var result = string.value;
231
+
232
+ result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value);
233
+ return new(tree.Quoted)(string.quote || '', result, string.escaped);
234
+ },
235
+ '%': function (string /* arg, arg, ...*/) {
222
236
  var args = Array.prototype.slice.call(arguments, 1),
223
- str = quoted.value;
237
+ result = string.value;
224
238
 
225
239
  for (var i = 0; i < args.length; i++) {
226
240
  /*jshint loopfunc:true */
227
- str = str.replace(/%[sda]/i, function(token) {
241
+ result = result.replace(/%[sda]/i, function(token) {
228
242
  var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
229
243
  return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
230
244
  });
231
245
  }
232
- str = str.replace(/%%/g, '%');
233
- return new(tree.Quoted)('"' + str + '"', str);
246
+ result = result.replace(/%%/g, '%');
247
+ return new(tree.Quoted)(string.quote || '', result, string.escaped);
234
248
  },
235
249
  unit: function (val, unit) {
236
250
  if(!(val instanceof tree.Dimension)) {
237
251
  throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
238
252
  }
239
- return new(tree.Dimension)(val.value, unit ? unit.toCSS() : "");
253
+ if (unit) {
254
+ if (unit instanceof tree.Keyword) {
255
+ unit = unit.value;
256
+ } else {
257
+ unit = unit.toCSS();
258
+ }
259
+ } else {
260
+ unit = "";
261
+ }
262
+ return new(tree.Dimension)(val.value, unit);
240
263
  },
241
264
  convert: function (val, unit) {
242
265
  return val.convertTo(unit.value);
@@ -264,28 +287,34 @@ tree.functions = {
264
287
  _minmax: function (isMin, args) {
265
288
  args = Array.prototype.slice.call(args);
266
289
  switch(args.length) {
267
- case 0: throw { type: "Argument", message: "one or more arguments required" };
268
- case 1: return args[0];
290
+ case 0: throw { type: "Argument", message: "one or more arguments required" };
269
291
  }
270
- var i, j, current, currentUnified, referenceUnified, unit,
292
+ var i, j, current, currentUnified, referenceUnified, unit, unitStatic, unitClone,
271
293
  order = [], // elems only contains original argument values.
272
294
  values = {}; // key is the unit.toString() for unified tree.Dimension values,
273
295
  // value is the index into the order array.
274
296
  for (i = 0; i < args.length; i++) {
275
297
  current = args[i];
276
298
  if (!(current instanceof tree.Dimension)) {
277
- order.push(current);
299
+ if(Array.isArray(args[i].value)) {
300
+ Array.prototype.push.apply(args, Array.prototype.slice.call(args[i].value));
301
+ }
278
302
  continue;
279
303
  }
280
- currentUnified = current.unify();
281
- unit = currentUnified.unit.toString();
282
- j = values[unit];
304
+ currentUnified = current.unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(current.value, unitClone).unify() : current.unify();
305
+ unit = currentUnified.unit.toString() === "" && unitStatic !== undefined ? unitStatic : currentUnified.unit.toString();
306
+ unitStatic = unit !== "" && unitStatic === undefined || unit !== "" && order[0].unify().unit.toString() === "" ? unit : unitStatic;
307
+ unitClone = unit !== "" && unitClone === undefined ? current.unit.toString() : unitClone;
308
+ j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit];
283
309
  if (j === undefined) {
310
+ if(unitStatic !== undefined && unit !== unitStatic) {
311
+ throw{ type: "Argument", message: "incompatible types" };
312
+ }
284
313
  values[unit] = order.length;
285
314
  order.push(current);
286
315
  continue;
287
316
  }
288
- referenceUnified = order[j].unify();
317
+ referenceUnified = order[j].unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(order[j].value, unitClone).unify() : order[j].unify();
289
318
  if ( isMin && currentUnified.value < referenceUnified.value ||
290
319
  !isMin && currentUnified.value > referenceUnified.value) {
291
320
  order[j] = current;
@@ -294,8 +323,7 @@ tree.functions = {
294
323
  if (order.length == 1) {
295
324
  return order[0];
296
325
  }
297
- args = order.map(function (a) { return a.toCSS(this.env); })
298
- .join(this.env.compress ? "," : ", ");
326
+ args = order.map(function (a) { return a.toCSS(this.env); }).join(this.env.compress ? "," : ", ");
299
327
  return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")");
300
328
  },
301
329
  min: function () {
@@ -304,6 +332,9 @@ tree.functions = {
304
332
  max: function () {
305
333
  return this._minmax(false, arguments);
306
334
  },
335
+ "get-unit": function (n) {
336
+ return new(tree.Anonymous)(n.unit);
337
+ },
307
338
  argb: function (color) {
308
339
  return new(tree.Anonymous)(color.toARGB());
309
340
  },
@@ -1,10 +1,19 @@
1
1
  (function (tree) {
2
- tree.importVisitor = function(importer, finish, evalEnv) {
2
+ tree.importVisitor = function(importer, finish, evalEnv, onceFileDetectionMap, recursionDetector) {
3
3
  this._visitor = new tree.visitor(this);
4
4
  this._importer = importer;
5
5
  this._finish = finish;
6
6
  this.env = evalEnv || new tree.evalEnv();
7
7
  this.importCount = 0;
8
+ this.onceFileDetectionMap = onceFileDetectionMap || {};
9
+ this.recursionDetector = {};
10
+ if (recursionDetector) {
11
+ for(var fullFilename in recursionDetector) {
12
+ if (recursionDetector.hasOwnProperty(fullFilename)) {
13
+ this.recursionDetector[fullFilename] = true;
14
+ }
15
+ }
16
+ }
8
17
  };
9
18
 
10
19
  tree.importVisitor.prototype = {
@@ -51,10 +60,22 @@
51
60
  env.importMultiple = true;
52
61
  }
53
62
 
54
- this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported, fullPath) {
63
+ this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, importedAtRoot, fullPath) {
55
64
  if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
56
65
 
57
- if (imported && !env.importMultiple) { importNode.skip = imported; }
66
+ if (!env.importMultiple) {
67
+ if (importedAtRoot) {
68
+ importNode.skip = true;
69
+ } else {
70
+ importNode.skip = function() {
71
+ if (fullPath in importVisitor.onceFileDetectionMap) {
72
+ return true;
73
+ }
74
+ importVisitor.onceFileDetectionMap[fullPath] = true;
75
+ return false;
76
+ };
77
+ }
78
+ }
58
79
 
59
80
  var subFinish = function(e) {
60
81
  importVisitor.importCount--;
@@ -67,8 +88,11 @@
67
88
  if (root) {
68
89
  importNode.root = root;
69
90
  importNode.importedFilename = fullPath;
70
- if (!inlineCSS && !importNode.skip) {
71
- new(tree.importVisitor)(importVisitor._importer, subFinish, env)
91
+ var duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector;
92
+
93
+ if (!inlineCSS && (env.importMultiple || !duplicateImport)) {
94
+ importVisitor.recursionDetector[fullPath] = true;
95
+ new(tree.importVisitor)(importVisitor._importer, subFinish, env, importVisitor.onceFileDetectionMap, importVisitor.recursionDetector)
72
96
  .run(root);
73
97
  return;
74
98
  }
@@ -4,7 +4,7 @@ var path = require('path'),
4
4
  fs = require('fs');
5
5
 
6
6
  var less = {
7
- version: [1, 6, 3],
7
+ version: [1, 7, 0],
8
8
  Parser: require('./parser').Parser,
9
9
  tree: require('./tree'),
10
10
  render: function (input, options, callback) {
@@ -20,8 +20,13 @@ var less = {
20
20
 
21
21
  if (callback) {
22
22
  parser.parse(input, function (e, root) {
23
- try { callback(e, root && root.toCSS && root.toCSS(options)); }
24
- catch (err) { callback(err); }
23
+ if (e) { callback(e); return; }
24
+ var css;
25
+ try {
26
+ css = root && root.toCSS && root.toCSS(options);
27
+ }
28
+ catch (err) { callback(err); return; }
29
+ callback(null, css);
25
30
  });
26
31
  } else {
27
32
  ee = new (require('events').EventEmitter)();
@@ -94,6 +99,7 @@ var less = {
94
99
 
95
100
  require('./tree/color');
96
101
  require('./tree/directive');
102
+ require('./tree/detached-ruleset');
97
103
  require('./tree/operation');
98
104
  require('./tree/dimension');
99
105
  require('./tree/keyword');
@@ -120,6 +126,7 @@ require('./tree/media');
120
126
  require('./tree/unicode-descriptor');
121
127
  require('./tree/negative');
122
128
  require('./tree/extend');
129
+ require('./tree/ruleset-call');
123
130
 
124
131
 
125
132
  var isUrlRe = /^(?:https?:)?\/\//i;
@@ -43,8 +43,7 @@ less.Parser = function Parser(env) {
43
43
  var input, // LeSS input string
44
44
  i, // current index in `input`
45
45
  j, // current chunk
46
- temp, // temporarily holds a chunk's state, for backtracking
47
- memo, // temporarily holds `i`, when backtracking
46
+ saveStack = [], // holds state for backtracking
48
47
  furthest, // furthest index the parser has gone to
49
48
  chunks, // chunkified input
50
49
  current, // current chunk
@@ -74,7 +73,7 @@ less.Parser = function Parser(env) {
74
73
  var fileParsedFunc = function (e, root, fullPath) {
75
74
  parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue
76
75
 
77
- var importedPreviously = fullPath in parserImports.files || fullPath === rootFilename;
76
+ var importedPreviously = fullPath === rootFilename;
78
77
 
79
78
  parserImports.files[fullPath] = root; // Store the root
80
79
 
@@ -111,8 +110,9 @@ less.Parser = function Parser(env) {
111
110
  }
112
111
  };
113
112
 
114
- function save() { temp = current; memo = currentPos = i; }
115
- function restore() { current = temp; currentPos = i = memo; }
113
+ function save() { currentPos = i; saveStack.push( { current: current, i: i, j: j }); }
114
+ function restore() { var state = saveStack.pop(); current = state.current; currentPos = i = state.i; j = state.j; }
115
+ function forget() { saveStack.pop(); }
116
116
 
117
117
  function sync() {
118
118
  if (i > currentPos) {
@@ -424,7 +424,7 @@ less.Parser = function Parser(env) {
424
424
  if (--level < 0) {
425
425
  return fail("missing opening `{`");
426
426
  }
427
- if (!level) { emitChunk(); }
427
+ if (!level && !parenLevel) { emitChunk(); }
428
428
  continue;
429
429
  case 92: // \
430
430
  if (parserCurrentIndex < len - 1) { parserCurrentIndex++; continue; }
@@ -729,7 +729,7 @@ less.Parser = function Parser(env) {
729
729
  while (current)
730
730
  {
731
731
  node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() ||
732
- mixin.call() || this.comment() || this.directive();
732
+ mixin.call() || this.comment() || this.rulesetCall() || this.directive();
733
733
  if (node) {
734
734
  root.push(node);
735
735
  } else {
@@ -737,6 +737,9 @@ less.Parser = function Parser(env) {
737
737
  break;
738
738
  }
739
739
  }
740
+ if (peekChar('}')) {
741
+ break;
742
+ }
740
743
  }
741
744
 
742
745
  return root;
@@ -804,7 +807,7 @@ less.Parser = function Parser(env) {
804
807
  keyword: function () {
805
808
  var k;
806
809
 
807
- k = $re(/^[_A-Za-z-][_A-Za-z0-9-]*/);
810
+ k = $re(/^%|^[_A-Za-z-][_A-Za-z0-9-]*/);
808
811
  if (k) {
809
812
  var color = tree.Color.fromKeyword(k);
810
813
  if (color) {
@@ -1027,6 +1030,19 @@ less.Parser = function Parser(env) {
1027
1030
  if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { return name[1]; }
1028
1031
  },
1029
1032
 
1033
+ //
1034
+ // The variable part of a variable definition. Used in the `rule` parser
1035
+ //
1036
+ // @fink();
1037
+ //
1038
+ rulesetCall: function () {
1039
+ var name;
1040
+
1041
+ if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) {
1042
+ return new tree.RulesetCall(name[1]);
1043
+ }
1044
+ },
1045
+
1030
1046
  //
1031
1047
  // extend syntax - used to extend selectors
1032
1048
  //
@@ -1112,6 +1128,7 @@ less.Parser = function Parser(env) {
1112
1128
  }
1113
1129
 
1114
1130
  if (parsers.end()) {
1131
+ forget();
1115
1132
  return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
1116
1133
  }
1117
1134
  }
@@ -1124,9 +1141,11 @@ less.Parser = function Parser(env) {
1124
1141
  expressions = [], argsSemiColon = [], argsComma = [],
1125
1142
  isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg;
1126
1143
 
1144
+ save();
1145
+
1127
1146
  while (true) {
1128
1147
  if (isCall) {
1129
- arg = parsers.expression();
1148
+ arg = parsers.detachedRuleset() || parsers.expression();
1130
1149
  } else {
1131
1150
  parsers.comments();
1132
1151
  if (input.charAt(i) === '.' && $re(/^\.{3}/)) {
@@ -1154,7 +1173,7 @@ less.Parser = function Parser(env) {
1154
1173
 
1155
1174
  if (isCall) {
1156
1175
  // Variable
1157
- if (arg.value.length == 1) {
1176
+ if (arg.value && arg.value.length == 1) {
1158
1177
  val = arg.value[0];
1159
1178
  }
1160
1179
  } else {
@@ -1169,7 +1188,21 @@ less.Parser = function Parser(env) {
1169
1188
  }
1170
1189
  expressionContainsNamed = true;
1171
1190
  }
1172
- value = expect(parsers.expression);
1191
+
1192
+ // we do not support setting a ruleset as a default variable - it doesn't make sense
1193
+ // However if we do want to add it, there is nothing blocking it, just don't error
1194
+ // and remove isCall dependency below
1195
+ value = (isCall && parsers.detachedRuleset()) || parsers.expression();
1196
+
1197
+ if (!value) {
1198
+ if (isCall) {
1199
+ error("could not understand value for named argument");
1200
+ } else {
1201
+ restore();
1202
+ returner.args = [];
1203
+ return returner;
1204
+ }
1205
+ }
1173
1206
  nameLoop = (name = val.name);
1174
1207
  } else if (!isCall && $re(/^\.{3}/)) {
1175
1208
  returner.variadic = true;
@@ -1214,6 +1247,7 @@ less.Parser = function Parser(env) {
1214
1247
  }
1215
1248
  }
1216
1249
 
1250
+ forget();
1217
1251
  returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
1218
1252
  return returner;
1219
1253
  },
@@ -1254,10 +1288,14 @@ less.Parser = function Parser(env) {
1254
1288
  variadic = argInfo.variadic;
1255
1289
 
1256
1290
  // .mixincall("@{a}");
1257
- // looks a bit like a mixin definition.. so we have to be nice and restore
1291
+ // looks a bit like a mixin definition..
1292
+ // also
1293
+ // .mixincall(@a: {rule: set;});
1294
+ // so we have to be nice and restore
1258
1295
  if (!$char(')')) {
1259
1296
  furthest = i;
1260
1297
  restore();
1298
+ return;
1261
1299
  }
1262
1300
 
1263
1301
  parsers.comments();
@@ -1269,10 +1307,13 @@ less.Parser = function Parser(env) {
1269
1307
  ruleset = parsers.block();
1270
1308
 
1271
1309
  if (ruleset) {
1310
+ forget();
1272
1311
  return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
1273
1312
  } else {
1274
1313
  restore();
1275
1314
  }
1315
+ } else {
1316
+ forget();
1276
1317
  }
1277
1318
  }
1278
1319
  },
@@ -1336,10 +1377,16 @@ less.Parser = function Parser(env) {
1336
1377
  this.entities.variableCurly();
1337
1378
 
1338
1379
  if (! e) {
1380
+ save();
1339
1381
  if ($char('(')) {
1340
1382
  if ((v = this.selector()) && $char(')')) {
1341
1383
  e = new(tree.Paren)(v);
1384
+ forget();
1385
+ } else {
1386
+ restore();
1342
1387
  }
1388
+ } else {
1389
+ forget();
1343
1390
  }
1344
1391
  }
1345
1392
 
@@ -1442,6 +1489,22 @@ less.Parser = function Parser(env) {
1442
1489
  }
1443
1490
  },
1444
1491
 
1492
+ blockRuleset: function() {
1493
+ var block = this.block();
1494
+
1495
+ if (block) {
1496
+ block = new tree.Ruleset(null, block);
1497
+ }
1498
+ return block;
1499
+ },
1500
+
1501
+ detachedRuleset: function() {
1502
+ var blockRuleset = this.blockRuleset();
1503
+ if (blockRuleset) {
1504
+ return new tree.DetachedRuleset(blockRuleset);
1505
+ }
1506
+ },
1507
+
1445
1508
  //
1446
1509
  // div, .class, body > p {...}
1447
1510
  //
@@ -1472,6 +1535,7 @@ less.Parser = function Parser(env) {
1472
1535
  }
1473
1536
 
1474
1537
  if (selectors && (rules = this.block())) {
1538
+ forget();
1475
1539
  var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports);
1476
1540
  if (env.dumpLineNumbers) {
1477
1541
  ruleset.debugInfo = debugInfo;
@@ -1484,28 +1548,38 @@ less.Parser = function Parser(env) {
1484
1548
  }
1485
1549
  },
1486
1550
  rule: function (tryAnonymous) {
1487
- var name, value, c = input.charAt(i), important, merge = false;
1488
- save();
1551
+ var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable;
1489
1552
 
1490
1553
  if (c === '.' || c === '#' || c === '&') { return; }
1491
1554
 
1555
+ save();
1556
+
1492
1557
  name = this.variable() || this.ruleProperty();
1493
1558
  if (name) {
1494
- // prefer to try to parse first if its a variable or we are compressing
1495
- // but always fallback on the other one
1496
- value = !tryAnonymous && (env.compress || (name.charAt && (name.charAt(0) === '@'))) ?
1497
- (this.value() || this.anonymousValue()) :
1498
- (this.anonymousValue() || this.value());
1499
-
1500
- important = this.important();
1559
+ isVariable = typeof name === "string";
1501
1560
 
1502
- // a name returned by this.ruleProperty() is always an array of the form:
1503
- // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
1504
- // where each item is a tree.Keyword or tree.Variable
1505
- merge = name.pop && (name.pop().value === "+");
1561
+ if (isVariable) {
1562
+ value = this.detachedRuleset();
1563
+ }
1564
+
1565
+ if (!value) {
1566
+ // prefer to try to parse first if its a variable or we are compressing
1567
+ // but always fallback on the other one
1568
+ value = !tryAnonymous && (env.compress || isVariable) ?
1569
+ (this.value() || this.anonymousValue()) :
1570
+ (this.anonymousValue() || this.value());
1571
+
1572
+ important = this.important();
1573
+
1574
+ // a name returned by this.ruleProperty() is always an array of the form:
1575
+ // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
1576
+ // where each item is a tree.Keyword or tree.Variable
1577
+ merge = !isVariable && name.pop().value;
1578
+ }
1506
1579
 
1507
1580
  if (value && this.end()) {
1508
- return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo);
1581
+ forget();
1582
+ return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo);
1509
1583
  } else {
1510
1584
  furthest = i;
1511
1585
  restore();
@@ -1513,6 +1587,8 @@ less.Parser = function Parser(env) {
1513
1587
  return this.rule(true);
1514
1588
  }
1515
1589
  }
1590
+ } else {
1591
+ forget();
1516
1592
  }
1517
1593
  },
1518
1594
  anonymousValue: function () {
@@ -1546,6 +1622,7 @@ less.Parser = function Parser(env) {
1546
1622
  if (dir && (path = this.entities.quoted() || this.entities.url())) {
1547
1623
  features = this.mediaFeatures();
1548
1624
  if ($char(';')) {
1625
+ forget();
1549
1626
  features = features && new(tree.Value)(features);
1550
1627
  return new(tree.Import)(path, features, options, index, env.currentFileInfo);
1551
1628
  }
@@ -1662,7 +1739,7 @@ less.Parser = function Parser(env) {
1662
1739
  //
1663
1740
  directive: function () {
1664
1741
  var index = i, name, value, rules, nonVendorSpecificName,
1665
- hasBlock, hasIdentifier, hasExpression, identifier;
1742
+ hasIdentifier, hasExpression, hasUnknown, hasBlock = true;
1666
1743
 
1667
1744
  if (input.charAt(i) !== '@') { return; }
1668
1745
 
@@ -1683,9 +1760,8 @@ less.Parser = function Parser(env) {
1683
1760
  }
1684
1761
 
1685
1762
  switch(nonVendorSpecificName) {
1763
+ /*
1686
1764
  case "@font-face":
1687
- hasBlock = true;
1688
- break;
1689
1765
  case "@viewport":
1690
1766
  case "@top-left":
1691
1767
  case "@top-left-corner":
@@ -1705,40 +1781,51 @@ less.Parser = function Parser(env) {
1705
1781
  case "@right-bottom":
1706
1782
  hasBlock = true;
1707
1783
  break;
1708
- case "@host":
1709
- case "@page":
1710
- case "@document":
1711
- case "@supports":
1712
- case "@keyframes":
1713
- hasBlock = true;
1784
+ */
1785
+ case "@charset":
1714
1786
  hasIdentifier = true;
1787
+ hasBlock = false;
1715
1788
  break;
1716
1789
  case "@namespace":
1717
1790
  hasExpression = true;
1791
+ hasBlock = false;
1792
+ break;
1793
+ case "@keyframes":
1794
+ hasIdentifier = true;
1795
+ break;
1796
+ case "@host":
1797
+ case "@page":
1798
+ case "@document":
1799
+ case "@supports":
1800
+ hasUnknown = true;
1718
1801
  break;
1719
1802
  }
1720
1803
 
1721
1804
  if (hasIdentifier) {
1722
- identifier = ($re(/^[^{]+/) || '').trim();
1723
- if (identifier) {
1724
- name += " " + identifier;
1805
+ value = this.entity();
1806
+ if (!value) {
1807
+ error("expected " + name + " identifier");
1808
+ }
1809
+ } else if (hasExpression) {
1810
+ value = this.expression();
1811
+ if (!value) {
1812
+ error("expected " + name + " expression");
1813
+ }
1814
+ } else if (hasUnknown) {
1815
+ value = ($re(/^[^{;]+/) || '').trim();
1816
+ if (value) {
1817
+ value = new(tree.Anonymous)(value);
1725
1818
  }
1726
1819
  }
1727
1820
 
1728
1821
  if (hasBlock) {
1729
- rules = this.block();
1730
- if (rules) {
1731
- return new(tree.Directive)(name, rules, index, env.currentFileInfo);
1732
- }
1733
- } else {
1734
- value = hasExpression ? this.expression() : this.entity();
1735
- if (value && $char(';')) {
1736
- var directive = new(tree.Directive)(name, value, index, env.currentFileInfo);
1737
- if (env.dumpLineNumbers) {
1738
- directive.debugInfo = getDebugInfo(i, input, env);
1739
- }
1740
- return directive;
1741
- }
1822
+ rules = this.blockRuleset();
1823
+ }
1824
+
1825
+ if (rules || (!hasBlock && value && $char(';'))) {
1826
+ forget();
1827
+ return new(tree.Directive)(name, value, rules, index, env.currentFileInfo,
1828
+ env.dumpLineNumbers ? getDebugInfo(index, input, env) : null);
1742
1829
  }
1743
1830
 
1744
1831
  restore();
@@ -1944,7 +2031,7 @@ less.Parser = function Parser(env) {
1944
2031
 
1945
2032
  match(/^(\*?)/);
1946
2033
  while (match(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/)); // !
1947
- if ((name.length > 1) && match(/^\s*(\+?)\s*:/)) {
2034
+ if ((name.length > 1) && match(/^\s*((?:\+_|\+)?)\s*:/)) {
1948
2035
  // at last, we have the complete match now. move forward,
1949
2036
  // convert name particles to tree objects and return:
1950
2037
  skipWhitespace(length);