less 2.0.8 → 2.0.9

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 (70) hide show
  1. data/lib/less/js/Makefile +8 -2
  2. data/lib/less/js/benchmark/less-benchmark.js +1 -1
  3. data/lib/less/js/bin/lessc +17 -11
  4. data/lib/less/js/build/amd.js +6 -0
  5. data/lib/less/js/dist/less-1.1.5.min.js +1 -8
  6. data/lib/less/js/dist/less-1.1.6.js +3004 -0
  7. data/lib/less/js/dist/less-1.1.6.min.js +9 -0
  8. data/lib/less/js/dist/less-1.2.0.js +3293 -0
  9. data/lib/less/js/dist/less-1.2.0.min.js +9 -0
  10. data/lib/less/js/dist/less-1.2.1.js +3318 -0
  11. data/lib/less/js/dist/less-1.2.1.min.js +9 -0
  12. data/lib/less/js/lib/less/browser.js +33 -28
  13. data/lib/less/js/lib/less/colors.js +151 -0
  14. data/lib/less/js/lib/less/cssmin.js +355 -0
  15. data/lib/less/js/lib/less/functions.js +49 -6
  16. data/lib/less/js/lib/less/index.js +21 -18
  17. data/lib/less/js/lib/less/parser.js +230 -92
  18. data/lib/less/js/lib/less/rhino.js +3 -1
  19. data/lib/less/js/lib/less/tree.js +6 -2
  20. data/lib/less/js/lib/less/tree/call.js +6 -3
  21. data/lib/less/js/lib/less/tree/condition.js +42 -0
  22. data/lib/less/js/lib/less/tree/dimension.js +15 -0
  23. data/lib/less/js/lib/less/tree/directive.js +8 -2
  24. data/lib/less/js/lib/less/tree/element.js +14 -2
  25. data/lib/less/js/lib/less/tree/expression.js +1 -1
  26. data/lib/less/js/lib/less/tree/import.js +13 -11
  27. data/lib/less/js/lib/less/tree/keyword.js +11 -1
  28. data/lib/less/js/lib/less/tree/mixin.js +31 -13
  29. data/lib/less/js/lib/less/tree/paren.js +16 -0
  30. data/lib/less/js/lib/less/tree/quoted.js +1 -1
  31. data/lib/less/js/lib/less/tree/rule.js +7 -3
  32. data/lib/less/js/lib/less/tree/ruleset.js +7 -4
  33. data/lib/less/js/lib/less/tree/selector.js +5 -0
  34. data/lib/less/js/lib/less/tree/variable.js +4 -2
  35. data/lib/less/js/package.json +1 -1
  36. data/lib/less/js/test/css/colors.css +10 -0
  37. data/lib/less/js/test/css/comments.css +9 -5
  38. data/lib/less/js/test/css/css-3.css +4 -2
  39. data/lib/less/js/test/css/css-escapes.css +1 -1
  40. data/lib/less/js/test/css/css.css +8 -4
  41. data/lib/less/js/test/css/functions.css +13 -0
  42. data/lib/less/js/test/css/import.css +10 -3
  43. data/lib/less/js/test/css/media.css +8 -2
  44. data/lib/less/js/test/css/mixins-args.css +5 -2
  45. data/lib/less/js/test/css/mixins-guards.css +58 -0
  46. data/lib/less/js/test/css/mixins-important.css +17 -0
  47. data/lib/less/js/test/css/mixins.css +2 -1
  48. data/lib/less/js/test/css/operations.css +3 -0
  49. data/lib/less/js/test/css/parens.css +1 -1
  50. data/lib/less/js/test/css/rulesets.css +6 -2
  51. data/lib/less/js/test/css/scope.css +6 -6
  52. data/lib/less/js/test/css/selectors.css +8 -4
  53. data/lib/less/js/test/css/strings.css +6 -4
  54. data/lib/less/js/test/css/variables.css +3 -0
  55. data/lib/less/js/test/css/whitespace.css +5 -3
  56. data/lib/less/js/test/less-test.js +1 -1
  57. data/lib/less/js/test/less/colors.less +13 -0
  58. data/lib/less/js/test/less/comments.less +8 -6
  59. data/lib/less/js/test/less/functions.less +15 -1
  60. data/lib/less/js/test/less/import.less +3 -1
  61. data/lib/less/js/test/less/import/import-test-e.less +2 -0
  62. data/lib/less/js/test/less/media.less +6 -0
  63. data/lib/less/js/test/less/mixins-args.less +6 -0
  64. data/lib/less/js/test/less/mixins-guards.less +94 -0
  65. data/lib/less/js/test/less/mixins-important.less +18 -0
  66. data/lib/less/js/test/less/operations.less +4 -0
  67. data/lib/less/js/test/less/strings.less +2 -0
  68. data/lib/less/js/test/less/variables.less +4 -0
  69. data/lib/less/version.rb +1 -1
  70. metadata +28 -12
