less 2.5.1 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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);