less 2.1.2 → 2.4.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 (148) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +1 -0
  2. package/.idea/jsLinters/jscs.xml +4 -0
  3. package/.idea/jsLinters/jshint.xml +58 -4
  4. package/.idea/workspace.xml +499 -760
  5. package/.jscsrc +73 -0
  6. package/.jshintrc +0 -2
  7. package/.travis.yml +1 -0
  8. package/CHANGELOG.md +59 -0
  9. package/Gruntfile.js +37 -3
  10. package/appveyor.yml +5 -0
  11. package/bin/lessc +383 -316
  12. package/bower.json +1 -1
  13. package/dist/less.js +1638 -1244
  14. package/dist/less.min.js +7 -7
  15. package/lib/less/contexts.js +3 -3
  16. package/lib/less/data/unit-conversions.js +3 -3
  17. package/lib/less/environment/abstract-file-manager.js +8 -8
  18. package/lib/less/environment/environment.js +2 -2
  19. package/lib/less/functions/color-blending.js +3 -3
  20. package/lib/less/functions/color.js +20 -12
  21. package/lib/less/functions/data-uri.js +26 -18
  22. package/lib/less/functions/math.js +1 -1
  23. package/lib/less/functions/number.js +2 -2
  24. package/lib/less/functions/string.js +3 -1
  25. package/lib/less/functions/svg.js +12 -15
  26. package/lib/less/functions/types.js +33 -10
  27. package/lib/less/import-manager.js +14 -9
  28. package/lib/less/index.js +2 -1
  29. package/lib/less/less-error.js +1 -1
  30. package/lib/less/logger.js +2 -2
  31. package/lib/less/parse-tree.js +48 -42
  32. package/lib/less/parse.js +68 -0
  33. package/lib/less/parser/parser-input.js +6 -5
  34. package/lib/less/parser/parser.js +76 -29
  35. package/lib/less/plugin-manager.js +30 -3
  36. package/lib/less/render.js +11 -37
  37. package/lib/less/source-map-builder.js +4 -0
  38. package/lib/less/source-map-output.js +6 -6
  39. package/lib/less/transform-tree.js +4 -4
  40. package/lib/less/tree/anonymous.js +1 -1
  41. package/lib/less/tree/assignment.js +0 -1
  42. package/lib/less/tree/call.js +1 -1
  43. package/lib/less/tree/color.js +6 -6
  44. package/lib/less/tree/condition.js +9 -5
  45. package/lib/less/tree/debug-info.js +6 -2
  46. package/lib/less/tree/dimension.js +5 -5
  47. package/lib/less/tree/directive.js +25 -6
  48. package/lib/less/tree/element.js +1 -1
  49. package/lib/less/tree/expression.js +2 -2
  50. package/lib/less/tree/extend.js +3 -3
  51. package/lib/less/tree/import.js +2 -2
  52. package/lib/less/tree/index.js +1 -0
  53. package/lib/less/tree/javascript.js +2 -2
  54. package/lib/less/tree/media.js +20 -19
  55. package/lib/less/tree/mixin-call.js +5 -6
  56. package/lib/less/tree/mixin-definition.js +33 -11
  57. package/lib/less/tree/quoted.js +3 -3
  58. package/lib/less/tree/rule.js +3 -4
  59. package/lib/less/tree/ruleset.js +290 -177
  60. package/lib/less/tree/selector.js +3 -2
  61. package/lib/less/tree/unit.js +2 -0
  62. package/lib/less/tree/url.js +5 -2
  63. package/lib/less/tree/value.js +2 -2
  64. package/lib/less/tree/variable.js +1 -1
  65. package/lib/less/visitors/extend-visitor.js +72 -36
  66. package/lib/less/visitors/import-sequencer.js +20 -14
  67. package/lib/less/visitors/import-visitor.js +15 -12
  68. package/lib/less/visitors/to-css-visitor.js +41 -9
  69. package/lib/less-browser/add-default-options.js +5 -1
  70. package/lib/less-browser/bootstrap.js +10 -8
  71. package/lib/less-browser/cache.js +3 -3
  72. package/lib/less-browser/file-manager.js +96 -96
  73. package/lib/less-browser/index.js +206 -205
  74. package/lib/less-browser/log-listener.js +2 -2
  75. package/lib/less-browser/utils.js +6 -6
  76. package/lib/less-node/file-manager.js +58 -30
  77. package/lib/less-node/image-size.js +34 -0
  78. package/lib/less-node/index.js +6 -3
  79. package/lib/less-node/lessc-helper.js +3 -2
  80. package/lib/less-node/plugin-loader.js +3 -3
  81. package/lib/less-node/url-file-manager.js +6 -3
  82. package/lib/less-rhino/index.js +15 -13
  83. package/lib/source-map/source-map-0.1.31.js +1 -1
  84. package/package.json +10 -7
  85. package/test/browser/common.js +13 -11
  86. package/test/browser/css/relative-urls/urls.css +1 -0
  87. package/test/browser/css/urls.css +6 -3
  88. package/test/browser/jasmine-jsreporter.js +2 -3
  89. package/test/browser/less/nested-gradient-with-svg-gradient/mixin-consumer.less +5 -0
  90. package/test/browser/less/nested-gradient-with-svg-gradient/svg-gradient-mixin.less +15 -0
  91. package/test/browser/less/relative-urls/urls.less +1 -0
  92. package/test/browser/less/urls.less +1 -0
  93. package/test/browser/runner-browser-options.js +23 -23
  94. package/test/browser/runner-errors-options.js +0 -1
  95. package/test/browser/runner-errors-spec.js +1 -2
  96. package/test/browser/runner-global-vars-spec.js +1 -1
  97. package/test/browser/runner-legacy-options.js +0 -1
  98. package/test/browser/runner-main-options.js +5 -5
  99. package/test/browser/runner-main-spec.js +1 -1
  100. package/test/browser/runner-no-js-errors-spec.js +0 -1
  101. package/test/browser/runner-production-options.js +0 -1
  102. package/test/browser/runner-relative-urls-options.js +0 -1
  103. package/test/browser/runner-rootpath-options.js +0 -1
  104. package/test/browser/runner-rootpath-relative-options.js +0 -1
  105. package/test/browser/runner-strict-units-options.js +0 -1
  106. package/test/copy-bom.js +72 -0
  107. package/test/css/comments.css +1 -0
  108. package/test/css/css-3.css +2 -0
  109. package/test/css/functions.css +10 -0
  110. package/test/css/import-interpolation.css +7 -0
  111. package/test/css/import-reference.css +25 -0
  112. package/test/css/include-path/include-path.css +6 -0
  113. package/test/css/mixins-important.css +12 -0
  114. package/test/css/selectors.css +15 -1
  115. package/test/css/static-urls/urls.css +1 -0
  116. package/test/css/url-args/urls.css +3 -3
  117. package/test/css/urls.css +13 -4
  118. package/test/css/variables.css +7 -1
  119. package/test/data/image.svg +3 -0
  120. package/test/index.js +15 -10
  121. package/test/less/comments.less +1 -0
  122. package/test/less/css-3.less +3 -0
  123. package/test/less/css-guards.less +2 -1
  124. package/test/less/errors/detached-ruleset-2.txt +1 -1
  125. package/test/less/errors/import-missing.txt +1 -1
  126. package/test/less/errors/parens-error-1.txt +1 -1
  127. package/test/less/errors/parens-error-2.txt +1 -1
  128. package/test/less/errors/parens-error-3.txt +1 -1
  129. package/test/less/errors/single-character.less +1 -0
  130. package/test/less/errors/single-character.txt +2 -0
  131. package/test/less/functions.less +18 -0
  132. package/test/less/import/import-interpolation.less +1 -0
  133. package/test/less/import/import-reference.less +38 -0
  134. package/test/less/import/imports/logo.less +1 -0
  135. package/test/less/import-reference.less +3 -1
  136. package/test/less/import.less +2 -0
  137. package/test/less/include-path/include-path.less +6 -0
  138. package/test/less/mixins-important.less +28 -0
  139. package/test/less/nested-gradient-with-svg-gradient/mixin-consumer.less +5 -0
  140. package/test/less/nested-gradient-with-svg-gradient/svg-gradient-mixin.less +15 -0
  141. package/test/less/selectors.less +29 -4
  142. package/test/less/sourcemaps-empty/empty.less +0 -0
  143. package/test/less/sourcemaps-empty/var-defs.less +1 -0
  144. package/test/less/urls.less +10 -1
  145. package/test/less/variables.less +17 -3
  146. package/test/less-test.js +133 -52
  147. package/test/modify-vars.js +14 -12
  148. package/test/browser/less.js +0 -9473