@@ -144,20 +144,63 @@ tree.functions = {
144
144
  return new(tree.Quoted)('"' + str + '"', str);
145
145
  },
146
146
  round: function (n) {
147
+ return this._math('round', n);
148
+ },
149
+ ceil: function (n) {
150
+ return this._math('ceil', n);
151
+ },
152
+ floor: function (n) {
153
+ return this._math('floor', n);
154
+ },
155
+ _math: function (fn, n) {
147
156
  if (n instanceof tree.Dimension) {
148
- return new(tree.Dimension)(Math.round(number(n)), n.unit);
157
+ return new(tree.Dimension)(Math[fn](number(n)), n.unit);
149
158
  } else if (typeof(n) === 'number') {
150
- return Math.round(n);
159
+ return Math[fn](n);
151
160
  } else {
152
- throw {
153
- error: "RuntimeError",
154
- message: "math functions take numbers as parameters"
155
- };
161
+ throw { type: "Argument", message: "argument must be a number" };
156
162
  }
157
163
  },
158
164
  argb: function (color) {
159
165
  return new(tree.Anonymous)(color.toARGB());
160
166
 
167
+ },
168
+ percentage: function (n) {
169
+ return new(tree.Dimension)(n.value * 100, '%');
170
+ },
171
+ color: function (n) {
172
+ if (n instanceof tree.Quoted) {
173
+ return new(tree.Color)(n.value.slice(1));
174
+ } else {
175
+ throw { type: "Argument", message: "argument must be a string" };
176
+ }
177
+ },
178
+ iscolor: function (n) {
179
+ return this._isa(n, tree.Color);
180
+ },
181
+ isnumber: function (n) {
182
+ return this._isa(n, tree.Dimension);
183
+ },
184
+ isstring: function (n) {
185
+ return this._isa(n, tree.Quoted);
186
+ },
187
+ iskeyword: function (n) {
188
+ return this._isa(n, tree.Keyword);
189
+ },
190
+ isurl: function (n) {
191
+ return this._isa(n, tree.URL);
192
+ },
193
+ ispixel: function (n) {
194
+ return (n instanceof tree.Dimension) && n.unit === 'px' ? tree.True : tree.False;
195
+ },
196
+ ispercentage: function (n) {
197
+ return (n instanceof tree.Dimension) && n.unit === '%' ? tree.True : tree.False;
198
+ },
199
+ isem: function (n) {
200
+ return (n instanceof tree.Dimension) && n.unit === 'em' ? tree.True : tree.False;
201
+ },
202
+ _isa: function (n, Type) {
203
+ return (n instanceof Type) ? tree.True : tree.False;
161
204
  }
162
205
  };
163
206
 
@@ -3,7 +3,7 @@ var path = require('path'),
3
3
  fs = require('fs');
4
4
 
