less 2.0.4 → 2.1.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 (112) hide show
  1. data/.gitmodules +0 -3
  2. data/.travis.yml +10 -0
  3. data/README.md +1 -1
  4. data/Rakefile +2 -0
  5. data/bin/lessc +21 -2
  6. data/less.gemspec +5 -3
  7. data/lib/less/defaults.rb +13 -0
  8. data/lib/less/js/CHANGELOG +41 -0
  9. data/lib/less/js/Makefile +21 -2
  10. data/lib/less/js/benchmark/benchmark.less +11 -11
  11. data/lib/less/js/benchmark/less-benchmark.js +3 -5
  12. data/lib/less/js/bin/lessc +52 -18
  13. data/lib/less/js/build/amd.js +6 -0
  14. data/lib/less/js/build/require-rhino.js +7 -0
  15. data/lib/less/js/dist/{less-1.0.44.js → less-1.1.4.js} +159 -45
  16. data/lib/less/js/dist/less-1.1.4.min.js +16 -0
  17. data/lib/less/js/dist/less-1.1.5.js +2805 -0
  18. data/lib/less/js/dist/less-1.1.5.min.js +9 -0
  19. data/lib/less/js/dist/less-1.1.6.js +3004 -0
  20. data/lib/less/js/dist/less-1.1.6.min.js +9 -0
  21. data/lib/less/js/dist/less-1.2.0.js +3293 -0
  22. data/lib/less/js/dist/less-1.2.0.min.js +9 -0
  23. data/lib/less/js/dist/less-1.2.1.js +3318 -0
  24. data/lib/less/js/dist/less-1.2.1.min.js +9 -0
  25. data/lib/less/js/dist/less-1.2.2.js +3337 -0
  26. data/lib/less/js/dist/less-1.2.2.min.js +9 -0
  27. data/lib/less/js/dist/less-1.3.0.js +3478 -0
  28. data/lib/less/js/dist/less-1.3.0.min.js +9 -0
  29. data/lib/less/js/dist/less-rhino-1.1.3.js +2460 -0
  30. data/lib/less/js/dist/less-rhino-1.1.5.js +2481 -0
  31. data/lib/less/js/lib/less/browser.js +38 -27
  32. data/lib/less/js/lib/less/colors.js +151 -0
  33. data/lib/less/js/lib/less/cssmin.js +355 -0
  34. data/lib/less/js/lib/less/functions.js +61 -7
  35. data/lib/less/js/lib/less/index.js +48 -37
  36. data/lib/less/js/lib/less/parser.js +327 -120
  37. data/lib/less/js/lib/less/rhino.js +62 -0
  38. data/lib/less/js/lib/less/tree/alpha.js +1 -1
  39. data/lib/less/js/lib/less/tree/anonymous.js +1 -1
  40. data/lib/less/js/lib/less/tree/assignment.js +17 -0
  41. data/lib/less/js/lib/less/tree/call.js +7 -4
  42. data/lib/less/js/lib/less/tree/color.js +9 -6
  43. data/lib/less/js/lib/less/tree/comment.js +1 -1
  44. data/lib/less/js/lib/less/tree/condition.js +42 -0
  45. data/lib/less/js/lib/less/tree/dimension.js +16 -1
  46. data/lib/less/js/lib/less/tree/directive.js +4 -2
  47. data/lib/less/js/lib/less/tree/element.js +20 -5
  48. data/lib/less/js/lib/less/tree/expression.js +2 -2
  49. data/lib/less/js/lib/less/tree/import.js +16 -14
  50. data/lib/less/js/lib/less/tree/javascript.js +1 -1
  51. data/lib/less/js/lib/less/tree/keyword.js +12 -2
  52. data/lib/less/js/lib/less/tree/media.js +114 -0
  53. data/lib/less/js/lib/less/tree/mixin.js +49 -20
  54. data/lib/less/js/lib/less/tree/operation.js +1 -1
  55. data/lib/less/js/lib/less/tree/paren.js +16 -0
  56. data/lib/less/js/lib/less/tree/quoted.js +2 -2
  57. data/lib/less/js/lib/less/tree/rule.js +8 -4
  58. data/lib/less/js/lib/less/tree/ruleset.js +49 -12
  59. data/lib/less/js/lib/less/tree/selector.js +18 -4
  60. data/lib/less/js/lib/less/tree/url.js +2 -2
  61. data/lib/less/js/lib/less/tree/value.js +1 -1
  62. data/lib/less/js/lib/less/tree/variable.js +5 -3
  63. data/lib/less/js/lib/less/tree.js +6 -2
  64. data/lib/less/js/package.json +2 -2
  65. data/lib/less/js/test/css/colors.css +16 -0
  66. data/lib/less/js/test/css/comments.css +9 -5
  67. data/lib/less/js/test/css/css-3.css +18 -2
  68. data/lib/less/js/test/css/css-escapes.css +1 -1
  69. data/lib/less/js/test/css/css.css +11 -4
  70. data/lib/less/js/test/css/functions.css +13 -0
  71. data/lib/less/js/test/css/ie-filters.css +5 -0
  72. data/lib/less/js/test/css/import.css +12 -3
  73. data/lib/less/js/test/css/media.css +60 -2
  74. data/lib/less/js/test/css/mixins-args.css +17 -2
  75. data/lib/less/js/test/css/mixins-guards.css +58 -0
  76. data/lib/less/js/test/css/mixins-important.css +17 -0
  77. data/lib/less/js/test/css/mixins-pattern.css +4 -6
  78. data/lib/less/js/test/css/mixins.css +21 -0
  79. data/lib/less/js/test/css/operations.css +3 -0
  80. data/lib/less/js/test/css/parens.css +1 -1
  81. data/lib/less/js/test/css/rulesets.css +6 -2
  82. data/lib/less/js/test/css/scope.css +6 -6
  83. data/lib/less/js/test/css/selectors.css +38 -1
  84. data/lib/less/js/test/css/strings.css +6 -4
  85. data/lib/less/js/test/css/variables.css +3 -0
  86. data/lib/less/js/test/css/whitespace.css +5 -3
  87. data/lib/less/js/test/less/colors.less +19 -0
  88. data/lib/less/js/test/less/comments.less +8 -6
  89. data/lib/less/js/test/less/css-3.less +10 -0
  90. data/lib/less/js/test/less/css.less +4 -0
  91. data/lib/less/js/test/less/functions.less +15 -1
  92. data/lib/less/js/test/less/ie-filters.less +8 -0
  93. data/lib/less/js/test/less/import/import-test-e.less +2 -0
  94. data/lib/less/js/test/less/import.less +4 -1
  95. data/lib/less/js/test/less/media.less +55 -1
  96. data/lib/less/js/test/less/mixins-args.less +20 -1
  97. data/lib/less/js/test/less/mixins-guards.less +94 -0
  98. data/lib/less/js/test/less/mixins-important.less +18 -0
  99. data/lib/less/js/test/less/mixins-pattern.less +3 -0
  100. data/lib/less/js/test/less/mixins.less +20 -0
  101. data/lib/less/js/test/less/operations.less +4 -0
  102. data/lib/less/js/test/less/selectors.less +31 -0
  103. data/lib/less/js/test/less/strings.less +2 -0
  104. data/lib/less/js/test/less/variables.less +4 -0
  105. data/lib/less/js/test/less-test.js +3 -5
  106. data/lib/less/loader.rb +24 -25
  107. data/lib/less/parser.rb +32 -6
  108. data/lib/less/version.rb +1 -1
  109. data/lib/less.rb +7 -8
  110. data/spec/less/faulty/faulty.less +3 -0
  111. data/spec/less/parser_spec.rb +28 -2
  112. metadata +321 -149