@@ -1,54 +1,60 @@
1
1
  var LessError = require('./less-error'),
2
- transformTree = require("./transform-tree");
2
+ transformTree = require("./transform-tree"),
3
+ logger = require("./logger");
3
4
 
4
5
  module.exports = function(SourceMapBuilder) {
5
- var ParseTree = function(root, imports) {
6
- this.root = root;
7
- this.imports = imports;
8
- };
6
+ var ParseTree = function(root, imports) {
7
+ this.root = root;
8
+ this.imports = imports;
9
+ };
10
+
11
+ ParseTree.prototype.toCSS = function(options) {
12
+ var evaldRoot, result = {}, sourceMapBuilder;
13
+ try {
14
+ evaldRoot = transformTree(this.root, options);
15
+ } catch (e) {
16
+ throw new LessError(e, this.imports);
17
+ }
9
18
 
10
- ParseTree.prototype.toCSS = function(options) {
11
- var evaldRoot, result = {}, sourceMapBuilder;
12
- try {
13
- evaldRoot = transformTree(this.root, options);
14
- } catch (e) {
15
- throw new LessError(e, this.imports);
16
- }
19
+ try {
20
+ var compress = Boolean(options.compress);
21
+ if (compress) {
22
+ logger.warn("The compress option has been deprecated. We recommend you use a dedicated css minifier, for instance see less-plugin-clean-css.");
23
+ }
17
24
 
18
- try {
19
- var toCSSOptions = {
20
- compress: Boolean(options.compress),
21
- dumpLineNumbers: options.dumpLineNumbers,
22
- strictUnits: Boolean(options.strictUnits),
23
- numPrecision: 8};
25
+ var toCSSOptions = {
26
+ compress: compress,
27
+ dumpLineNumbers: options.dumpLineNumbers,
28
+ strictUnits: Boolean(options.strictUnits),
29
+ numPrecision: 8};
24
30
 
25
- if (options.sourceMap) {
26
- sourceMapBuilder = new SourceMapBuilder(options.sourceMap);
27
- result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);
28
- } else {
29
- result.css = evaldRoot.toCSS(toCSSOptions);
31
+ if (options.sourceMap) {
32
+ sourceMapBuilder = new SourceMapBuilder(options.sourceMap);
33
+ result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);
34
+ } else {
35
+ result.css = evaldRoot.toCSS(toCSSOptions);
36
+ }
37
+ } catch (e) {
38
+ throw new LessError(e, this.imports);
30
39
  }
31
- } catch (e) {
32
- throw new LessError(e, this.imports);
33
- }
34
40
 
35
- if (options.pluginManager) {
36
- var postProcessors = options.pluginManager.getPostProcessors();
37
- for(var i = 0; i < postProcessors.length; i++) {
38
- result.css = postProcessors[i].process(result.css, { sourceMap: sourceMapBuilder, options: options, imports: this.imports });
41
+ if (options.pluginManager) {
42
+ var postProcessors = options.pluginManager.getPostProcessors();
43
+ for (var i = 0; i < postProcessors.length; i++) {
44
+ result.css = postProcessors[i].process(result.css, { sourceMap: sourceMapBuilder, options: options, imports: this.imports });
45
+ }
46
+ }
47
+ if (options.sourceMap) {
48
+ result.map = sourceMapBuilder.getExternalSourceMap();
39
49
  }
40
- }
41
- if (options.sourceMap) {
42
- result.map = sourceMapBuilder.getExternalSourceMap();
43
- }
44
50
 
45
- result.imports = [];
46
- for(var file in this.imports.files) {
47
- if (this.imports.files.hasOwnProperty(file) && file !== this.imports.rootFilename) {
48
- result.imports.push(file);
51
+ result.imports = [];
52
+ for (var file in this.imports.files) {
53
+ if (this.imports.files.hasOwnProperty(file) && file !== this.imports.rootFilename) {
54
+ result.imports.push(file);
55
+ }
49
56
  }
50
- }
51
- return result;
52
- };
53
- return ParseTree;
57
+ return result;
58
+ };
59
+ return ParseTree;
54
60
  };
@@ -0,0 +1,68 @@
1
+ var PromiseConstructor,
2
+ contexts = require("./contexts"),
3
+ Parser = require('./parser/parser'),
4
+ PluginManager = require('./plugin-manager');
5
+
6
+ module.exports = function(environment, ParseTree, ImportManager) {
7
+ var parse = function (input, options, callback) {
8
+ options = options || {};
9
+
10
+ if (typeof options === 'function') {
11
+ callback = options;
12
+ options = {};
13
+ }
14
+
15
+ if (!callback) {
16
+ if (!PromiseConstructor) {
17
+ PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
18
+ }
19
+ var self = this;
20
+ return new PromiseConstructor(function (resolve, reject) {
21
+ parse.call(self, input, options, function(err, output) {
22
+ if (err) {
23
+ reject(err);
24
+ } else {
25
+ resolve(output);
26
+ }
27
+ });
28
+ });
29
+ } else {
30
+ var context,
31
+ rootFileInfo,
32
+ pluginManager = new PluginManager(this);
33
+
34
+ pluginManager.addPlugins(options.plugins);
35
+ options.pluginManager = pluginManager;
36
+
37
+ context = new contexts.Parse(options);
38
+
39
+ if (options.rootFileInfo) {
40
+ rootFileInfo = options.rootFileInfo;
41
+ } else {
42
+ var filename = options.filename || "input";
43
+ var entryPath = filename.replace(/[^\/\\]*$/, "");
44
+ rootFileInfo = {
45
+ filename: filename,
46
+ relativeUrls: context.relativeUrls,
47
+ rootpath: context.rootpath || "",
48
+ currentDirectory: entryPath,
49
+ entryPath: entryPath,
50
+ rootFilename: filename
51
+ };
52
+ // add in a missing trailing slash
53
+ if (rootFileInfo.rootpath && rootFileInfo.rootpath.slice(-1) !== "/") {
54
+ rootFileInfo.rootpath += "/";
55
+ }
56
+ }
57
+
58
+ var imports = new ImportManager(context, rootFileInfo);
59
+
60
+ new Parser(context, imports, rootFileInfo)
61
+ .parse(input, function (e, root) {
62
+ if (e) { return callback(e); }
63
+ callback(null, root, imports, options);
64
+ }, options);
65
+ }
66
+ };
67
+ return parse;
68
+ };
@@ -16,7 +16,8 @@ module.exports = function() {
16
16
  saveStack.push( { current: current, i: parserInput.i, j: j });
17
17
  };
18
18
  parserInput.restore = function(possibleErrorMessage) {
19
- if (parserInput.i > furthest) {
19
+
20
+ if (parserInput.i > furthest || (parserInput.i === furthest && possibleErrorMessage && !furthestPossibleErrorMessage)) {
20
21
  furthest = parserInput.i;
21
22
  furthestPossibleErrorMessage = possibleErrorMessage;
22
23
  }
@@ -72,7 +73,7 @@ module.exports = function() {
72
73
  //
73
74
  skipWhitespace(length);
74
75
 
75
- if(typeof(match) === 'string') {
76
+ if (typeof match === 'string') {
76
77
  return match;
77
78
  } else {
78
79
  return match.length === 1 ? match[0] : match;
@@ -91,7 +92,7 @@ module.exports = function() {
91
92
  }
92
93
 
93
94
  skipWhitespace(m[0].length);
94
- if(typeof m === "string") {
95
+ if (typeof m === "string") {
95
96
  return m;
96
97
  }
97
98
 
@@ -184,7 +185,7 @@ module.exports = function() {
184
185
  // Same as $(), but don't change the state of the parser,
185
186
  // just return the match.
186
187
  parserInput.peek = function(tok) {
187
- if (typeof(tok) === 'string') {
188
+ if (typeof tok === 'string') {
188
189
  return input.charAt(parserInput.i) === tok;
189
190
  } else {
190
191
  return tok.test(current);
@@ -238,7 +239,7 @@ module.exports = function() {
238
239
 
239
240
  parserInput.end = function() {
240
241
  var message,
241
- isFinished = parserInput.i >= input.length - 1;
242
+ isFinished = parserInput.i >= input.length;
242
243
 
243
244
  if (parserInput.i < furthest) {
244
245
  message = furthestPossibleErrorMessage;
@@ -47,7 +47,7 @@ var Parser = function Parser(context, imports, fileInfo) {
47
47
  if (result) {
48
48
  return result;
49
49
  }
50
- error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + parserInput.currentChar() + "'"
50
+ error(msg || (typeof arg === 'string' ? "expected '" + arg + "' got '" + parserInput.currentChar() + "'"
51
51
  : "unexpected token"));
52
52
  }
53
53
 
@@ -92,17 +92,26 @@ var Parser = function Parser(context, imports, fileInfo) {
92
92
  // @param [additionalData] An optional map which can contains vars - a map (key, value) of variables to apply
93
93
  //
94
94
  parse: function (str, callback, additionalData) {
95
- var root, error = null, globalVars, modifyVars, preText = "";
95
+ var root, error = null, globalVars, modifyVars, ignored, preText = "";
96
96
 
97
97
  globalVars = (additionalData && additionalData.globalVars) ? Parser.serializeVars(additionalData.globalVars) + '\n' : '';
98
98
  modifyVars = (additionalData && additionalData.modifyVars) ? '\n' + Parser.serializeVars(additionalData.modifyVars) : '';
99
99
 
100
+ if (context.pluginManager) {
101
+ var preProcessors = context.pluginManager.getPreProcessors();
102
+ for (var i = 0; i < preProcessors.length; i++) {
103
+ str = preProcessors[i].process(str, { context: context, imports: imports, fileInfo: fileInfo });
104
+ }
105
+ }
106
+
100
107
  if (globalVars || (additionalData && additionalData.banner)) {
101
108
  preText = ((additionalData && additionalData.banner) ? additionalData.banner : "") + globalVars;
102
- imports.contentsIgnoredChars[fileInfo.filename] = preText.length;
109
+ ignored = imports.contentsIgnoredChars;
110
+ ignored[fileInfo.filename] = ignored[fileInfo.filename] || 0;
111
+ ignored[fileInfo.filename] += preText.length;
103
112
  }
104
113
 
105
- str = str.replace(/\r\n/g, '\n');
114
+ str = str.replace(/\r\n?/g, '\n');
106
115
  // Remove potential UTF Byte Order Mark
107
116
  str = preText + str.replace(/^\uFEFF/, '') + modifyVars;
108
117
  imports.contents[fileInfo.filename] = str;
@@ -204,7 +213,7 @@ var Parser = function Parser(context, imports, fileInfo) {
204
213
  // Ruleset (Selector '.class', [
205
214
  // Rule ("color", Value ([Expression [Color #fff]]))
206
215
  // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
207
- // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
216
+ // Rule ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
208
217
  // Ruleset (Selector [Element '>', '.child'], [...])
209
218
  // ])
210
219
  //
@@ -231,13 +240,17 @@ var Parser = function Parser(context, imports, fileInfo) {
231
240
  primary: function () {
232
241
  var mixin = this.mixin, root = [], node;
233
242
 
234
- while (!parserInput.finished)
243
+ while (true)
235
244
  {
236
- while(true) {
245
+ while (true) {
237
246
  node = this.comment();
238
247
  if (!node) { break; }
239
248
  root.push(node);
240
249
  }
250
+ // always process comments before deciding if finished
251
+ if (parserInput.finished) {
252
+ break;
253
+ }
241
254
  if (parserInput.peek('}')) {
242
255
  break;
243
256
  }
@@ -328,7 +341,7 @@ var Parser = function Parser(context, imports, fileInfo) {
328
341
 
329
342
  if (nameLC === 'alpha') {
330
343
  alpha = parsers.alpha();
331
- if(alpha) {
344
+ if (alpha) {
332
345
  return alpha;
333
346
  }
334
347
  }
@@ -450,7 +463,9 @@ var Parser = function Parser(context, imports, fileInfo) {
450
463
  var rgb;
451
464
 
452
465
  if (parserInput.currentChar() === '#' && (rgb = parserInput.$re(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) {
453
- var colorCandidateString = rgb.input.match(/^#([\w]+).*/); // strip colons, brackets, whitespaces and other characters that should not definitely be part of color string
466
+ // strip colons, brackets, whitespaces and other characters that should not
467
+ // definitely be part of color string
468
+ var colorCandidateString = rgb.input.match(/^#([\w]+).*/);
454
469
  colorCandidateString = colorCandidateString[1];
455
470
  if (!colorCandidateString.match(/^[A-Fa-f0-9]+$/)) { // verify if candidate consists only of allowed HEX characters
456
471
  error("Invalid HEX color code");
@@ -541,17 +556,27 @@ var Parser = function Parser(context, imports, fileInfo) {
541
556
  elements = null;
542
557
  while (! (option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
543
558
  e = this.element();
544
- if (!e) { break; }
545
- if (elements) { elements.push(e); } else { elements = [ e ]; }
559
+ if (!e) {
560
+ break;
561
+ }
562
+ if (elements) {
563
+ elements.push(e);
564
+ } else {
565
+ elements = [ e ];
566
+ }
546
567
  }
547
568
 
548
569
  option = option && option[1];
549
- if (!elements)
570
+ if (!elements) {
550
571
  error("Missing target selector for :extend().");
572
+ }
551
573
  extend = new(tree.Extend)(new(tree.Selector)(elements), option, index);
552
- if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
553
-
554
- } while(parserInput.$char(","));
574
+ if (extendList) {
575
+ extendList.push(extend);
576
+ } else {
577
+ extendList = [ extend ];
578
+ }
579
+ } while (parserInput.$char(","));
555
580
 
556
581
  expect(/^\)/);
557
582
 
@@ -599,7 +624,11 @@ var Parser = function Parser(context, imports, fileInfo) {
599
624
  break;
600
625
  }
601
626
  elem = new(tree.Element)(c, e, elemIndex, fileInfo);
602
- if (elements) { elements.push(elem); } else { elements = [ elem ]; }
627
+ if (elements) {
628
+ elements.push(elem);
629
+ } else {
630
+ elements = [ elem ];
631
+ }
603
632
  c = parserInput.$char('>');
604
633
  }
605
634
 
@@ -857,8 +886,10 @@ var Parser = function Parser(context, imports, fileInfo) {
857
886
 
858
887
  c = this.combinator();
859
888
 
860
- e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) || parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
861
- parserInput.$char('*') || parserInput.$char('&') || this.attribute() || parserInput.$re(/^\([^()@]+\)/) || parserInput.$re(/^[\.#](?=@)/) ||
889
+ e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) ||
890
+ parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
891
+ parserInput.$char('*') || parserInput.$char('&') || this.attribute() ||
892
+ parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[\.#:](?=@)/) ||
862
893
  this.entities.variableCurly();
863
894
 
864
895
  if (! e) {
@@ -938,11 +969,19 @@ var Parser = function Parser(context, imports, fileInfo) {
938
969
  } else if (condition) {
939
970
  error("CSS guard can only be used at the end of selector");
940
971
  } else if (extendList) {
941
- if (allExtends) { allExtends = allExtends.concat(extendList); } else { allExtends = extendList; }
972
+ if (allExtends) {
973
+ allExtends = allExtends.concat(extendList);
974
+ } else {
975
+ allExtends = extendList;
976
+ }
942
977
  } else {
943
978
  if (allExtends) { error("Extend can only be used at the end of selector"); }
944
979
  c = parserInput.currentChar();
945
- if (elements) { elements.push(e); } else { elements = [ e ]; }
980
+ if (elements) {
981
+ elements.push(e);
982
+ } else {
983
+ elements = [ e ];
984
+ }
946
985
  e = null;
947
986
  }
948
987
  if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
@@ -1017,7 +1056,11 @@ var Parser = function Parser(context, imports, fileInfo) {
1017
1056
  if (!s) {
1018
1057
  break;
1019
1058
  }
1020
- if (selectors) { selectors.push(s); } else { selectors = [ s ]; }
1059
+ if (selectors) {
1060
+ selectors.push(s);
1061
+ } else {
1062
+ selectors = [ s ];
1063
+ }
1021
1064
  parserInput.commentStore.length = 0;
1022
1065
  if (s.condition && selectors.length > 1) {
1023
1066
  error("Guards are only currently allowed on a single selector.");
@@ -1154,11 +1197,11 @@ var Parser = function Parser(context, imports, fileInfo) {
1154
1197
  case "css":
1155
1198
  optionName = "less";
1156
1199
  value = false;
1157
- break;
1200
+ break;
1158
1201
  case "once":
1159
1202
  optionName = "multiple";
1160
1203
  value = false;
1161
- break;
1204
+ break;
1162
1205
  }
1163
1206
  options[optionName] = value;
1164
1207
  if (! parserInput.$char(',')) { break; }
@@ -1169,7 +1212,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1169
1212
  },
1170
1213
 
1171
1214
  importOption: function() {
1172
- var opt = parserInput.$re(/^(less|css|multiple|once|inline|reference)/);
1215
+ var opt = parserInput.$re(/^(less|css|multiple|once|inline|reference|optional)/);
1173
1216
  if (opt) {
1174
1217
  return opt[1];
1175
1218
  }
@@ -1369,7 +1412,7 @@ var Parser = function Parser(context, imports, fileInfo) {
1369
1412
  expressions.push(e);
1370
1413
  if (! parserInput.$char(',')) { break; }
1371
1414
  }
1372
- } while(e);
1415
+ } while (e);
1373
1416
 
1374
1417
  if (expressions.length > 0) {
1375
1418
  return new(tree.Value)(expressions);
@@ -1383,15 +1426,19 @@ var Parser = function Parser(context, imports, fileInfo) {
1383
1426
  sub: function () {
1384
1427
  var a, e;
1385
1428
 
1429
+ parserInput.save();
1386
1430
  if (parserInput.$char('(')) {
1387
1431
  a = this.addition();
1388
- if (a) {
1432
+ if (a && parserInput.$char(')')) {
1433
+ parserInput.forget();
1389
1434
  e = new(tree.Expression)([a]);
1390
- expectChar(')');
1391
1435
  e.parens = true;
1392
1436
  return e;
1393
1437
  }
1438
+ parserInput.restore("Expected ')'");
1439
+ return;
1394
1440
  }
1441
+ parserInput.restore();
1395
1442
  },
1396
1443
  multiplication: function () {
1397
1444
  var m, a, op, operation, isSpaced;
@@ -1598,8 +1645,8 @@ Parser.serializeVars = function(vars) {
1598
1645
  for (var name in vars) {
1599
1646
  if (Object.hasOwnProperty.call(vars, name)) {
1600
1647
  var value = vars[name];
1601
- s += ((name[0] === '@') ? '' : '@') + name +': '+ value +
1602
- ((('' + value).slice(-1) === ';') ? '' : ';');
1648
+ s += ((name[0] === '@') ? '' : '@') + name + ': ' + value +
1649
+ ((String(value).slice(-1) === ';') ? '' : ';');
1603
1650
  }
1604
1651
  }
1605
1652
 
@@ -4,6 +4,7 @@
4
4
  var PluginManager = function(less) {
5
5
  this.less = less;
6
6
  this.visitors = [];
7
+ this.preProcessors = [];
7
8
  this.postProcessors = [];
8
9
  this.installedPlugins = [];
9
10
  this.fileManagers = [];
@@ -14,7 +15,7 @@ var PluginManager = function(less) {
14
15
  */
15
16
  PluginManager.prototype.addPlugins = function(plugins) {
16
17
  if (plugins) {
17
- for(var i = 0;i < plugins.length; i++) {
18
+ for (var i = 0; i < plugins.length; i++) {
18
19
  this.addPlugin(plugins[i]);
19
20
  }
20
21
  }
@@ -35,6 +36,20 @@ PluginManager.prototype.addPlugin = function(plugin) {
35
36
  PluginManager.prototype.addVisitor = function(visitor) {
36
37
  this.visitors.push(visitor);
37
38
  };
39
+ /**
40
+ * Adds a pre processor object
41
+ * @param {object} preProcessor
42
+ * @param {number} priority - guidelines 1 = before import, 1000 = import, 2000 = after import
43
+ */
44
+ PluginManager.prototype.addPreProcessor = function(preProcessor, priority) {
45
+ var indexToInsertAt;
46
+ for (indexToInsertAt = 0; indexToInsertAt < this.preProcessors.length; indexToInsertAt++) {
47
+ if (this.preProcessors[indexToInsertAt].priority >= priority) {
48
+ break;
49
+ }
50
+ }
51
+ this.preProcessors.splice(indexToInsertAt, 0, {preProcessor: preProcessor, priority: priority});
52
+ };
38
53
  /**
39
54
  * Adds a post processor object
40
55
  * @param {object} postProcessor
@@ -42,7 +57,7 @@ PluginManager.prototype.addVisitor = function(visitor) {
42
57
  */
43
58
  PluginManager.prototype.addPostProcessor = function(postProcessor, priority) {
44
59
  var indexToInsertAt;
45
- for(indexToInsertAt = 0; indexToInsertAt < this.postProcessors.length; indexToInsertAt++) {
60
+ for (indexToInsertAt = 0; indexToInsertAt < this.postProcessors.length; indexToInsertAt++) {
46
61
  if (this.postProcessors[indexToInsertAt].priority >= priority) {
47
62
  break;
48
63
  }
@@ -56,6 +71,18 @@ PluginManager.prototype.addPostProcessor = function(postProcessor, priority) {
56
71
  PluginManager.prototype.addFileManager = function(manager) {
57
72
  this.fileManagers.push(manager);
58
73
  };
74
+ /**
75
+ *
76
+ * @returns {Array}
77
+ * @private
78
+ */
79
+ PluginManager.prototype.getPreProcessors = function() {
80
+ var preProcessors = [];
81
+ for (var i = 0; i < this.preProcessors.length; i++) {
82
+ preProcessors.push(this.preProcessors[i].preProcessor);
83
+ }
84
+ return preProcessors;
85
+ };
59
86
  /**
60
87
  *
61
88
  * @returns {Array}
@@ -63,7 +90,7 @@ PluginManager.prototype.addFileManager = function(manager) {
63
90
  */
64
91
  PluginManager.prototype.getPostProcessors = function() {
65
92
  var postProcessors = [];
66
- for(var i = 0; i < this.postProcessors.length; i++) {
93
+ for (var i = 0; i < this.postProcessors.length; i++) {
67
94
  postProcessors.push(this.postProcessors[i].postProcessor);
68
95
  }
69
96
  return postProcessors;
@@ -1,18 +1,16 @@
1
- var PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
2
- contexts = require("./contexts"),
3
- Parser = require('./parser/parser'),
4
- PluginManager = require('./plugin-manager');
1
+ var PromiseConstructor;
5
2
 
6
3
  module.exports = function(environment, ParseTree, ImportManager) {
7
4
  var render = function (input, options, callback) {
8
- options = options || {};
9
-
10
- if (typeof(options) === 'function') {
5
+ if (typeof options === 'function') {
11
6
  callback = options;
12
7
  options = {};
13
8
  }
14
9
 
15
10
  if (!callback) {
11
+ if (!PromiseConstructor) {
12
+ PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
13
+ }
16
14
  var self = this;
17
15
  return new PromiseConstructor(function (resolve, reject) {
18
16
  render.call(self, input, options, function(err, output) {
@@ -24,44 +22,20 @@ module.exports = function(environment, ParseTree, ImportManager) {
24
22
  });
25
23
  });
26
24
  } else {
27
- var context,
28
- rootFileInfo,
29
- pluginManager = new PluginManager(this);
30
-
31
- pluginManager.addPlugins(options.plugins);
32
- options.pluginManager = pluginManager;
33
-
34
- context = new contexts.Parse(options);
35
-
36
- if (options.rootFileInfo) {
37
- rootFileInfo = options.rootFileInfo;
38
- } else {
39
- var filename = options.filename || "input";
40
- var entryPath = filename.replace(/[^\/\\]*$/, "");
41
- rootFileInfo = {
42
- filename: filename,
43
- relativeUrls: context.relativeUrls,
44
- rootpath: context.rootpath || "",
45
- currentDirectory: entryPath,
46
- entryPath: entryPath,
47
- rootFilename: filename
48
- };
49
- }
50
-
51
- var imports = new ImportManager(context, rootFileInfo);
25
+ this.parse(input, options, function(err, root, imports, options) {
26
+ if (err) { return callback(err); }
52
27
 
53
- new Parser(context, imports, rootFileInfo)
54
- .parse(input, function (e, root) {
55
- if (e) { return callback(e); }
56
28
  var result;
57
29
  try {
58
30
  var parseTree = new ParseTree(root, imports);
59
31
  result = parseTree.toCSS(options);
60
32
  }
61
- catch (err) { return callback( err); }
33
+ catch (err) { return callback(err); }
34
+
62
35
  callback(null, result);
63
- }, options);
36
+ });
64
37
  }
65
38
  };
39
+
66
40
  return render;
67
41
  };
@@ -30,8 +30,12 @@ module.exports = function (SourceMapOutput, environment) {
30
30
  };
31
31
 
32
32
  SourceMapBuilder.prototype.getCSSAppendage = function() {
33
+
33
34
  var sourceMapURL = this.sourceMapURL;
34
35
  if (this.options.sourceMapFileInline) {
36
+ if (this.sourceMap === undefined) {
37
+ return "";
38
+ }
35
39
  sourceMapURL = "data:application/json;base64," + environment.encodeBase64(this.sourceMap);
36
40
  }
37
41
 
@@ -15,7 +15,7 @@ module.exports = function (environment) {
15
15
  }
16
16
  if (options.sourceMapRootpath) {
17
17
  this._sourceMapRootpath = options.sourceMapRootpath.replace(/\\/g, '/');
18
- if (this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') {
18
+ if (this._sourceMapRootpath.charAt(this._sourceMapRootpath.length - 1) !== '/') {
19
19
  this._sourceMapRootpath += '/';
20
20
  }
21
21
  } else {
@@ -34,7 +34,7 @@ module.exports = function (environment) {
34
34
  if (this._sourceMapBasepath && filename.indexOf(this._sourceMapBasepath) === 0) {
35
35
  filename = filename.substring(this._sourceMapBasepath.length);
36
36
  if (filename.charAt(0) === '\\' || filename.charAt(0) === '/') {
37
- filename = filename.substring(1);
37
+ filename = filename.substring(1);
38
38
  }
39
39
  }
40
40
  return (this._sourceMapRootpath || "") + filename;
@@ -66,11 +66,11 @@ module.exports = function (environment) {
66
66
  }
67
67
  inputSource = inputSource.substring(0, index);
68
68
  sourceLines = inputSource.split("\n");
69
- sourceColumns = sourceLines[sourceLines.length-1];
69
+ sourceColumns = sourceLines[sourceLines.length - 1];
70
70
  }
71
71
 
72
72
  lines = chunk.split("\n");
73
- columns = lines[lines.length-1];
73
+ columns = lines[lines.length - 1];
74
74
 
75
75
  if (fileInfo) {
76
76
  if (!mapLines) {
@@ -78,7 +78,7 @@ module.exports = function (environment) {
78
78
  original: { line: sourceLines.length, column: sourceColumns.length},
79
79
  source: this.normalizeFilename(fileInfo.filename)});
80
80
  } else {
81
- for(i = 0; i < lines.length; i++) {
81
+ for (i = 0; i < lines.length; i++) {
82
82
  this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + i + 1, column: i === 0 ? this._column : 0},
83
83
  original: { line: sourceLines.length + i, column: i === 0 ? sourceColumns.length : 0},
84
84
  source: this.normalizeFilename(fileInfo.filename)});
@@ -104,7 +104,7 @@ module.exports = function (environment) {
104
104
  this._sourceMapGenerator = new this._sourceMapGeneratorConstructor({ file: this._outputFilename, sourceRoot: null });
105
105
 
106
106
  if (this._outputSourceFiles) {
107
- for(var filename in this._contentsMap) {
107
+ for (var filename in this._contentsMap) {
108
108
  if (this._contentsMap.hasOwnProperty(filename))
109
109
  {
110
110
  var source = this._contentsMap[filename];