5
5
  var less = {
6
- version: [1, 1, 5],
6
+ version: [1, 2, 1],
7
7
  Parser: require('./parser').Parser,
8
8
  importer: require('./parser').importer,
9
9
  tree: require('./tree'),
@@ -43,7 +43,9 @@ var less = {
43
43
 
44
44
  if (options.silent) { return }
45
45
 
46
- if (!ctx.index) {
46
+ if (ctx.stack) { return sys.error(stylize(ctx.stack, 'red')) }
47
+
48
+ if (!ctx.hasOwnProperty('index')) {
47
49
  return sys.error(ctx.stack || ctx.message);
48
50
  }
49
51
 
@@ -51,17 +53,20 @@ var less = {
51
53
  error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
52
54
  }
53
55
 
54
- error.push(ctx.line + ' ' + extract[1].slice(0, ctx.column)
55
- + stylize(stylize(extract[1][ctx.column], 'bold')
56
- + extract[1].slice(ctx.column + 1), 'yellow'));
56
+ if (extract[1]) {
57
+ error.push(ctx.line + ' ' + extract[1].slice(0, ctx.column)
58
+ + stylize(stylize(stylize(extract[1][ctx.column], 'bold')
59
+ + extract[1].slice(ctx.column + 1), 'red'), 'inverse'));
60
+ }
57
61
 
58
62
  if (typeof(extract[2]) === 'string') {
59
63
  error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
60
64
  }
61
65
  error = error.join('\n') + '\033[0m\n';
62
66
 
63
- message += stylize(ctx.message, 'red');
64
- ctx.filename && (message += stylize(' in ', 'red') + ctx.filename);
67
+ message += stylize(ctx.type + 'Error: ' + ctx.message, 'red');
68
+ ctx.filename && (message += stylize(' in ', 'red') + ctx.filename +
69
+ stylize(':' + ctx.line + ':' + ctx.column, 'grey'));
65
70
 
66
71
  sys.error(message, error);
67
72
 
@@ -69,16 +74,15 @@ var less = {
69
74
  sys.error(stylize('from ', 'red') + (ctx.filename || ''));
70
75
  sys.error(stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract);
71
76
  }
72
- if (ctx.stack) { sys.error(stylize(ctx.stack, 'red')) }
73
77
  }
74
78
  };
75
79
 
76
- ['color', 'directive', 'operation', 'dimension',
77
- 'keyword', 'variable', 'ruleset', 'element',
78
- 'selector', 'quoted', 'expression', 'rule',
79
- 'call', 'url', 'alpha', 'import',
80
- 'mixin', 'comment', 'anonymous', 'value',
81
- 'javascript', 'assignment'
80
+ ['color', 'directive', 'operation', 'dimension',
81
+ 'keyword', 'variable', 'ruleset', 'element',
82
+ 'selector', 'quoted', 'expression', 'rule',
83
+ 'call', 'url', 'alpha', 'import',
84
+ 'mixin', 'comment', 'anonymous', 'value',
85
+ 'javascript', 'assignment', 'condition', 'paren'
82
86
  ].forEach(function (n) {
83
87
  require('./tree/' + n);
84
88
  });
@@ -106,17 +110,16 @@ less.Parser.importer = function (file, paths, callback) {
106
110
  paths: [path.dirname(pathname)].concat(paths),
107
111
  filename: pathname
108
112
  }).parse(data, function (e, root) {
109
- if (e) less.writeError(e);
110
- callback(root);
113
+ callback(e, root, data);
111
114
  });
112
115
  });
113
116
  } else {
114
- sys.error("file '" + file + "' wasn't found.\n");
115
- process.exit(1);
117
+ callback({ type: 'File', message: "'" + file + "' wasn't found.\n" });
116
118
  }
117
119
  }
118
120
 
119
121
  require('./functions');
122
+ require('./colors');
120
123
 
121
124
  for (var k in less) { exports[k] = less[k] }
122
125
 
@@ -3,7 +3,8 @@ var less, tree;
3
3
  if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") {
4
4
  // Rhino
5
5
  // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88
6
- less = {};
6
+ if (typeof(window) === 'undefined') { less = {} }
7
+ else { less = window.less = {} }
7
8
  tree = less.tree = {};
8
9
  less.mode = 'rhino';