@@ -1,12 +1,23 @@
1
1
  var less, tree;
2
2
 
3
- if (typeof(window) === 'undefined') {
3
+ if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") {
4
+ // Rhino
5
+ // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88
6
+ if (typeof(window) === 'undefined') { less = {} }
7
+ else { less = window.less = {} }
8
+ tree = less.tree = {};
9
+ less.mode = 'rhino';
10
+ } else if (typeof(window) === 'undefined') {
11
+ // Node.js
4
12
  less = exports,
5
- tree = require('less/tree');
13
+ tree = require('./tree');
14
+ less.mode = 'node';
6
15
  } else {
16
+ // Browser
7
17
  if (typeof(window.less) === 'undefined') { window.less = {} }
8
18
  less = window.less,
9
19
  tree = window.less.tree = {};
20
+ less.mode = 'browser';
10
21
  }
11
22
  //
12
23
  // less.js - parser
@@ -62,7 +73,9 @@ less.Parser = function Parser(env) {
62
73
  paths: env && env.paths || [], // Search paths, when importing
63
74
  queue: [], // Files which haven't been imported yet
64
75
  files: {}, // Holds the imported parse trees
76
+ contents: {}, // Holds the imported file contents
65
77
  mime: env && env.mime, // MIME type of .less files
78
+ error: null, // Error in parsing/evaluating an import
66
79
  push: function (path, callback) {
67
80
  var that = this;
68
81
  this.queue.push(path);
@@ -70,11 +83,13 @@ less.Parser = function Parser(env) {
70
83
  //
71
84
  // Import a file asynchronously
72
85
  //
73
- less.Parser.importer(path, this.paths, function (root) {
86
+ less.Parser.importer(path, this.paths, function (e, root, contents) {
74
87
  that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
75
88
  that.files[path] = root; // Store the root
89
+ that.contents[path] = contents;
76
90
 
77
- callback(root);
91
+ if (e && !that.error) { that.error = e }
92
+ callback(e, root);
78
93
 
79
94
  if (that.queue.length === 0) { finish() } // Call `finish` if we're done importing
80
95
  }, env);
@@ -148,6 +163,20 @@ less.Parser = function Parser(env) {
148
163
  }
149
164
  }
150
165
 
166
+ function expect(arg, msg) {
167
+ var result = $(arg);
168
+ if (! result) {
169
+ error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'"
170
+ : "unexpected token"));
171
+ } else {
172
+ return result;
173
+ }
174
+ }
175
+
176
+ function error(msg, type) {
177
+ throw { index: i, type: type || 'Syntax', message: msg };
178
+ }
179
+
151
180
  // Same as $(), but don't change the state of the parser,
152
181
  // just return the match.
153
182
  function peek(tok) {
@@ -162,6 +191,54 @@ less.Parser = function Parser(env) {
162
191
  }
163
192
  }
164
193
 
194
+ function basename(pathname) {
195
+ if (less.mode === 'node') {
196
+ return require('path').basename(pathname);
197
+ } else {
198
+ return pathname.match(/[^\/]+$/)[0];
199
+ }
200
+ }
201
+
202
+ function getInput(e, env) {
203
+ if (e.filename && env.filename && (e.filename !== env.filename)) {
204
+ return parser.imports.contents[basename(e.filename)];
205
+ } else {
206
+ return input;
207
+ }
208
+ }
209
+
210
+ function getLocation(index, input) {
211
+ for (var n = index, column = -1;
212
+ n >= 0 && input.charAt(n) !== '\n';
213
+ n--) { column++ }
214
+
215
+ return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null,
216
+ column: column };
217
+ }
218
+
219
+ function LessError(e, env) {
220
+ var input = getInput(e, env),
221
+ loc = getLocation(e.index, input),
222
+ line = loc.line,
223
+ col = loc.column,
224
+ lines = input.split('\n');
225
+
226
+ this.type = e.type || 'Syntax';
227
+ this.message = e.message;
228
+ this.filename = e.filename || env.filename;
229
+ this.index = e.index;
230
+ this.line = typeof(line) === 'number' ? line + 1 : null;
231
+ this.callLine = e.call && (getLocation(e.call, input).line + 1);
232
+ this.callExtract = lines[getLocation(e.call, input).line];
233
+ this.stack = e.stack;
234
+ this.column = col;
235
+ this.extract = [
236
+ lines[line - 1],
237
+ lines[line],
238
+ lines[line + 1]
239
+ ];
240
+ }
241
+
165
242
  this.env = env = env || {};
166
243
 
167
244
  // The optimization level dictates the thoroughness of the parser,
@@ -186,19 +263,18 @@ less.Parser = function Parser(env) {
186
263
  var root, start, end, zone, line, lines, buff = [], c, error = null;
187
264
 
188
265
  i = j = current = furthest = 0;
189
- chunks = [];
190
266
  input = str.replace(/\r\n/g, '\n');
191
267
 
192
268
  // Split the input into chunks.
193
269
  chunks = (function (chunks) {
194
270
  var j = 0,
195
- skip = /[^"'`\{\}\/\(\)]+/g,
271
+ skip = /[^"'`\{\}\/\(\)\\]+/g,
196
272
  comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
273
+ string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`\\\r\n]|\\.)*)`/g,
197
274
  level = 0,
198
275
  match,
199
276
  chunk = chunks[0],
200
- inParam,
201
- inString;
277
+ inParam;
202
278
 
203
279
  for (var i = 0, c, cc; i < input.length; i++) {
204
280
  skip.lastIndex = i;
@@ -209,9 +285,17 @@ less.Parser = function Parser(env) {
209
285
  }
210
286
  }
211
287
  c = input.charAt(i);
212
- comment.lastIndex = i;
288
+ comment.lastIndex = string.lastIndex = i;
213
289
 
214
- if (!inString && !inParam && c === '/') {
290
+ if (match = string.exec(input)) {
291
+ if (match.index === i) {
292
+ i += match[0].length;
293
+ chunk.push(match[0]);
294
+ c = input.charAt(i);
295
+ }
296
+ }
297
+
298
+ if (!inParam && c === '/') {
215
299
  cc = input.charAt(i + 1);
216
300
  if (cc === '/' || cc === '*') {
217
301
  if (match = comment.exec(input)) {
@@ -224,51 +308,46 @@ less.Parser = function Parser(env) {
224
308
  }
225
309
  }
226
310
 
227
- if (c === '{' && !inString && !inParam) { level ++;
228
- chunk.push(c);
229
- } else if (c === '}' && !inString && !inParam) { level --;
230
- chunk.push(c);
231
- chunks[++j] = chunk = [];
232
- } else if (c === '(' && !inString && !inParam) {
233
- chunk.push(c);
234
- inParam = true;
235
- } else if (c === ')' && !inString && inParam) {
236
- chunk.push(c);
237
- inParam = false;
238
- } else {
239
- if (c === '"' || c === "'" || c === '`') {
240
- if (! inString) {
241
- inString = c;
242
- } else {
243
- inString = inString === c ? false : inString;
244
- }
245
- }
246
- chunk.push(c);
311
+ switch (c) {
312
+ case '{': if (! inParam) { level ++; chunk.push(c); break }
313
+ case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break }
314
+ case '(': if (! inParam) { inParam = true; chunk.push(c); break }
315
+ case ')': if ( inParam) { inParam = false; chunk.push(c); break }
316
+ default: chunk.push(c);
247
317
  }
248
318
  }
249
319
  if (level > 0) {
250
- throw {
251
- type: 'Syntax',
252
- message: "Missing closing `}`",
320
+ error = new(LessError)({
321
+ index: i,
322
+ type: 'Parse',
323
+ message: "missing closing `}`",
253
324
  filename: env.filename
254
- };
325
+ }, env);
255
326
  }
256
327
 
257
328
  return chunks.map(function (c) { return c.join('') });;
258
329
  })([[]]);
259
330
 
331
+ if (error) {
332
+ return callback(error);
333
+ }
334
+
260
335
  // Start with the primary rule.
261
336
  // The whole syntax tree is held under a Ruleset node,
262
337
  // with the `root` property set to true, so no `{}` are
263
338
  // output. The callback is called when the input is parsed.
264
- root = new(tree.Ruleset)([], $(this.parsers.primary));
265
- root.root = true;
339
+ try {
340
+ root = new(tree.Ruleset)([], $(this.parsers.primary));
341
+ root.root = true;
342
+ } catch (e) {
343
+ return callback(new(LessError)(e, env));
344
+ }
266
345
 
267
346
  root.toCSS = (function (evaluate) {
268
347
  var line, lines, column;
269
348
 
270
349
  return function (options, variables) {
271
- var frames = [];
350
+ var frames = [], importError;
272
351
 
273
352
  options = options || {};
274
353
  //
@@ -303,39 +382,21 @@ less.Parser = function Parser(env) {
303
382
  var css = evaluate.call(this, { frames: frames })
304
383
  .toCSS([], { compress: options.compress || false });
305
384
  } catch (e) {
306
- lines = input.split('\n');
307
- line = getLine(e.index);
308
-
309
- for (var n = e.index, column = -1;
310
- n >= 0 && input.charAt(n) !== '\n';
311
- n--) { column++ }
312
-
313
- throw {
314
- type: e.type,
315
- message: e.message,
316
- filename: env.filename,
317
- index: e.index,
318
- line: typeof(line) === 'number' ? line + 1 : null,
319
- callLine: e.call && (getLine(e.call) + 1),
320
- callExtract: lines[getLine(e.call)],
321
- stack: e.stack,
322
- column: column,
323
- extract: [
324
- lines[line - 1],
325
- lines[line],
326
- lines[line + 1]
327
- ]
328
- };
385
+ throw new(LessError)(e, env);
386
+ }
387
+
388
+ if ((importError = parser.imports.error)) { // Check if there was an error during importing
389
+ if (importError instanceof LessError) throw importError;
390
+ else throw new(LessError)(importError, env);
329
391
  }
330
- if (options.compress) {
392
+
393
+ if (options.yuicompress && less.mode === 'node') {
394
+ return require('./cssmin').compressor.cssmin(css);
395
+ } else if (options.compress) {
331
396
  return css.replace(/(\s)+/g, "$1");
332
397
  } else {
333
398
  return css;
334
399
  }
335
-
336
- function getLine(index) {
337
- return index ? (input.slice(0, index).match(/\n/g) || "").length : null;
338
- }
339
400
  };
340
401
  })(root.eval);
341
402
 
@@ -355,7 +416,7 @@ less.Parser = function Parser(env) {
355
416
  for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
356
417
 
357
418
  error = {
358
- name: "ParseError",
419
+ type: "Parse",
359
420
  message: "Syntax Error on line " + line,
360
421
  index: i,
361
422
  filename: env.filename,
@@ -476,7 +537,15 @@ less.Parser = function Parser(env) {
476
537
  //
477
538
  keyword: function () {
478
539
  var k;
479
- if (k = $(/^[A-Za-z-]+/)) { return new(tree.Keyword)(k) }
540
+
541
+ if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
542
+ if (tree.colors.hasOwnProperty(k)) {
543
+ // detect named color
544
+ return new(tree.Color)(tree.colors[k].slice(1));
545
+ } else {
546
+ return new(tree.Keyword)(k);
547
+ }
548
+ }
480
549
  },
481
550
 
482
551
  //
@@ -492,7 +561,7 @@ less.Parser = function Parser(env) {
492
561
  call: function () {
493
562
  var name, args, index = i;
494
563
 
495
- if (! (name = /^([\w-]+|%)\(/.exec(chunks[j]))) return;
564
+ if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) return;
496
565
 
497
566
  name = name[1].toLowerCase();
498
567
 
@@ -507,12 +576,12 @@ less.Parser = function Parser(env) {
507
576
 
508
577
  if (! $(')')) return;
509
578
 
510
- if (name) { return new(tree.Call)(name, args, index) }
579
+ if (name) { return new(tree.Call)(name, args, index, env.filename) }
511
580
  },
512
581
  arguments: function () {
513
582
  var args = [], arg;
514
583
 
515
- while (arg = $(this.expression)) {
584
+ while (arg = $(this.entities.assignment) || $(this.expression)) {
516
585
  args.push(arg);
517
586
  if (! $(',')) { break }
518
587
  }
@@ -524,6 +593,19 @@ less.Parser = function Parser(env) {
524
593
  $(this.entities.quoted);
525
594
  },
526
595
 
596
+ // Assignments are argument entities for calls.
597
+ // They are present in ie filter properties as shown below.
598
+ //
599
+ // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
600
+ //
601
+
602
+ assignment: function () {
603
+ var key, value;
604
+ if ((key = $(/^\w+(?=\s?=)/i)) && $('=') && (value = $(this.entity))) {
605
+ return new(tree.Assignment)(key, value);
606
+ }
607
+ },
608
+
527
609
  //
528
610
  // Parse url() tokens
529
611
  //
@@ -537,7 +619,8 @@ less.Parser = function Parser(env) {
537
619
  if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
538
620
  value = $(this.entities.quoted) || $(this.entities.variable) ||
539
621
  $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || "";
540
- if (! $(')')) throw new(Error)("missing closing ) for url()");
622
+
623
+ expect(')');
541
624
 
542
625
  return new(tree.URL)((value.value || value.data || value instanceof tree.Variable)
543
626
  ? value : new(tree.Anonymous)(value), imports.paths);
@@ -569,7 +652,7 @@ less.Parser = function Parser(env) {
569
652
  var name, index = i;
570
653
 
571
654
  if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
572
- return new(tree.Variable)(name, index);
655
+ return new(tree.Variable)(name, index, env.filename);
573
656
  }
574
657
  },
575
658
 
@@ -597,7 +680,7 @@ less.Parser = function Parser(env) {
597
680
  var value, c = input.charCodeAt(i);
598
681
  if ((c > 57 || c < 45) || c === 47) return;
599
682
 
600
- if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
683
+ if (value = $(/^(-?\d*\.?\d+)(px|%|em|rem|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
601
684
  return new(tree.Dimension)(value[1], value[2]);
602
685
  }
603
686
  },
@@ -665,18 +748,22 @@ less.Parser = function Parser(env) {
665
748
  // selector for now.
666
749
  //
667
750
  call: function () {
668
- var elements = [], e, c, args, index = i, s = input.charAt(i);
751
+ var elements = [], e, c, args, index = i, s = input.charAt(i), important = false;
669
752
 
670
753
  if (s !== '.' && s !== '#') { return }
671
754
 
672
755
  while (e = $(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)) {
673
- elements.push(new(tree.Element)(c, e));
756
+ elements.push(new(tree.Element)(c, e, i));
674
757
  c = $('>');
675
758
  }
676
759
  $('(') && (args = $(this.entities.arguments)) && $(')');
677
760
 
761
+ if ($(this.important)) {
762
+ important = true;
763
+ }
764
+
678
765
  if (elements.length > 0 && ($(';') || peek('}'))) {
679
- return new(tree.mixin.Call)(elements, args, index);
766
+ return new(tree.mixin.Call)(elements, args || [], index, env.filename, important);
680
767
  }
681
768
  },
682
769
 
@@ -700,38 +787,53 @@ less.Parser = function Parser(env) {
700
787
  // the `{...}` block.
701
788
  //
702
789
  definition: function () {
703
- var name, params = [], match, ruleset, param, value;
704
-
790
+ var name, params = [], match, ruleset, param, value, cond, variadic = false;
705
791
  if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
706
792
  peek(/^[^{]*(;|})/)) return;
707
793
 
794
+ save();
795
+
708
796
  if (match = $(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)) {
709
797
  name = match[1];
710
798
 
711
- while (param = $(this.entities.variable) || $(this.entities.literal)
712
- || $(this.entities.keyword)) {
713
- // Variable
714
- if (param instanceof tree.Variable) {
715
- if ($(':')) {
716
- if (value = $(this.expression)) {
799
+ do {
800
+ if (input.charAt(i) === '.' && $(/^\.{3}/)) {
801
+ variadic = true;
802
+ break;
803
+ } else if (param = $(this.entities.variable) || $(this.entities.literal)
804
+ || $(this.entities.keyword)) {
805
+ // Variable
806
+ if (param instanceof tree.Variable) {
807
+ if ($(':')) {
808
+ value = expect(this.expression, 'expected expression');
717
809
  params.push({ name: param.name, value: value });
810
+ } else if ($(/^\.{3}/)) {
811
+ params.push({ name: param.name, variadic: true });
812
+ variadic = true;
813
+ break;
718
814
  } else {
719
- throw new(Error)("Expected value");
815
+ params.push({ name: param.name });
720
816
  }
721
817
  } else {
722
- params.push({ name: param.name });
818
+ params.push({ value: param });
723
819
  }
724
820
  } else {
725
- params.push({ value: param });
821
+ break;
726
822
  }
727
- if (! $(',')) { break }
823
+ } while ($(','))
824
+
825
+ expect(')');
826
+
827
+ if ($(/^when/)) { // Guard
828
+ cond = expect(this.conditions, 'expected condition');
728
829
  }
729
- if (! $(')')) throw new(Error)("Expected )");
730
830
 
731
831
  ruleset = $(this.block);
732
832
 
733
833
  if (ruleset) {
734
- return new(tree.mixin.Definition)(name, params, ruleset);
834
+ return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
835
+ } else {
836
+ restore();
735
837
  }
736
838
  }
737
839
  }
@@ -766,7 +868,7 @@ less.Parser = function Parser(env) {
766
868
 
767
869
  if (! $(/^\(opacity=/i)) return;
768
870
  if (value = $(/^\d+/) || $(this.entities.variable)) {
769
- if (! $(')')) throw new(Error)("missing closing ) for alpha()");
871
+ expect(')');
770
872
  return new(tree.Alpha)(value);
771
873
  }
772
874
  },
@@ -784,12 +886,21 @@ less.Parser = function Parser(env) {
784
886
  // and an element name, such as a tag a class, or `*`.
785
887
  //
786
888
  element: function () {
787
- var e, t, c;
889
+ var e, t, c, v;
788
890
 
789
891
  c = $(this.combinator);
790
- e = $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) || $('*') || $(this.attribute) || $(/^\([^)@]+\)/);
892
+ e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) ||
893
+ $('*') || $(this.attribute) || $(/^\([^)@]+\)/);
791
894
 
792
- if (e) { return new(tree.Element)(c, e) }
895
+ if (! e) {
896
+ $('(') && (v = $(this.entities.variable)) && $(')') && (e = new(tree.Paren)(v));
897
+ }
898
+
899
+ if (e) { return new(tree.Element)(c, e, i) }
900
+
901
+ if (c.value && c.value.charAt(0) === '&') {
902
+ return new(tree.Element)(c, null, i);
903
+ }
793
904
  },
794
905
 
795
906
  //
@@ -804,14 +915,18 @@ less.Parser = function Parser(env) {
804
915
  combinator: function () {
805
916
  var match, c = input.charAt(i);
806
917
 
807
- if (c === '>' || c === '&' || c === '+' || c === '~') {
918
+ if (c === '>' || c === '+' || c === '~') {
808
919
  i++;
809
920
  while (input.charAt(i) === ' ') { i++ }
810
921
  return new(tree.Combinator)(c);
811
- } else if (c === ':' && input.charAt(i + 1) === ':') {
812
- i += 2;
922
+ } else if (c === '&') {
923
+ match = '&';
924
+ i++;
925
+ if(input.charAt(i) === ' ') {
926
+ match = '& ';
927
+ }
813
928
  while (input.charAt(i) === ' ') { i++ }
814
- return new(tree.Combinator)('::');
929
+ return new(tree.Combinator)(match);
815
930
  } else if (input.charAt(i - 1) === ' ') {
816
931
  return new(tree.Combinator)(" ");
817
932
  } else {
@@ -830,6 +945,12 @@ less.Parser = function Parser(env) {
830
945
  selector: function () {
831
946
  var sel, e, elements = [], c, match;
832
947
 
948
+ if ($('(')) {
949
+ sel = $(this.entity);
950
+ expect(')');
951
+ return new(tree.Selector)([new(tree.Element)('', sel, i)]);
952
+ }
953
+
833
954
  while (e = $(this.element)) {
834
955
  c = input.charAt(i);
835
956
  elements.push(e)
@@ -877,20 +998,15 @@ less.Parser = function Parser(env) {
877
998
  var selectors = [], s, rules, match;
878
999
  save();
879
1000
 
880
- if (match = /^([.#: \w-]+)[\s\n]*\{/.exec(chunks[j])) {
881
- i += match[0].length - 1;
882
- selectors = [new(tree.Selector)([new(tree.Element)(null, match[1])])];
883
- } else {
884
- while (s = $(this.selector)) {
885
- selectors.push(s);
886
- $(this.comment);
887
- if (! $(',')) { break }
888
- $(this.comment);
889
- }
1001
+ while (s = $(this.selector)) {
1002
+ selectors.push(s);
1003
+ $(this.comment);
1004
+ if (! $(',')) { break }
1005
+ $(this.comment);
890
1006
  }
891
1007
 
892
1008
  if (selectors.length > 0 && (rules = $(this.block))) {
893
- return new(tree.Ruleset)(selectors, rules);
1009
+ return new(tree.Ruleset)(selectors, rules, env.strictImports);
894
1010
  } else {
895
1011
  // Backtrack
896
1012
  furthest = i;
@@ -934,11 +1050,67 @@ less.Parser = function Parser(env) {
934
1050
  // stored in `import`, which we pass to the Import constructor.
935
1051
  //
936
1052
  "import": function () {
937
- var path;
1053
+ var path, features, index = i;
938
1054
  if ($(/^@import\s+/) &&
939
- (path = $(this.entities.quoted) || $(this.entities.url)) &&
940
- $(';')) {
941
- return new(tree.Import)(path, imports);
1055
+ (path = $(this.entities.quoted) || $(this.entities.url))) {
1056
+ features = $(this.mediaFeatures);
1057
+ if ($(';')) {
1058
+ return new(tree.Import)(path, imports, features, index);
1059
+ }
1060
+ }
1061
+ },
1062
+
1063
+ mediaFeature: function () {
1064
+ var e, p, nodes = [];
1065
+
1066
+ do {
1067
+ if (e = $(this.entities.keyword)) {
1068
+ nodes.push(e);
1069
+ } else if ($('(')) {
1070
+ p = $(this.property);
1071
+ e = $(this.entity);
1072
+ if ($(')')) {
1073
+ if (p && e) {
1074
+ nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, true)));
1075
+ } else if (e) {
1076
+ nodes.push(new(tree.Paren)(e));
1077
+ } else {
1078
+ return null;
1079
+ }
1080
+ } else { return null }
1081
+ }
1082
+ } while (e);
1083
+
1084
+ if (nodes.length > 0) {
1085
+ return new(tree.Expression)(nodes);
1086
+ }
1087
+ },
1088
+
1089
+ mediaFeatures: function () {
1090
+ var e, features = [];
1091
+
1092
+ do {
1093
+ if (e = $(this.mediaFeature)) {
1094
+ features.push(e);
1095
+ if (! $(',')) { break }
1096
+ } else if (e = $(this.entities.variable)) {
1097
+ features.push(e);
1098
+ if (! $(',')) { break }
1099
+ }
1100
+ } while (e);
1101
+
1102
+ return features.length > 0 ? features : null;
1103
+ },
1104
+
1105
+ media: function () {
1106
+ var features, rules;
1107
+
1108
+ if ($(/^@media/)) {
1109
+ features = $(this.mediaFeatures);
1110
+
1111
+ if (rules = $(this.block)) {
1112
+ return new(tree.Media)(rules, features);
1113
+ }
942
1114
  }
943
1115
  },
944
1116
 
@@ -948,13 +1120,13 @@ less.Parser = function Parser(env) {
948
1120
  // @charset "utf-8";
949
1121
  //
950
1122
  directive: function () {
951
- var name, value, rules, types;
1123
+ var name, value, rules, types, e, nodes;
952
1124
 
953
1125
  if (input.charAt(i) !== '@') return;
954
1126
 
955
- if (value = $(this['import'])) {
1127
+ if (value = $(this['import']) || $(this.media)) {
956
1128
  return value;
957
- } else if (name = $(/^@media|@page|@-[-a-z]+/)) {
1129
+ } else if (name = $(/^@page|@keyframes/) || $(/^@(?:-webkit-|-moz-|-o-|-ms-)[a-z0-9-]+/)) {
958
1130
  types = ($(/^[^{]+/) || '').trim();
959
1131
  if (rules = $(this.block)) {
960
1132
  return new(tree.Directive)(name + " " + types, rules);
@@ -1021,7 +1193,7 @@ less.Parser = function Parser(env) {
1021
1193
  multiplication: function () {
1022
1194
  var m, a, op, operation;
1023
1195
  if (m = $(this.operand)) {
1024
- while ((op = ($('/') || $('*'))) && (a = $(this.operand))) {
1196
+ while (!peek(/^\/\*/) && (op = ($('/') || $('*'))) && (a = $(this.operand))) {
1025
1197
  operation = new(tree.Operation)(op, [operation || m, a]);
1026
1198
  }
1027
1199
  return operation || m;
@@ -1037,6 +1209,35 @@ less.Parser = function Parser(env) {
1037
1209
  return operation || m;
1038
1210
  }
1039
1211
  },
1212
+ conditions: function () {
1213
+ var a, b, index = i, condition;
1214
+
1215
+ if (a = $(this.condition)) {
1216
+ while ($(',') && (b = $(this.condition))) {
1217
+ condition = new(tree.Condition)('or', condition || a, b, index);
1218
+ }
1219
+ return condition || a;
1220
+ }
1221
+ },
1222
+ condition: function () {
1223
+ var a, b, c, op, index = i, negate = false;
1224
+
1225
+ if ($(/^not/)) { negate = true }
1226
+ expect('(');
1227
+ if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1228
+ if (op = $(/^(?:>=|=<|[<=>])/)) {
1229
+ if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1230
+ c = new(tree.Condition)(op, a, b, index, negate);
1231
+ } else {
1232
+ error('expected expression');
1233
+ }
1234
+ } else {
1235
+ c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
1236
+ }
1237
+ expect(')');
1238
+ return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c;
1239
+ }
1240
+ },
1040
1241
 
1041
1242
  //
1042
1243
  // An operand is anything that can be part of an operation,
@@ -1081,18 +1282,24 @@ less.Parser = function Parser(env) {
1081
1282
  };
1082
1283
  };
1083
1284
 
1084
- if (typeof(window) !== 'undefined') {
1285
+ if (less.mode === 'browser' || less.mode === 'rhino') {
1085
1286
  //
1086
1287
  // Used by `@import` directives
1087
1288
  //
1088
1289
  less.Parser.importer = function (path, paths, callback, env) {
1089
- if (path.charAt(0) !== '/' && paths.length > 0) {
1290
+ if (!/^([a-z]+:)?\//.test(path) && paths.length > 0) {
1090
1291
  path = paths[0] + path;
1091
1292
  }
1092
1293
  // We pass `true` as 3rd argument, to force the reload of the import.
1093
1294
  // This is so we can get the syntax tree as opposed to just the CSS output,
1094
1295
  // as we need this to evaluate the current stylesheet.
1095
- loadStyleSheet({ href: path, title: path, type: env.mime }, callback, true);
1296
+ loadStyleSheet({ href: path, title: path, type: env.mime }, function (e) {
1297
+ if (e && typeof(env.errback) === "function") {
1298
+ env.errback.call(null, path, paths, callback, env);
1299
+ } else {
1300
+ callback.apply(null, arguments);
1301
+ }
1302
+ }, true);
1096
1303
  };
1097
1304
  }
1098
1305