9
10
  } else if (typeof(window) === 'undefined') {
@@ -72,7 +73,9 @@ less.Parser = function Parser(env) {
72
73
  paths: env && env.paths || [], // Search paths, when importing
73
74
  queue: [], // Files which haven't been imported yet
74
75
  files: {}, // Holds the imported parse trees
76
+ contents: {}, // Holds the imported file contents
75
77
  mime: env && env.mime, // MIME type of .less files
78
+ error: null, // Error in parsing/evaluating an import
76
79
  push: function (path, callback) {
77
80
  var that = this;
78
81
  this.queue.push(path);
@@ -80,11 +83,13 @@ less.Parser = function Parser(env) {
80
83
  //
81
84
  // Import a file asynchronously
82
85
  //
83
- less.Parser.importer(path, this.paths, function (root) {
86
+ less.Parser.importer(path, this.paths, function (e, root, contents) {
84
87
  that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
85
88
  that.files[path] = root; // Store the root
89
+ that.contents[path] = contents;
86
90
 
87
- callback(root);
91
+ if (e && !that.error) { that.error = e }
92
+ callback(e, root);
88
93
 
89
94
  if (that.queue.length === 0) { finish() } // Call `finish` if we're done importing
90
95
  }, env);
@@ -158,6 +163,20 @@ less.Parser = function Parser(env) {
158
163
  }
159
164
  }
160
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
+
161
180
  // Same as $(), but don't change the state of the parser,
162
181
  // just return the match.
163
182
  function peek(tok) {
@@ -172,6 +191,46 @@ less.Parser = function Parser(env) {
172
191
  }
173
192
  }
174
193
 
194
+ function getInput(e, env) {
195
+ if (e.filename && env.filename && (e.filename !== env.filename)) {
196
+ return parser.imports.contents[e.filename];
197
+ } else {
198
+ return input;
199
+ }
200
+ }
201
+
202
+ function getLocation(index, input) {
203
+ for (var n = index, column = -1;
204
+ n >= 0 && input.charAt(n) !== '\n';
205
+ n--) { column++ }
206
+
207
+ return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null,
208
+ column: column };
209
+ }
210
+
211
+ function LessError(e, env) {
212
+ var input = getInput(e, env),
213
+ loc = getLocation(e.index, input),
214
+ line = loc.line,
215
+ col = loc.column,
216
+ lines = input.split('\n');
217
+
218
+ this.type = e.type || 'Syntax';
219
+ this.message = e.message;
220
+ this.filename = e.filename || env.filename;
221
+ this.index = e.index;
222
+ this.line = typeof(line) === 'number' ? line + 1 : null;
223
+ this.callLine = e.call && (getLocation(e.call, input) + 1);
224
+ this.callExtract = lines[getLocation(e.call, input)];
225
+ this.stack = e.stack;
226
+ this.column = col;
227
+ this.extract = [
228
+ lines[line - 1],
229
+ lines[line],
230
+ lines[line + 1]
231
+ ];
232
+ }
233
+
175
234
  this.env = env = env || {};
176
235
 
177
236
  // The optimization level dictates the thoroughness of the parser,
@@ -196,19 +255,18 @@ less.Parser = function Parser(env) {
196
255
  var root, start, end, zone, line, lines, buff = [], c, error = null;
197
256
 
198
257
  i = j = current = furthest = 0;
199
- chunks = [];
200
258
  input = str.replace(/\r\n/g, '\n');
201
259
 
202
260
  // Split the input into chunks.
203
261
  chunks = (function (chunks) {
204
262
  var j = 0,
205
- skip = /[^"'`\{\}\/\(\)]+/g,
263
+ skip = /[^"'`\{\}\/\(\)\\]+/g,
206
264
  comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
265
+ string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`\\\r\n]|\\.)*)`/g,
207
266
  level = 0,
208
267
  match,
209
268
  chunk = chunks[0],
210
- inParam,
211
- inString;
269
+ inParam;
212
270
 
213
271
  for (var i = 0, c, cc; i < input.length; i++) {
214
272
  skip.lastIndex = i;
@@ -219,9 +277,17 @@ less.Parser = function Parser(env) {
219
277
  }
220
278
  }
221
279
  c = input.charAt(i);
222
- comment.lastIndex = i;
280
+ comment.lastIndex = string.lastIndex = i;
281
+
282
+ if (match = string.exec(input)) {
283
+ if (match.index === i) {
284
+ i += match[0].length;
285
+ chunk.push(match[0]);
286
+ c = input.charAt(i);
287
+ }
288
+ }
223
289
 
224
- if (!inString && !inParam && c === '/') {
290
+ if (!inParam && c === '/') {
225
291
  cc = input.charAt(i + 1);
226
292
  if (cc === '/' || cc === '*') {
227
293
  if (match = comment.exec(input)) {
@@ -234,34 +300,21 @@ less.Parser = function Parser(env) {
234
300
  }
235
301
  }
236
302
 
237
- if (c === '{' && !inString && !inParam) { level ++;
238
- chunk.push(c);
239
- } else if (c === '}' && !inString && !inParam) { level --;
240
- chunk.push(c);
241
- chunks[++j] = chunk = [];
242
- } else if (c === '(' && !inString && !inParam) {
243
- chunk.push(c);
244
- inParam = true;
245
- } else if (c === ')' && !inString && inParam) {
246
- chunk.push(c);
247
- inParam = false;
248
- } else {
249
- if (c === '"' || c === "'" || c === '`') {
250
- if (! inString) {
251
- inString = c;
252
- } else {
253
- inString = inString === c ? false : inString;
254
- }
255
- }
256
- chunk.push(c);
303
+ switch (c) {
304
+ case '{': if (! inParam) { level ++; chunk.push(c); break }
305
+ case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break }
306
+ case '(': if (! inParam) { inParam = true; chunk.push(c); break }
307
+ case ')': if ( inParam) { inParam = false; chunk.push(c); break }
308
+ default: chunk.push(c);
257
309
  }
258
310
  }
259
311
  if (level > 0) {
260
- throw {
261
- type: 'Syntax',
262
- message: "Missing closing `}`",
312
+ return callback(new(LessError)({
313
+ index: i,
314
+ type: 'Parse',
315
+ message: "missing closing `}`",
263
316
  filename: env.filename
264
- };
317
+ }, env));
265
318
  }
266
319
 
267
320
  return chunks.map(function (c) { return c.join('') });;
@@ -271,14 +324,18 @@ less.Parser = function Parser(env) {
271
324
  // The whole syntax tree is held under a Ruleset node,
272
325
  // with the `root` property set to true, so no `{}` are
273
326
  // output. The callback is called when the input is parsed.
274
- root = new(tree.Ruleset)([], $(this.parsers.primary));
275
- root.root = true;
327
+ try {
328
+ root = new(tree.Ruleset)([], $(this.parsers.primary));
329
+ root.root = true;
330
+ } catch (e) {
331
+ return callback(new(LessError)(e, env));
332
+ }
276
333
 
277
334
  root.toCSS = (function (evaluate) {
278
335
  var line, lines, column;
279
336
 
280
337
  return function (options, variables) {
281
- var frames = [];
338
+ var frames = [], importError;
282
339
 
283
340
  options = options || {};
284
341
  //
@@ -313,39 +370,21 @@ less.Parser = function Parser(env) {
313
370
  var css = evaluate.call(this, { frames: frames })
314
371
  .toCSS([], { compress: options.compress || false });
315
372
  } catch (e) {
316
- lines = input.split('\n');
317
- line = getLine(e.index);
318
-
319
- for (var n = e.index, column = -1;
320
- n >= 0 && input.charAt(n) !== '\n';
321
- n--) { column++ }
322
-
323
- throw {
324
- type: e.type,
325
- message: e.message,
326
- filename: env.filename,
327
- index: e.index,
328
- line: typeof(line) === 'number' ? line + 1 : null,
329
- callLine: e.call && (getLine(e.call) + 1),
330
- callExtract: lines[getLine(e.call)],
331
- stack: e.stack,
332
- column: column,
333
- extract: [
334
- lines[line - 1],
335
- lines[line],
336
- lines[line + 1]
337
- ]
338
- };
373
+ throw new(LessError)(e, env);
374
+ }
375
+
376
+ if ((importError = parser.imports.error)) { // Check if there was an error during importing
377
+ if (importError instanceof LessError) throw importError;
378
+ else throw new(LessError)(importError, env);
339
379
  }
340
- if (options.compress) {
380
+
381
+ if (options.yuicompress && less.mode === 'node') {
382
+ return require('./cssmin').compressor.cssmin(css);
383
+ } else if (options.compress) {
341
384
  return css.replace(/(\s)+/g, "$1");
342
385
  } else {
343
386
  return css;
344
387
  }
345
-
346
- function getLine(index) {
347
- return index ? (input.slice(0, index).match(/\n/g) || "").length : null;
348
- }
349
388
  };
350
389
  })(root.eval);
351
390
 
@@ -365,7 +404,7 @@ less.Parser = function Parser(env) {
365
404
  for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
366
405
 
367
406
  error = {
368
- name: "ParseError",
407
+ type: "Parse",
369
408
  message: "Syntax Error on line " + line,
370
409
  index: i,
371
410
  filename: env.filename,
@@ -486,7 +525,15 @@ less.Parser = function Parser(env) {
486
525
  //
487
526
  keyword: function () {
488
527
  var k;
489
- if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) { return new(tree.Keyword)(k) }
528
+
529
+ if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
530
+ if (tree.colors.hasOwnProperty(k)) {
531
+ // detect named color
532
+ return new(tree.Color)(tree.colors[k].slice(1));
533
+ } else {
534
+ return new(tree.Keyword)(k);
535
+ }
536
+ }
490
537
  },
491
538
 
492
539
  //
@@ -517,7 +564,7 @@ less.Parser = function Parser(env) {
517
564
 
518
565
  if (! $(')')) return;
519
566
 
520
- if (name) { return new(tree.Call)(name, args, index) }
567
+ if (name) { return new(tree.Call)(name, args, index, env.filename) }
521
568
  },
522
569
  arguments: function () {
523
570
  var args = [], arg;
@@ -560,7 +607,8 @@ less.Parser = function Parser(env) {
560
607
  if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
561
608
  value = $(this.entities.quoted) || $(this.entities.variable) ||
562
609
  $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || "";
563
- if (! $(')')) throw new(Error)("missing closing ) for url()");
610
+
611
+ expect(')');
564
612
 
565
613
  return new(tree.URL)((value.value || value.data || value instanceof tree.Variable)
566
614
  ? value : new(tree.Anonymous)(value), imports.paths);
@@ -592,7 +640,7 @@ less.Parser = function Parser(env) {
592
640
  var name, index = i;
593
641
 
594
642
  if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
595
- return new(tree.Variable)(name, index);
643
+ return new(tree.Variable)(name, index, env.filename);
596
644
  }
597
645
  },
598
646
 
@@ -620,7 +668,7 @@ less.Parser = function Parser(env) {
620
668
  var value, c = input.charCodeAt(i);
621
669
  if ((c > 57 || c < 45) || c === 47) return;
622
670
 
623
- if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
671
+ if (value = $(/^(-?\d*\.?\d+)(px|%|em|rem|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
624
672
  return new(tree.Dimension)(value[1], value[2]);
625
673
  }
626
674
  },
@@ -688,7 +736,7 @@ less.Parser = function Parser(env) {
688
736
  // selector for now.
689
737
  //
690
738
  call: function () {
691
- var elements = [], e, c, args, index = i, s = input.charAt(i);
739
+ var elements = [], e, c, args, index = i, s = input.charAt(i), important = false;
692
740
 
693
741
  if (s !== '.' && s !== '#') { return }
694
742
 
@@ -698,8 +746,12 @@ less.Parser = function Parser(env) {
698
746
  }
699
747
  $('(') && (args = $(this.entities.arguments)) && $(')');
700
748
 
749
+ if ($(this.important)) {
750
+ important = true;
751
+ }
752
+
701
753
  if (elements.length > 0 && ($(';') || peek('}'))) {
702
- return new(tree.mixin.Call)(elements, args, index);
754
+ return new(tree.mixin.Call)(elements, args, index, env.filename, important);
703
755
  }
704
756
  },
705
757
 
@@ -723,11 +775,12 @@ less.Parser = function Parser(env) {
723
775
  // the `{...}` block.
724
776
  //
725
777
  definition: function () {
726
- var name, params = [], match, ruleset, param, value;
727
-
778
+ var name, params = [], match, ruleset, param, value, cond;
728
779
  if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
729
780
  peek(/^[^{]*(;|})/)) return;
730
781
 
782
+ save();
783
+
731
784
  if (match = $(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)) {
732
785
  name = match[1];
733
786
 
@@ -736,11 +789,8 @@ less.Parser = function Parser(env) {
736
789
  // Variable
737
790
  if (param instanceof tree.Variable) {
738
791
  if ($(':')) {
739
- if (value = $(this.expression)) {
740
- params.push({ name: param.name, value: value });
741
- } else {
742
- throw new(Error)("Expected value");
743
- }
792
+ value = expect(this.expression, 'expected expression');
793
+ params.push({ name: param.name, value: value });
744
794
  } else {
745
795
  params.push({ name: param.name });
746
796
  }
@@ -749,12 +799,18 @@ less.Parser = function Parser(env) {
749
799
  }
750
800
  if (! $(',')) { break }
751
801
  }
752
- if (! $(')')) throw new(Error)("Expected )");
802
+ expect(')');
803
+
804
+ if ($(/^when/)) { // Guard
805
+ cond = expect(this.conditions, 'expected condition');
806
+ }
753
807
 
754
808
  ruleset = $(this.block);
755
809
 
756
810
  if (ruleset) {
757
- return new(tree.mixin.Definition)(name, params, ruleset);
811
+ return new(tree.mixin.Definition)(name, params, ruleset, cond);
812
+ } else {
813
+ restore();
758
814
  }
759
815
  }
760
816
  }
@@ -789,7 +845,7 @@ less.Parser = function Parser(env) {
789
845
 
790
846
  if (! $(/^\(opacity=/i)) return;
791
847
  if (value = $(/^\d+/) || $(this.entities.variable)) {
792
- if (! $(')')) throw new(Error)("missing closing ) for alpha()");
848
+ expect(')');
793
849
  return new(tree.Alpha)(value);
794
850
  }
795
851
  },
@@ -807,12 +863,16 @@ less.Parser = function Parser(env) {
807
863
  // and an element name, such as a tag a class, or `*`.
808
864
  //
809
865
  element: function () {
810
- var e, t, c;
866
+ var e, t, c, v;
811
867
 
812
868
  c = $(this.combinator);
813
869
  e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) ||
814
870
  $('*') || $(this.attribute) || $(/^\([^)@]+\)/);
815
871
 
872
+ if (! e) {
873
+ $('(') && (v = $(this.entities.variable)) && $(')') && (e = new(tree.Paren)(v));
874
+ }
875
+
816
876
  if (e) { return new(tree.Element)(c, e, i) }
817
877
 
818
878
  if (c.value && c.value.charAt(0) === '&') {
@@ -965,11 +1025,60 @@ less.Parser = function Parser(env) {
965
1025
  // stored in `import`, which we pass to the Import constructor.
966
1026
  //
967
1027
  "import": function () {
968
- var path;
1028
+ var path, features, index = i;
969
1029
  if ($(/^@import\s+/) &&
970
- (path = $(this.entities.quoted) || $(this.entities.url)) &&
971
- $(';')) {
972
- return new(tree.Import)(path, imports);
1030
+ (path = $(this.entities.quoted) || $(this.entities.url))) {
1031
+ features = $(this.mediaFeatures);
1032
+ if ($(';')) {
1033
+ return new(tree.Import)(path, imports, features, index);
1034
+ }
1035
+ }
1036
+ },
1037
+
1038
+ mediaFeature: function () {
1039
+ var nodes = [];
1040
+
1041
+ do {
1042
+ if (e = $(this.entities.keyword)) {
1043
+ nodes.push(e);
1044
+ } else if ($('(')) {
1045
+ p = $(this.property);
1046
+ e = $(this.entity);
1047
+ if ($(')')) {
1048
+ if (p && e) {
1049
+ nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, true)));
1050
+ } else if (e) {
1051
+ nodes.push(new(tree.Paren)(e));
1052
+ } else {
1053
+ return null;
1054
+ }
1055
+ } else { return null }
1056
+ }
1057
+ } while (e);
1058
+
1059
+ if (nodes.length > 0) {
1060
+ return new(tree.Expression)(nodes);
1061
+ }
1062
+ },
1063
+
1064
+ mediaFeatures: function () {
1065
+ var f, features = [];
1066
+ while (f = $(this.mediaFeature)) {
1067
+ features.push(f);
1068
+ if (! $(',')) { break }
1069
+ }
1070
+ return features.length > 0 ? features : null;
1071
+ },
1072
+
1073
+ media: function () {
1074
+ var features;
1075
+
1076
+ if ($(/^@media/)) {
1077
+ features = $(this.mediaFeatures);
1078
+
1079
+ if (rules = $(this.block)) {
1080
+ return new(tree.Directive)('@media', rules, features);
1081
+ }
973
1082
  }
974
1083
  },
975
1084
 
@@ -979,13 +1088,13 @@ less.Parser = function Parser(env) {
979
1088
  // @charset "utf-8";
980
1089
  //
981
1090
  directive: function () {
982
- var name, value, rules, types;
1091
+ var name, value, rules, types, e, nodes;
983
1092
 
984
1093
  if (input.charAt(i) !== '@') return;
985
1094
 
986
- if (value = $(this['import'])) {
1095
+ if (value = $(this['import']) || $(this.media)) {
987
1096
  return value;
988
- } else if (name = $(/^@media|@page/) || $(/^@(?:-webkit-|-moz-|-o-)[a-z0-9-]+/) || $('keyframes')) {
1097
+ } else if (name = $(/^@page|@keyframes/) || $(/^@(?:-webkit-|-moz-|-o-|-ms-)[a-z0-9-]+/)) {
989
1098
  types = ($(/^[^{]+/) || '').trim();
990
1099
  if (rules = $(this.block)) {
991
1100
  return new(tree.Directive)(name + " " + types, rules);
@@ -1052,7 +1161,7 @@ less.Parser = function Parser(env) {
1052
1161
  multiplication: function () {
1053
1162
  var m, a, op, operation;
1054
1163
  if (m = $(this.operand)) {
1055
- while ((op = ($('/') || $('*'))) && (a = $(this.operand))) {
1164
+ while (!peek(/^\/\*/) && (op = ($('/') || $('*'))) && (a = $(this.operand))) {
1056
1165
  operation = new(tree.Operation)(op, [operation || m, a]);
1057
1166
  }
1058
1167
  return operation || m;
@@ -1068,6 +1177,35 @@ less.Parser = function Parser(env) {
1068
1177
  return operation || m;
1069
1178
  }
1070
1179
  },
1180
+ conditions: function () {
1181
+ var a, b, index = i, condition;
1182
+
1183
+ if (a = $(this.condition)) {
1184
+ while ($(',') && (b = $(this.condition))) {
1185
+ condition = new(tree.Condition)('or', condition || a, b, index);
1186
+ }
1187
+ return condition || a;
1188
+ }
1189
+ },
1190
+ condition: function () {
1191
+ var a, b, c, op, index = i, negate = false;
1192
+
1193
+ if ($(/^not/)) { negate = true }
1194
+ expect('(');
1195
+ if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1196
+ if (op = $(/^(?:>=|=<|[<=>])/)) {
1197
+ if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1198
+ c = new(tree.Condition)(op, a, b, index, negate);
1199
+ } else {
1200
+ error('expected expression');
1201
+ }
1202
+ } else {
1203
+ c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
1204
+ }
1205
+ expect(')');
1206
+ return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c;
1207
+ }
1208
+ },
1071
1209
 
1072
1210
  //
1073
1211
  // An operand is anything that can be part of an operation,
@@ -1117,7 +1255,7 @@ if (less.mode === 'browser' || less.mode === 'rhino') {
1117
1255
  // Used by `@import` directives
1118
1256
  //
1119
1257
  less.Parser.importer = function (path, paths, callback, env) {
1120
- if (path.charAt(0) !== '/' && paths.length > 0) {
1258
+ if (!/^([a-z]+:)?\//.test(path) && paths.length > 0) {
1121
1259
  path = paths[0] + path;
1122
1260
  }
1123
1261
  // We pass `true` as 3rd argument, to force the reload of the import.