less 2.3.3 → 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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +5 -0
  3. data/less.gemspec +1 -1
  4. data/lib/less/js/.gitattributes +9 -0
  5. data/lib/less/js/.gitignore +1 -0
  6. data/lib/less/js/.npmignore +1 -1
  7. data/lib/less/js/CHANGELOG.md +68 -0
  8. data/lib/less/js/CONTRIBUTING.md +33 -34
  9. data/lib/less/js/Makefile +24 -9
  10. data/lib/less/js/README.md +2 -2
  11. data/lib/less/js/bin/lessc +102 -25
  12. data/lib/less/js/build/amd.js +1 -1
  13. data/lib/less/js/build/header.js +9 -7
  14. data/lib/less/js/dist/less-1.3.3.js +2 -2
  15. data/lib/less/js/dist/less-1.3.3.min.js +2 -2
  16. data/lib/less/js/dist/less-1.4.0-beta.js +5830 -0
  17. data/lib/less/js/dist/less-1.4.0-beta.min.js +11 -0
  18. data/lib/less/js/dist/less-1.4.0.js +5830 -0
  19. data/lib/less/js/dist/less-1.4.0.min.js +11 -0
  20. data/lib/less/js/dist/less-1.4.1.js +5837 -0
  21. data/lib/less/js/dist/less-1.4.1.min.js +11 -0
  22. data/lib/less/js/dist/less-1.4.2.js +5837 -0
  23. data/lib/less/js/dist/less-1.4.2.min.js +11 -0
  24. data/lib/less/js/dist/less-rhino-1.4.0.js +4273 -0
  25. data/lib/less/js/lib/less/browser.js +131 -101
  26. data/lib/less/js/lib/less/env.js +105 -0
  27. data/lib/less/js/lib/less/extend-visitor.js +391 -0
  28. data/lib/less/js/lib/less/functions.js +174 -19
  29. data/lib/less/js/lib/less/import-visitor.js +107 -0
  30. data/lib/less/js/lib/less/index.js +70 -63
  31. data/lib/less/js/lib/less/join-selector-visitor.js +37 -0
  32. data/lib/less/js/lib/less/lessc_helper.js +13 -4
  33. data/lib/less/js/lib/less/parser.js +353 -264
  34. data/lib/less/js/lib/less/rhino.js +5 -2
  35. data/lib/less/js/lib/less/tree.js +1 -1
  36. data/lib/less/js/lib/less/tree/alpha.js +7 -3
  37. data/lib/less/js/lib/less/tree/anonymous.js +1 -0
  38. data/lib/less/js/lib/less/tree/assignment.js +4 -0
  39. data/lib/less/js/lib/less/tree/call.js +14 -8
  40. data/lib/less/js/lib/less/tree/color.js +50 -5
  41. data/lib/less/js/lib/less/tree/comment.js +1 -0
  42. data/lib/less/js/lib/less/tree/condition.js +35 -28
  43. data/lib/less/js/lib/less/tree/dimension.js +270 -16
  44. data/lib/less/js/lib/less/tree/directive.js +7 -2
  45. data/lib/less/js/lib/less/tree/element.js +57 -21
  46. data/lib/less/js/lib/less/tree/expression.js +29 -4
  47. data/lib/less/js/lib/less/tree/extend.js +43 -0
  48. data/lib/less/js/lib/less/tree/import.js +49 -28
  49. data/lib/less/js/lib/less/tree/javascript.js +1 -0
  50. data/lib/less/js/lib/less/tree/keyword.js +3 -2
  51. data/lib/less/js/lib/less/tree/media.js +20 -4
  52. data/lib/less/js/lib/less/tree/mixin.js +38 -18
  53. data/lib/less/js/lib/less/tree/negative.js +22 -0
  54. data/lib/less/js/lib/less/tree/operation.js +32 -17
  55. data/lib/less/js/lib/less/tree/paren.js +5 -1
  56. data/lib/less/js/lib/less/tree/quoted.js +5 -3
  57. data/lib/less/js/lib/less/tree/rule.js +44 -31
  58. data/lib/less/js/lib/less/tree/ruleset.js +50 -23
  59. data/lib/less/js/lib/less/tree/selector.js +49 -39
  60. data/lib/less/js/lib/less/tree/unicode-descriptor.js +1 -0
  61. data/lib/less/js/lib/less/tree/url.js +9 -5
  62. data/lib/less/js/lib/less/tree/value.js +4 -1
  63. data/lib/less/js/lib/less/tree/variable.js +4 -3
  64. data/lib/less/js/lib/less/visitor.js +54 -0
  65. data/lib/less/js/package.json +69 -19
  66. data/lib/less/js/test/browser-test-prepare.js +23 -6
  67. data/lib/less/js/test/browser/common.js +55 -3
  68. data/lib/less/js/test/browser/css/urls.css +13 -0
  69. data/lib/less/js/test/browser/less/relative-urls/urls.less +1 -1
  70. data/lib/less/js/test/browser/less/urls.less +16 -0
  71. data/lib/less/js/test/browser/phantom-runner.js +7 -5
  72. data/lib/less/js/test/browser/runner-browser.js +5 -1
  73. data/lib/less/js/test/browser/runner-errors.js +5 -0
  74. data/lib/less/js/test/browser/runner-legacy.js +6 -0
  75. data/lib/less/js/test/browser/runner-production.js +7 -0
  76. data/lib/less/js/test/browser/template.htm +6 -6
  77. data/lib/less/js/test/css/comments.css +1 -0
  78. data/lib/less/js/test/css/compression/compression.css +2 -0
  79. data/lib/less/js/test/css/css-3.css +4 -0
  80. data/lib/less/js/test/css/css.css +9 -3
  81. data/lib/less/js/test/css/extend-chaining.css +72 -0
  82. data/lib/less/js/test/css/extend-clearfix.css +19 -0
  83. data/lib/less/js/test/css/extend-exact.css +37 -0
  84. data/lib/less/js/test/css/extend-media.css +24 -0
  85. data/lib/less/js/test/css/extend-nest.css +57 -0
  86. data/lib/less/js/test/css/extend-selector.css +72 -0
  87. data/lib/less/js/test/css/extend.css +76 -0
  88. data/lib/less/js/test/css/functions.css +28 -0
  89. data/lib/less/js/test/css/import-interpolation.css +6 -0
  90. data/lib/less/js/test/css/import.css +18 -1
  91. data/lib/less/js/test/css/legacy/legacy.css +7 -0
  92. data/lib/less/js/test/css/media.css +9 -1
  93. data/lib/less/js/test/css/mixins-args.css +18 -0
  94. data/lib/less/js/test/css/mixins-guards.css +5 -0
  95. data/lib/less/js/test/css/parens.css +18 -5
  96. data/lib/less/js/test/css/selectors.css +14 -6
  97. data/lib/less/js/test/css/urls.css +17 -0
  98. data/lib/less/js/test/css/variables.css +22 -3
  99. data/lib/less/js/test/data/data-uri-fail.png +0 -0
  100. data/lib/less/js/test/data/image.jpg +0 -0
  101. data/lib/less/js/test/data/page.html +1 -0
  102. data/lib/less/js/test/less-test.js +41 -9
  103. data/lib/less/js/test/less/colors.less +4 -4
  104. data/lib/less/js/test/less/comments.less +2 -2
  105. data/lib/less/js/test/less/compression/compression.less +16 -0
  106. data/lib/less/js/test/less/css-3.less +5 -1
  107. data/lib/less/js/test/less/css.less +9 -3
  108. data/lib/less/js/test/less/errors/add-mixed-units.less +3 -0
  109. data/lib/less/js/test/less/errors/add-mixed-units.txt +2 -0
  110. data/lib/less/js/test/less/errors/add-mixed-units2.less +3 -0
  111. data/lib/less/js/test/less/errors/add-mixed-units2.txt +2 -0
  112. data/lib/less/js/test/less/errors/bad-variable-declaration1.txt +1 -1
  113. data/lib/less/js/test/less/errors/color-operation-error.less +3 -0
  114. data/lib/less/js/test/less/errors/color-operation-error.txt +2 -0
  115. data/lib/less/js/test/less/errors/comment-in-selector.txt +1 -1
  116. data/lib/less/js/test/less/errors/divide-mixed-units.less +3 -0
  117. data/lib/less/js/test/less/errors/divide-mixed-units.txt +4 -0
  118. data/lib/less/js/test/less/errors/extend-no-selector.less +3 -0
  119. data/lib/less/js/test/less/errors/extend-no-selector.txt +3 -0
  120. data/lib/less/js/test/less/errors/extend-not-at-end.less +3 -0
  121. data/lib/less/js/test/less/errors/extend-not-at-end.txt +3 -0
  122. data/lib/less/js/test/less/errors/import-missing.less +5 -0
  123. data/lib/less/js/test/less/errors/import-missing.txt +3 -3
  124. data/lib/less/js/test/less/errors/import-no-semi.txt +1 -1
  125. data/lib/less/js/test/less/errors/import-subfolder1.txt +1 -1
  126. data/lib/less/js/test/less/errors/import-subfolder2.txt +1 -1
  127. data/lib/less/js/test/less/errors/javascript-error.txt +1 -1
  128. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-1.txt +1 -1
  129. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-2.txt +1 -1
  130. data/lib/less/js/test/less/errors/mixin-not-defined.txt +1 -1
  131. data/lib/less/js/test/less/errors/mixin-not-matched.txt +1 -1
  132. data/lib/less/js/test/less/errors/mixin-not-matched2.txt +1 -1
  133. data/lib/less/js/test/less/errors/multiply-mixed-units.less +7 -0
  134. data/lib/less/js/test/less/errors/multiply-mixed-units.txt +4 -0
  135. data/lib/less/js/test/less/errors/parens-error-1.less +3 -0
  136. data/lib/less/js/test/less/errors/parens-error-1.txt +4 -0
  137. data/lib/less/js/test/less/errors/parens-error-2.less +3 -0
  138. data/lib/less/js/test/less/errors/parens-error-2.txt +4 -0
  139. data/lib/less/js/test/less/errors/parens-error-3.less +3 -0
  140. data/lib/less/js/test/less/errors/parens-error-3.txt +4 -0
  141. data/lib/less/js/test/less/errors/parse-error-curly-bracket.txt +1 -1
  142. data/lib/less/js/test/less/errors/parse-error-missing-bracket.txt +2 -1
  143. data/lib/less/js/test/less/errors/parse-error-with-import.txt +1 -1
  144. data/lib/less/js/test/less/errors/property-ie5-hack.txt +1 -1
  145. data/lib/less/js/test/less/errors/property-in-root.less +4 -0
  146. data/lib/less/js/test/less/errors/property-in-root.txt +4 -0
  147. data/lib/less/js/test/less/errors/property-in-root2.less +1 -0
  148. data/lib/less/js/test/less/errors/property-in-root2.txt +4 -0
  149. data/lib/less/js/test/less/errors/property-in-root3.less +4 -0
  150. data/lib/less/js/test/less/errors/property-in-root3.txt +3 -0
  151. data/lib/less/js/test/less/errors/recursive-variable.txt +1 -1
  152. data/lib/less/js/test/less/extend-chaining.less +79 -0
  153. data/lib/less/js/test/less/extend-clearfix.less +19 -0
  154. data/lib/less/js/test/less/extend-exact.less +46 -0
  155. data/lib/less/js/test/less/extend-media.less +24 -0
  156. data/lib/less/js/test/less/extend-nest.less +65 -0
  157. data/lib/less/js/test/less/extend-selector.less +84 -0
  158. data/lib/less/js/test/less/extend.less +81 -0
  159. data/lib/less/js/test/less/functions.less +37 -6
  160. data/lib/less/js/test/less/import-interpolation.less +8 -0
  161. data/lib/less/js/test/less/import-once.less +4 -4
  162. data/lib/less/js/test/less/import.less +11 -2
  163. data/lib/less/js/test/less/import/deeper/import-once-test-a.less +1 -1
  164. data/lib/less/js/test/less/import/import-interpolation.less +1 -0
  165. data/lib/less/js/test/less/import/import-interpolation2.less +5 -0
  166. data/lib/less/js/test/less/javascript.less +1 -1
  167. data/lib/less/js/test/less/legacy/legacy.less +7 -0
  168. data/lib/less/js/test/less/media.less +14 -3
  169. data/lib/less/js/test/less/mixins-args.less +43 -5
  170. data/lib/less/js/test/less/mixins-guards.less +13 -0
  171. data/lib/less/js/test/less/mixins-named-args.less +5 -5
  172. data/lib/less/js/test/less/mixins-nested.less +2 -2
  173. data/lib/less/js/test/less/mixins-pattern.less +1 -1
  174. data/lib/less/js/test/less/mixins.less +1 -1
  175. data/lib/less/js/test/less/operations.less +27 -27
  176. data/lib/less/js/test/less/parens.less +20 -5
  177. data/lib/less/js/test/less/selectors.less +14 -7
  178. data/lib/less/js/test/less/urls.less +24 -0
  179. data/lib/less/js/test/less/variables.less +42 -12
  180. data/lib/less/loader.rb +33 -0
  181. data/lib/less/version.rb +1 -1
  182. data/spec/less/parser_spec.rb +5 -5
  183. metadata +76 -6
  184. data/lib/less/js/build/ecma-5.js +0 -120
  185. data/lib/less/js/lib/less/tree/ratio.js +0 -13
@@ -0,0 +1,107 @@
1
+ (function (tree) {
2
+ tree.importVisitor = function(importer, finish, evalEnv) {
3
+ this._visitor = new tree.visitor(this);
4
+ this._importer = importer;
5
+ this._finish = finish;
6
+ this.env = evalEnv || new tree.evalEnv();
7
+ this.importCount = 0;
8
+ };
9
+
10
+ tree.importVisitor.prototype = {
11
+ isReplacing: true,
12
+ run: function (root) {
13
+ var error;
14
+ try {
15
+ // process the contents
16
+ this._visitor.visit(root);
17
+ }
18
+ catch(e) {
19
+ error = e;
20
+ }
21
+
22
+ this.isFinished = true;
23
+
24
+ if (this.importCount === 0) {
25
+ this._finish(error);
26
+ }
27
+ },
28
+ visitImport: function (importNode, visitArgs) {
29
+ var importVisitor = this,
30
+ evaldImportNode;
31
+
32
+ if (!importNode.css) {
33
+
34
+ try {
35
+ evaldImportNode = importNode.evalForImport(this.env);
36
+ } catch(e){
37
+ if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
38
+ // attempt to eval properly and treat as css
39
+ importNode.css = true;
40
+ // if that fails, this error will be thrown
41
+ importNode.error = e;
42
+ }
43
+
44
+ if (evaldImportNode && !evaldImportNode.css) {
45
+ importNode = evaldImportNode;
46
+ this.importCount++;
47
+ var env = new tree.evalEnv(this.env, this.env.frames.slice(0));
48
+ this._importer.push(importNode.getPath(), importNode.currentFileInfo, function (e, root, imported) {
49
+ if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
50
+ if (imported && !importNode.options.multiple) { importNode.skip = imported; }
51
+
52
+ var subFinish = function(e) {
53
+ importVisitor.importCount--;
54
+
55
+ if (importVisitor.importCount === 0 && importVisitor.isFinished) {
56
+ importVisitor._finish(e);
57
+ }
58
+ };
59
+
60
+ if (root) {
61
+ importNode.root = root;
62
+ new(tree.importVisitor)(importVisitor._importer, subFinish, env)
63
+ .run(root);
64
+ } else {
65
+ subFinish();
66
+ }
67
+ });
68
+ }
69
+ }
70
+ visitArgs.visitDeeper = false;
71
+ return importNode;
72
+ },
73
+ visitRule: function (ruleNode, visitArgs) {
74
+ visitArgs.visitDeeper = false;
75
+ return ruleNode;
76
+ },
77
+ visitDirective: function (directiveNode, visitArgs) {
78
+ this.env.frames.unshift(directiveNode);
79
+ return directiveNode;
80
+ },
81
+ visitDirectiveOut: function (directiveNode) {
82
+ this.env.frames.shift();
83
+ },
84
+ visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
85
+ this.env.frames.unshift(mixinDefinitionNode);
86
+ return mixinDefinitionNode;
87
+ },
88
+ visitMixinDefinitionOut: function (mixinDefinitionNode) {
89
+ this.env.frames.shift();
90
+ },
91
+ visitRuleset: function (rulesetNode, visitArgs) {
92
+ this.env.frames.unshift(rulesetNode);
93
+ return rulesetNode;
94
+ },
95
+ visitRulesetOut: function (rulesetNode) {
96
+ this.env.frames.shift();
97
+ },
98
+ visitMedia: function (mediaNode, visitArgs) {
99
+ this.env.frames.unshift(mediaNode.ruleset);
100
+ return mediaNode;
101
+ },
102
+ visitMediaOut: function (mediaNode) {
103
+ this.env.frames.shift();
104
+ }
105
+ };
106
+
107
+ })(require('./tree'));
@@ -1,11 +1,11 @@
1
1
  var path = require('path'),
2
2
  sys = require('util'),
3
3
  url = require('url'),
4
- http = require('http'),
4
+ request,
5
5
  fs = require('fs');
6
6
 
7
7
  var less = {
8
- version: [1, 3, 3],
8
+ version: [1, 4, 2],
9
9
  Parser: require('./parser').Parser,
10
10
  importer: require('./parser').importer,
11
11
  tree: require('./tree'),
@@ -54,10 +54,14 @@ var less = {
54
54
  error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
55
55
  }
56
56
 
57
- if (extract[1]) {
58
- error.push(ctx.line + ' ' + extract[1].slice(0, ctx.column)
59
- + stylize(stylize(stylize(extract[1][ctx.column], 'bold')
60
- + extract[1].slice(ctx.column + 1), 'red'), 'inverse'));
57
+ if (typeof(extract[1]) === 'string') {
58
+ var errorTxt = ctx.line + ' ';
59
+ if (extract[1]) {
60
+ errorTxt += extract[1].slice(0, ctx.column) +
61
+ stylize(stylize(stylize(extract[1][ctx.column], 'bold') +
62
+ extract[1].slice(ctx.column + 1), 'red'), 'inverse');
63
+ }
64
+ error.push(errorTxt);
61
65
  }
62
66
 
63
67
  if (typeof(extract[2]) === 'string') {
@@ -67,7 +71,7 @@ var less = {
67
71
 
68
72
  message += stylize(ctx.type + 'Error: ' + ctx.message, 'red');
69
73
  ctx.filename && (message += stylize(' in ', 'red') + ctx.filename +
70
- stylize(':' + ctx.line + ':' + ctx.column, 'grey'));
74
+ stylize(' on line ' + ctx.line + ', column ' + (ctx.column + 1) + ':', 'grey'));
71
75
 
72
76
  message += '\n' + error;
73
77
 
@@ -85,13 +89,13 @@ var less = {
85
89
  }
86
90
  };
87
91
 
88
- ['color', 'directive', 'operation', 'dimension',
89
- 'keyword', 'variable', 'ruleset', 'element',
90
- 'selector', 'quoted', 'expression', 'rule',
91
- 'call', 'url', 'alpha', 'import',
92
- 'mixin', 'comment', 'anonymous', 'value',
93
- 'javascript', 'assignment', 'condition', 'paren',
94
- 'media', 'ratio', 'unicode-descriptor'
92
+ ['color', 'directive', 'operation', 'dimension',
93
+ 'keyword', 'variable', 'ruleset', 'element',
94
+ 'selector', 'quoted', 'expression', 'rule',
95
+ 'call', 'url', 'alpha', 'import',
96
+ 'mixin', 'comment', 'anonymous', 'value',
97
+ 'javascript', 'assignment', 'condition', 'paren',
98
+ 'media', 'unicode-descriptor', 'negative', 'extend'
95
99
  ].forEach(function (n) {
96
100
  require('./tree/' + n);
97
101
  });
@@ -99,14 +103,22 @@ var less = {
99
103
 
100
104
  var isUrlRe = /^(?:https?:)?\/\//i;
101
105
 
102
- less.Parser.importer = function (file, paths, callback, env) {
103
- var pathname, dirname, data;
106
+ less.Parser.importer = function (file, currentFileInfo, callback, env) {
107
+ var pathname, dirname, data,
108
+ newFileInfo = {
109
+ relativeUrls: env.relativeUrls,
110
+ entryPath: currentFileInfo.entryPath,
111
+ rootpath: currentFileInfo.rootpath,
112
+ rootFilename: currentFileInfo.rootFilename
113
+ };
104
114
 
105
115
  function parseFile(e, data) {
106
- if (e) return callback(e);
116
+ if (e) { return callback(e); }
117
+
118
+ env = new less.tree.parseEnv(env);
119
+ env.processImports = false;
107
120
 
108
- var rootpath = env.rootpath,
109
- j = file.lastIndexOf('/');
121
+ var j = file.lastIndexOf('/');
110
122
 
111
123
  // Pass on an updated rootpath if path of imported file is relative and file
112
124
  // is in a (sub|sup) directory
@@ -116,29 +128,32 @@ less.Parser.importer = function (file, paths, callback, env) {
116
128
  // then rootpath should become 'less/module/nav/'
117
129
  // - If path of imported file is '../mixins.less' and rootpath is 'less/',
118
130
  // then rootpath should become 'less/../'
119
- if(env.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) {
120
- rootpath = rootpath + file.slice(0, j+1); // append (sub|sup) directory path of imported file
131
+ if(newFileInfo.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) {
132
+ var relativeSubDirectory = file.slice(0, j+1);
133
+ newFileInfo.rootpath = newFileInfo.rootpath + relativeSubDirectory; // append (sub|sup) directory path of imported file
121
134
  }
135
+ newFileInfo.currentDirectory = pathname.replace(/[^\\\/]*$/, "");
136
+ newFileInfo.filename = pathname;
122
137
 
123
138
  env.contents[pathname] = data; // Updating top importing parser content cache.
124
- new(less.Parser)({
125
- paths: [dirname].concat(paths),
126
- filename: pathname,
127
- contents: env.contents,
128
- files: env.files,
129
- syncImport: env.syncImport,
130
- relativeUrls: env.relativeUrls,
131
- rootpath: rootpath,
132
- dumpLineNumbers: env.dumpLineNumbers
133
- }).parse(data, function (e, root) {
139
+ env.currentFileInfo = newFileInfo;
140
+ new(less.Parser)(env).parse(data, function (e, root) {
134
141
  callback(e, root, pathname);
135
142
  });
136
143
  };
137
144
 
138
145
  var isUrl = isUrlRe.test( file );
139
- if (isUrl || isUrlRe.test(paths[0])) {
146
+ if (isUrl || isUrlRe.test(currentFileInfo.currentDirectory)) {
147
+ if (request === undefined) {
148
+ try { request = require('request'); }
149
+ catch(e) { request = null; }
150
+ }
151
+ if (!request) {
152
+ callback({ type: 'File', message: "optional dependency 'request' required to import over http(s)\n" });
153
+ return;
154
+ }
140
155
 
141
- var urlStr = isUrl ? file : url.resolve(paths[0], file),
156
+ var urlStr = isUrl ? file : url.resolve(currentFileInfo.currentDirectory, file),
142
157
  urlObj = url.parse(urlStr),
143
158
  req = {
144
159
  host: urlObj.hostname,
@@ -146,31 +161,24 @@ less.Parser.importer = function (file, paths, callback, env) {
146
161
  path: urlObj.pathname + (urlObj.search||'')
147
162
  };
148
163
 
149
- http.get(req, function (res) {
150
- var body = '';
151
- res.on('data', function (chunk) {
152
- body += chunk.toString();
153
- });
154
- res.on('end', function () {
155
- if (res.statusCode === 404) {
156
- callback({ type: 'File', message: "resource '" + urlStr + "' was not found\n" });
157
- }
158
- if (!body) {
159
- sys.error( 'Warning: Empty body (HTTP '+ res.statusCode + ') returned by "' + urlStr +'"' );
160
- }
161
- pathname = urlStr;
162
- dirname = urlObj.protocol +'//'+ urlObj.host + urlObj.pathname.replace(/[^\/]*$/, '');
163
- parseFile(null, body);
164
- });
165
- }).on('error', function (err) {
166
- callback({ type: 'File', message: "resource '" + urlStr + "' gave this Error:\n "+ err +"\n" });
164
+ request.get(urlStr, function (error, res, body) {
165
+ if (res.statusCode === 404) {
166
+ callback({ type: 'File', message: "resource '" + urlStr + "' was not found\n" });
167
+ return;
168
+ }
169
+ if (!body) {
170
+ sys.error( 'Warning: Empty body (HTTP '+ res.statusCode + ') returned by "' + urlStr +'"' );
171
+ }
172
+ if (error) {
173
+ callback({ type: 'File', message: "resource '" + urlStr + "' gave this Error:\n "+ error +"\n" });
174
+ }
175
+ pathname = urlStr;
176
+ dirname = urlObj.protocol +'//'+ urlObj.host + urlObj.pathname.replace(/[^\/]*$/, '');
177
+ parseFile(null, body);
167
178
  });
168
-
169
179
  } else {
170
180
 
171
- // TODO: Undo this at some point,
172
- // or use different approach.
173
- var paths = [].concat(paths);
181
+ var paths = [currentFileInfo.currentDirectory].concat(env.paths);
174
182
  paths.push('.');
175
183
 
176
184
  for (var i = 0; i < paths.length; i++) {
@@ -183,15 +191,9 @@ less.Parser.importer = function (file, paths, callback, env) {
183
191
  }
184
192
  }
185
193
 
186
- paths = paths.slice(0, paths.length - 1);
187
-
188
194
  if (!pathname) {
189
195
 
190
- if (typeof(env.errback) === "function") {
191
- env.errback(file, paths, callback);
192
- } else {
193
- callback({ type: 'File', message: "'" + file + "' wasn't found.\n" });
194
- }
196
+ callback({ type: 'File', message: "'" + file + "' wasn't found" });
195
197
  return;
196
198
  }
197
199
 
@@ -210,7 +212,12 @@ less.Parser.importer = function (file, paths, callback, env) {
210
212
  }
211
213
  }
212
214
 
215
+ require('./env');
213
216
  require('./functions');
214
217
  require('./colors');
218
+ require('./visitor.js');
219
+ require('./import-visitor.js');
220
+ require('./extend-visitor.js');
221
+ require('./join-selector-visitor.js');
215
222
 
216
- for (var k in less) { exports[k] = less[k] }
223
+ for (var k in less) { exports[k] = less[k]; }
@@ -0,0 +1,37 @@
1
+ (function (tree) {
2
+ tree.joinSelectorVisitor = function() {
3
+ this.contexts = [[]];
4
+ this._visitor = new tree.visitor(this);
5
+ };
6
+
7
+ tree.joinSelectorVisitor.prototype = {
8
+ run: function (root) {
9
+ return this._visitor.visit(root);
10
+ },
11
+ visitRule: function (ruleNode, visitArgs) {
12
+ visitArgs.visitDeeper = false;
13
+ },
14
+ visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
15
+ visitArgs.visitDeeper = false;
16
+ },
17
+
18
+ visitRuleset: function (rulesetNode, visitArgs) {
19
+ var context = this.contexts[this.contexts.length - 1];
20
+ var paths = [];
21
+ this.contexts.push(paths);
22
+
23
+ if (! rulesetNode.root) {
24
+ rulesetNode.joinSelectors(paths, context, rulesetNode.selectors);
25
+ rulesetNode.paths = paths;
26
+ }
27
+ },
28
+ visitRulesetOut: function (rulesetNode) {
29
+ this.contexts.length = this.contexts.length - 1;
30
+ },
31
+ visitMedia: function (mediaNode, visitArgs) {
32
+ var context = this.contexts[this.contexts.length - 1];
33
+ mediaNode.ruleset.root = (context.length === 0 || context[0].multiMedia);
34
+ }
35
+ };
36
+
37
+ })(require('./tree'));
@@ -1,7 +1,7 @@
1
1
  // lessc_helper.js
2
2
  //
3
3
  // helper functions for lessc
4
- sys = require('util');
4
+ var sys = require('util');
5
5
 
6
6
  var lessc_helper = {
7
7
 
@@ -29,14 +29,18 @@ var lessc_helper = {
29
29
  sys.puts("");
30
30
  sys.puts("options:");
31
31
  sys.puts(" -h, --help Print help (this message) and exit.");
32
- sys.puts(" --include-path Set include paths. Separated by `:'. Use `;' on Windows.");
32
+ sys.puts(" --include-path=PATHS Set include paths. Separated by `:'. Use `;' on Windows.");
33
+ sys.puts(" -M, --depends Output a makefile import dependency list to stdout");
33
34
  sys.puts(" --no-color Disable colorized output.");
35
+ sys.puts(" --no-ie-compat Disable IE compatibility checks.");
36
+ sys.puts(" -l, --lint Syntax check only (lint).");
34
37
  sys.puts(" -s, --silent Suppress output of error messages.");
35
38
  sys.puts(" --strict-imports Force evaluation of imports.");
36
39
  sys.puts(" --verbose Be verbose.");
37
40
  sys.puts(" -v, --version Print version number and exit.");
38
41
  sys.puts(" -x, --compress Compress output by removing some whitespaces.");
39
42
  sys.puts(" --yui-compress Compress output using ycssmin");
43
+ sys.puts(" --max-line-len=LINELEN Max line length used by ycssmin");
40
44
  sys.puts(" -O0, -O1, -O2 Set the parser's optimization level. The lower");
41
45
  sys.puts(" the number, the less nodes it will create in the");
42
46
  sys.puts(" tree. This could matter for debugging, or if you");
@@ -47,9 +51,14 @@ var lessc_helper = {
47
51
  sys.puts(" that will output the information within a fake");
48
52
  sys.puts(" media query which is compatible with the SASS");
49
53
  sys.puts(" format, and 'all' which will do both.");
50
- sys.puts(" -rp, --rootpath Set rootpath for url rewriting in relative imports and urls.");
51
- sys.puts(" Works with or withour the relative-urls option.");
54
+ sys.puts(" -rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls.");
55
+ sys.puts(" Works with or without the relative-urls option.");
52
56
  sys.puts(" -ru, --relative-urls re-write relative urls to the base less file.");
57
+ sys.puts(" -sm=on|off Turn on or off strict math, where in strict mode, math");
58
+ sys.puts(" --strict-math=on|off requires brackets. This option may default to on and then");
59
+ sys.puts(" be removed in the future.");
60
+ sys.puts(" -su=on|off Allow mixed units, e.g. 1px+1em or 1px*1px which have units");
61
+ sys.puts(" --strict-units=on|off that cannot be represented.");
53
62
  sys.puts("");
54
63
  sys.puts("Report bugs to: http://github.com/cloudhead/less.js/issues");
55
64
  sys.puts("Home page: <http://lesscss.org/>");
@@ -66,16 +66,10 @@ less.Parser = function Parser(env) {
66
66
  var that = this;
67
67
 
68
68
  // Top parser on an import tree must be sure there is one "env"
69
- // which will then be passed arround by reference.
70
- var env = env || { };
71
- // env.contents and files must be passed arround with top env
72
- if (!env.contents) { env.contents = {}; }
73
- env.rootpath = env.rootpath || ''; // env.rootpath must be initialized to '' if not provided
74
- if (!env.files) { env.files = {}; }
75
-
76
- // This function is called after all files
77
- // have been imported through `@import`.
78
- var finish = function () {};
69
+ // which will then be passed around by reference.
70
+ if (!(env instanceof tree.parseEnv)) {
71
+ env = new tree.parseEnv(env);
72
+ }
79
73
 
80
74
  var imports = this.imports = {
81
75
  paths: env.paths || [], // Search paths, when importing
@@ -84,31 +78,29 @@ less.Parser = function Parser(env) {
84
78
  contents: env.contents, // Holds the imported file contents
85
79
  mime: env.mime, // MIME type of .less files
86
80
  error: null, // Error in parsing/evaluating an import
87
- push: function (path, callback) {
88
- var that = this;
81
+ push: function (path, currentFileInfo, callback) {
82
+ var parserImporter = this;
89
83
  this.queue.push(path);
90
84
 
91
85
  //
92
86
  // Import a file asynchronously
93
87
  //
94
- less.Parser.importer(path, this.paths, function (e, root, fullPath) {
95
- that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
96
-
97
- var imported = fullPath in that.files;
88
+ less.Parser.importer(path, currentFileInfo, function (e, root, fullPath) {
89
+ parserImporter.queue.splice(parserImporter.queue.indexOf(path), 1); // Remove the path from the queue
98
90
 
99
- that.files[fullPath] = root; // Store the root
91
+ var imported = fullPath in parserImporter.files;
100
92
 
101
- if (e && !that.error) { that.error = e }
93
+ parserImporter.files[fullPath] = root; // Store the root
102
94
 
95
+ if (e && !parserImporter.error) { parserImporter.error = e; }
96
+
103
97
  callback(e, root, imported);
104
-
105
- if (that.queue.length === 0) { finish(that.error) } // Call `finish` if we're done importing
106
98
  }, env);
107
99
  }
108
100
  };
109
101
 
110
- function save() { temp = chunks[j], memo = i, current = i }
111
- function restore() { chunks[j] = temp, i = memo, current = i }
102
+ function save() { temp = chunks[j], memo = i, current = i; }
103
+ function restore() { chunks[j] = temp, i = memo, current = i; }
112
104
 
113
105
  function sync() {
114
106
  if (i > current) {
@@ -217,7 +209,7 @@ less.Parser = function Parser(env) {
217
209
  }
218
210
 
219
211
  function getInput(e, env) {
220
- if (e.filename && env.filename && (e.filename !== env.filename)) {
212
+ if (e.filename && env.currentFileInfo.filename && (e.filename !== env.currentFileInfo.filename)) {
221
213
  return parser.imports.contents[e.filename];
222
214
  } else {
223
215
  return input;
@@ -233,17 +225,15 @@ less.Parser = function Parser(env) {
233
225
  column: column };
234
226
  }
235
227
 
236
- function getFileName(e) {
237
- if(less.mode === 'browser' || less.mode === 'rhino')
238
- return e.filename;
239
- else
240
- return require('path').resolve(e.filename);
241
- }
228
+ function getDebugInfo(index, inputStream, env) {
229
+ var filename = env.currentFileInfo.filename;
230
+ if(less.mode !== 'browser' && less.mode !== 'rhino') {
231
+ filename = require('path').resolve(filename);
232
+ }
242
233
 
243
- function getDebugInfo(index, inputStream, e) {
244
234
  return {
245
235
  lineNumber: getLocation(index, inputStream).line + 1,
246
- fileName: getFileName(e)
236
+ fileName: filename
247
237
  };
248
238
  }
249
239
 
@@ -256,7 +246,7 @@ less.Parser = function Parser(env) {
256
246
 
257
247
  this.type = e.type || 'Syntax';
258
248
  this.message = e.message;
259
- this.filename = e.filename || env.filename;
249
+ this.filename = e.filename || env.currentFileInfo.filename;
260
250
  this.index = e.index;
261
251
  this.line = typeof(line) === 'number' ? line + 1 : null;
262
252
  this.callLine = e.call && (getLocation(e.call, input).line + 1);
@@ -270,6 +260,9 @@ less.Parser = function Parser(env) {
270
260
  ];
271
261
  }
272
262
 
263
+ LessError.prototype = new Error();
264
+ LessError.prototype.constructor = LessError;
265
+
273
266
  this.env = env = env || {};
274
267
 
275
268
  // The optimization level dictates the thoroughness of the parser,
@@ -278,8 +271,6 @@ less.Parser = function Parser(env) {
278
271
  // the individual nodes in the tree.
279
272
  this.optimization = ('optimization' in this.env) ? this.env.optimization : 1;
280
273
 
281
- this.env.filename = this.env.filename || null;
282
-
283
274
  //
284
275
  // The Parser
285
276
  //
@@ -357,7 +348,7 @@ less.Parser = function Parser(env) {
357
348
  index: i-1,
358
349
  type: 'Parse',
359
350
  message: (level > 0) ? "missing closing `}`" : "missing opening `{`",
360
- filename: env.filename
351
+ filename: env.currentFileInfo.filename
361
352
  }, env);
362
353
  }
363
354
 
@@ -365,7 +356,7 @@ less.Parser = function Parser(env) {
365
356
  })([[]]);
366
357
 
367
358
  if (error) {
368
- return callback(error, env);
359
+ return callback(new(LessError)(error, env));
369
360
  }
370
361
 
371
362
  // Start with the primary rule.
@@ -375,6 +366,7 @@ less.Parser = function Parser(env) {
375
366
  try {
376
367
  root = new(tree.Ruleset)([], $(this.parsers.primary));
377
368
  root.root = true;
369
+ root.firstRoot = true;
378
370
  } catch (e) {
379
371
  return callback(new(LessError)(e, env));
380
372
  }
@@ -383,9 +375,10 @@ less.Parser = function Parser(env) {
383
375
  var line, lines, column;
384
376
 
385
377
  return function (options, variables) {
386
- var frames = [], importError;
387
-
388
378
  options = options || {};
379
+ var importError,
380
+ evalEnv = new tree.evalEnv(options);
381
+
389
382
  //
390
383
  // Allows setting variables with a hash, so:
391
384
  //
@@ -411,23 +404,28 @@ less.Parser = function Parser(env) {
411
404
  }
412
405
  return new(tree.Rule)('@' + k, value, false, 0);
413
406
  });
414
- frames = [new(tree.Ruleset)(null, variables)];
407
+ evalEnv.frames = [new(tree.Ruleset)(null, variables)];
415
408
  }
416
409
 
417
410
  try {
418
- var css = evaluate.call(this, { frames: frames })
419
- .toCSS([], { compress: options.compress || false, dumpLineNumbers: env.dumpLineNumbers });
411
+ var evaldRoot = evaluate.call(this, evalEnv);
412
+
413
+ new(tree.joinSelectorVisitor)()
414
+ .run(evaldRoot);
415
+
416
+ new(tree.processExtendsVisitor)()
417
+ .run(evaldRoot);
418
+
419
+ var css = evaldRoot.toCSS({
420
+ compress: Boolean(options.compress),
421
+ dumpLineNumbers: env.dumpLineNumbers,
422
+ strictUnits: Boolean(options.strictUnits)});
420
423
  } catch (e) {
421
424
  throw new(LessError)(e, env);
422
425
  }
423
426
 
424
- if ((importError = parser.imports.error)) { // Check if there was an error during importing
425
- if (importError instanceof LessError) throw importError;
426
- else throw new(LessError)(importError, env);
427
- }
428
-
429
427
  if (options.yuicompress && less.mode === 'node') {
430
- return require('ycssmin').cssmin(css);
428
+ return require('ycssmin').cssmin(css, options.maxLineLen);
431
429
  } else if (options.compress) {
432
430
  return css.replace(/(\s)+/g, "$1");
433
431
  } else {
@@ -453,9 +451,9 @@ less.Parser = function Parser(env) {
453
451
 
454
452
  error = {
455
453
  type: "Parse",
456
- message: "Syntax Error on line " + line,
454
+ message: "Unrecognised input",
457
455
  index: i,
458
- filename: env.filename,
456
+ filename: env.currentFileInfo.filename,
459
457
  line: line,
460
458
  column: column,
461
459
  extract: [
@@ -466,14 +464,26 @@ less.Parser = function Parser(env) {
466
464
  };
467
465
  }
468
466
 
469
- if (this.imports.queue.length > 0) {
470
- finish = function (e) {
471
- e = error || e;
472
- if (e) callback(e);
473
- else callback(null, root);
474
- };
467
+ var finish = function (e) {
468
+ e = error || e || parser.imports.error;
469
+
470
+ if (e) {
471
+ if (!(e instanceof LessError)) {
472
+ e = new(LessError)(e, env);
473
+ }
474
+
475
+ callback(e);
476
+ }
477
+ else {
478
+ callback(null, root);
479
+ }
480
+ };
481
+
482
+ if (env.processImports !== false) {
483
+ new tree.importVisitor(this.imports, finish)
484
+ .run(root);
475
485
  } else {
476
- callback(error, root);
486
+ finish();
477
487
  }
478
488
  },
479
489
 
@@ -525,7 +535,7 @@ less.Parser = function Parser(env) {
525
535
  primary: function () {
526
536
  var node, root = [];
527
537
 
528
- while ((node = $(this.mixin.definition) || $(this.rule) || $(this.ruleset) ||
538
+ while ((node = $(this.extendRule) || $(this.mixin.definition) || $(this.rule) || $(this.ruleset) ||
529
539
  $(this.mixin.call) || $(this.comment) || $(this.directive))
530
540
  || $(/^[\s\n]+/) || $(/^;+/)) {
531
541
  node && root.push(node);
@@ -558,7 +568,7 @@ less.Parser = function Parser(env) {
558
568
  // "milky way" 'he\'s the one!'
559
569
  //
560
570
  quoted: function () {
561
- var str, j = i, e;
571
+ var str, j = i, e, index = i;
562
572
 
563
573
  if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
564
574
  if (input.charAt(j) !== '"' && input.charAt(j) !== "'") return;
@@ -566,7 +576,7 @@ less.Parser = function Parser(env) {
566
576
  e && $('~');
567
577
 
568
578
  if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
569
- return new(tree.Quoted)(str[0], str[1] || str[2], e);
579
+ return new(tree.Quoted)(str[0], str[1] || str[2], e, index, env.currentFileInfo);
570
580
  }
571
581
  },
572
582
 
@@ -607,7 +617,7 @@ less.Parser = function Parser(env) {
607
617
  nameLC = name.toLowerCase();
608
618
 
609
619
  if (nameLC === 'url') { return null }
610
- else { i += name.length }
620
+ else { i += name.length }
611
621
 
612
622
  if (nameLC === 'alpha') {
613
623
  alpha_ret = $(this.alpha);
@@ -620,9 +630,11 @@ less.Parser = function Parser(env) {
620
630
 
621
631
  args = $(this.entities.arguments);
622
632
 
623
- if (! $(')')) return;
633
+ if (! $(')')) {
634
+ return;
635
+ }
624
636
 
625
- if (name) { return new(tree.Call)(name, args, index, env.filename) }
637
+ if (name) { return new(tree.Call)(name, args, index, env.currentFileInfo); }
626
638
  },
627
639
  arguments: function () {
628
640
  var args = [], arg;
@@ -634,8 +646,7 @@ less.Parser = function Parser(env) {
634
646
  return args;
635
647
  },
636
648
  literal: function () {
637
- return $(this.entities.ratio) ||
638
- $(this.entities.dimension) ||
649
+ return $(this.entities.dimension) ||
639
650
  $(this.entities.color) ||
640
651
  $(this.entities.quoted) ||
641
652
  $(this.entities.unicodeDescriptor);
@@ -671,7 +682,7 @@ less.Parser = function Parser(env) {
671
682
  expect(')');
672
683
 
673
684
  return new(tree.URL)((value.value != null || value instanceof tree.Variable)
674
- ? value : new(tree.Anonymous)(value), env.rootpath);
685
+ ? value : new(tree.Anonymous)(value), env.currentFileInfo);
675
686
  },
676
687
 
677
688
  //
@@ -686,7 +697,7 @@ less.Parser = function Parser(env) {
686
697
  var name, index = i;
687
698
 
688
699
  if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
689
- return new(tree.Variable)(name, index, env.filename);
700
+ return new(tree.Variable)(name, index, env.currentFileInfo);
690
701
  }
691
702
  },
692
703
 
@@ -695,7 +706,7 @@ less.Parser = function Parser(env) {
695
706
  var name, curly, index = i;
696
707
 
697
708
  if (input.charAt(i) === '@' && (curly = $(/^@\{([\w-]+)\}/))) {
698
- return new(tree.Variable)("@" + curly[1], index, env.filename);
709
+ return new(tree.Variable)("@" + curly[1], index, env.currentFileInfo);
699
710
  }
700
711
  },
701
712
 
@@ -724,25 +735,11 @@ less.Parser = function Parser(env) {
724
735
  //Is the first char of the dimension 0-9, '.', '+' or '-'
725
736
  if ((c > 57 || c < 43) || c === 47 || c == 44) return;
726
737
 
727
- if (value = $(/^([+-]?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn|dpi|dpcm|dppx|rem|vw|vh|vmin|vm|ch)?/)) {
738
+ if (value = $(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/)) {
728
739
  return new(tree.Dimension)(value[1], value[2]);
729
740
  }
730
741
  },
731
742
 
732
- //
733
- // A Ratio
734
- //
735
- // 16/9
736
- //
737
- ratio: function () {
738
- var value, c = input.charCodeAt(i);
739
- if (c > 57 || c < 48) return;
740
-
741
- if (value = $(/^(\d+\/\d+)/)) {
742
- return new(tree.Ratio)(value[1]);
743
- }
744
- },
745
-
746
743
  //
747
744
  // A unicode descriptor, as is used in unicode-range
748
745
  //
@@ -787,26 +784,46 @@ less.Parser = function Parser(env) {
787
784
  },
788
785
 
789
786
  //
790
- // A font size/line-height shorthand
791
- //
792
- // small/12px
793
- //
794
- // We need to peek first, or we'll match on keywords and dimensions
787
+ // extend syntax - used to extend selectors
795
788
  //
796
- shorthand: function () {
797
- var a, b;
789
+ extend: function(isRule) {
790
+ var elements, e, index = i, option, extendList = [];
798
791
 
799
- if (! peek(/^[@\w.%-]+\/[@\w.-]+/)) return;
792
+ if (!$(isRule ? /^&:extend\(/ : /^:extend\(/)) { return; }
800
793
 
801
- save();
794
+ do {
795
+ option = null;
796
+ elements = [];
797
+ while (true) {
798
+ option = $(/^(all)(?=\s*(\)|,))/);
799
+ if (option) { break; }
800
+ e = $(this.element);
801
+ if (!e) { break; }
802
+ elements.push(e);
803
+ }
804
+
805
+ option = option && option[1];
806
+
807
+ extendList.push(new(tree.Extend)(new(tree.Selector)(elements), option, index));
808
+
809
+ } while($(","))
810
+
811
+ expect(/^\)/);
802
812
 
803
- if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) {
804
- return new(tree.Shorthand)(a, b);
813
+ if (isRule) {
814
+ expect(/^;/);
805
815
  }
806
816
 
807
- restore();
817
+ return extendList;
808
818
  },
809
819
 
820
+ //
821
+ // extendRule - used in a rule to extend all the parent selectors
822
+ //
823
+ extendRule: function() {
824
+ return this.extend(true);
825
+ },
826
+
810
827
  //
811
828
  // Mixins
812
829
  //
@@ -823,10 +840,10 @@ less.Parser = function Parser(env) {
823
840
  // selector for now.
824
841
  //
825
842
  call: function () {
826
- var elements = [], e, c, argsSemiColon = [], argsComma = [], args, delim, arg, nameLoop, expressions, isSemiColonSeperated, expressionContainsNamed, index = i, s = input.charAt(i), name, value, important = false;
843
+ var elements = [], e, c, args, delim, arg, index = i, s = input.charAt(i), important = false;
827
844
 
828
845
  if (s !== '.' && s !== '#') { return }
829
-
846
+
830
847
  save(); // stop us absorbing part of an invalid selector
831
848
 
832
849
  while (e = $(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)) {
@@ -834,71 +851,119 @@ less.Parser = function Parser(env) {
834
851
  c = $('>');
835
852
  }
836
853
  if ($('(')) {
837
- expressions = [];
838
- while (arg = $(this.expression)) {
839
- nameLoop = null;
840
- value = arg;
854
+ args = this.mixin.args.call(this, true).args;
855
+ expect(')');
856
+ }
857
+
858
+ args = args || [];
841
859
 
860
+ if ($(this.important)) {
861
+ important = true;
862
+ }
863
+
864
+ if (elements.length > 0 && ($(';') || peek('}'))) {
865
+ return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
866
+ }
867
+
868
+ restore();
869
+ },
870
+ args: function (isCall) {
871
+ var expressions = [], argsSemiColon = [], isSemiColonSeperated, argsComma = [], expressionContainsNamed, name, nameLoop, value, arg,
872
+ returner = {args:null, variadic: false};
873
+ while (true) {
874
+ if (isCall) {
875
+ arg = $(this.expression);
876
+ } else {
877
+ $(this.comment);
878
+ if (input.charAt(i) === '.' && $(/^\.{3}/)) {
879
+ returner.variadic = true;
880
+ if ($(";") && !isSemiColonSeperated) {
881
+ isSemiColonSeperated = true;
882
+ }
883
+ (isSemiColonSeperated ? argsSemiColon : argsComma)
884
+ .push({ variadic: true });
885
+ break;
886
+ }
887
+ arg = $(this.entities.variable) || $(this.entities.literal)
888
+ || $(this.entities.keyword);
889
+ }
890
+
891
+ if (!arg) {
892
+ break;
893
+ }
894
+
895
+ nameLoop = null;
896
+ if (arg.throwAwayComments) {
897
+ arg.throwAwayComments();
898
+ }
899
+ value = arg;
900
+ var val = null;
901
+
902
+ if (isCall) {
842
903
  // Variable
843
904
  if (arg.value.length == 1) {
844
905
  var val = arg.value[0];
845
- if (val instanceof tree.Variable) {
846
- if ($(':')) {
847
- if (expressions.length > 0) {
848
- if (isSemiColonSeperated) {
849
- error("Cannot mix ; and , as delimiter types");
850
- }
851
- expressionContainsNamed = true;
852
- }
853
- value = expect(this.expression);
854
- nameLoop = (name = val.name);
855
- }
856
- }
857
- }
858
-
859
- expressions.push(value);
860
-
861
- argsComma.push({ name: nameLoop, value: value });
862
-
863
- if ($(',')) {
864
- continue;
865
906
  }
866
-
867
- if ($(';') || isSemiColonSeperated) {
868
-
869
- if (expressionContainsNamed) {
870
- error("Cannot mix ; and , as delimiter types");
907
+ } else {
908
+ val = arg;
909
+ }
910
+
911
+ if (val && val instanceof tree.Variable) {
912
+ if ($(':')) {
913
+ if (expressions.length > 0) {
914
+ if (isSemiColonSeperated) {
915
+ error("Cannot mix ; and , as delimiter types");
916
+ }
917
+ expressionContainsNamed = true;
871
918
  }
872
-
873
- isSemiColonSeperated = true;
874
-
875
- if (expressions.length > 1) {
876
- value = new(tree.Value)(expressions);
919
+ value = expect(this.expression);
920
+ nameLoop = (name = val.name);
921
+ } else if (!isCall && $(/^\.{3}/)) {
922
+ returner.variadic = true;
923
+ if ($(";") && !isSemiColonSeperated) {
924
+ isSemiColonSeperated = true;
877
925
  }
878
- argsSemiColon.push({ name: name, value: value });
879
-
880
- name = null;
881
- expressions = [];
882
- expressionContainsNamed = false;
926
+ (isSemiColonSeperated ? argsSemiColon : argsComma)
927
+ .push({ name: arg.name, variadic: true });
928
+ break;
929
+ } else if (!isCall) {
930
+ name = nameLoop = val.name;
931
+ value = null;
883
932
  }
884
933
  }
885
934
 
886
- expect(')');
887
- }
935
+ if (value) {
936
+ expressions.push(value);
937
+ }
888
938
 
889
- args = isSemiColonSeperated ? argsSemiColon : argsComma;
939
+ argsComma.push({ name:nameLoop, value:value });
890
940
 
891
- if ($(this.important)) {
892
- important = true;
893
- }
941
+ if ($(',')) {
942
+ continue;
943
+ }
894
944
 
895
- if (elements.length > 0 && ($(';') || peek('}'))) {
896
- return new(tree.mixin.Call)(elements, args, index, env.filename, important);
945
+ if ($(';') || isSemiColonSeperated) {
946
+
947
+ if (expressionContainsNamed) {
948
+ error("Cannot mix ; and , as delimiter types");
949
+ }
950
+
951
+ isSemiColonSeperated = true;
952
+
953
+ if (expressions.length > 1) {
954
+ value = new (tree.Value)(expressions);
955
+ }
956
+ argsSemiColon.push({ name:name, value:value });
957
+
958
+ name = null;
959
+ expressions = [];
960
+ expressionContainsNamed = false;
961
+ }
897
962
  }
898
-
899
- restore();
900
- },
901
963
 
964
+ returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
965
+ return returner;
966
+ },
902
967
  //
903
968
  // A Mixin definition, with a list of parameters
904
969
  //
@@ -928,35 +993,11 @@ less.Parser = function Parser(env) {
928
993
  if (match = $(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)) {
929
994
  name = match[1];
930
995
 
931
- do {
932
- $(this.comment);
933
- if (input.charAt(i) === '.' && $(/^\.{3}/)) {
934
- variadic = true;
935
- params.push({ variadic: true });
936
- break;
937
- } else if (param = $(this.entities.variable) || $(this.entities.literal)
938
- || $(this.entities.keyword)) {
939
- // Variable
940
- if (param instanceof tree.Variable) {
941
- if ($(':')) {
942
- value = expect(this.expression, 'expected expression');
943
- params.push({ name: param.name, value: value });
944
- } else if ($(/^\.{3}/)) {
945
- params.push({ name: param.name, variadic: true });
946
- variadic = true;
947
- break;
948
- } else {
949
- params.push({ name: param.name });
950
- }
951
- } else {
952
- params.push({ value: param });
953
- }
954
- } else {
955
- break;
956
- }
957
- } while ($(',') || $(';'))
996
+ var argInfo = this.mixin.args.call(this, false);
997
+ params = argInfo.args;
998
+ variadic = argInfo.variadic;
958
999
 
959
- // .mixincall("@{a}");
1000
+ // .mixincall("@{a}");
960
1001
  // looks a bit like a mixin definition.. so we have to be nice and restore
961
1002
  if (!$(')')) {
962
1003
  furthest = i;
@@ -1036,9 +1077,7 @@ less.Parser = function Parser(env) {
1036
1077
 
1037
1078
  if (! e) {
1038
1079
  if ($('(')) {
1039
- if ((v = ($(this.entities.variableCurly) ||
1040
- $(this.entities.variable) ||
1041
- $(this.selector))) &&
1080
+ if ((v = ($(this.selector))) &&
1042
1081
  $(')')) {
1043
1082
  e = new(tree.Paren)(v);
1044
1083
  }
@@ -1058,7 +1097,7 @@ less.Parser = function Parser(env) {
1058
1097
  // we deal with this in *combinator.js*.
1059
1098
  //
1060
1099
  combinator: function () {
1061
- var match, c = input.charAt(i);
1100
+ var c = input.charAt(i);
1062
1101
 
1063
1102
  if (c === '>' || c === '+' || c === '~' || c === '|') {
1064
1103
  i++;
@@ -1080,38 +1119,41 @@ less.Parser = function Parser(env) {
1080
1119
  // Selectors are made out of one or more Elements, see above.
1081
1120
  //
1082
1121
  selector: function () {
1083
- var sel, e, elements = [], c, match;
1122
+ var sel, e, elements = [], c, extend, extendList = [];
1084
1123
 
1085
- // depreciated, will be removed soon
1086
- if ($('(')) {
1087
- sel = $(this.entity);
1088
- if (!$(')')) { return null; }
1089
- return new(tree.Selector)([new(tree.Element)('', sel, i)]);
1090
- }
1091
-
1092
- while (e = $(this.element)) {
1093
- c = input.charAt(i);
1094
- elements.push(e)
1124
+ while ((extend = $(this.extend)) || (e = $(this.element))) {
1125
+ if (extend) {
1126
+ extendList.push.apply(extendList, extend);
1127
+ } else {
1128
+ if (extendList.length) {
1129
+ error("Extend can only be used at the end of selector");
1130
+ }
1131
+ c = input.charAt(i);
1132
+ elements.push(e)
1133
+ e = null;
1134
+ }
1095
1135
  if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { break }
1096
1136
  }
1097
1137
 
1098
- if (elements.length > 0) { return new(tree.Selector)(elements) }
1138
+ if (elements.length > 0) { return new(tree.Selector)(elements, extendList); }
1139
+ if (extendList.length) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
1099
1140
  },
1100
1141
  attribute: function () {
1101
1142
  var attr = '', key, val, op;
1102
1143
 
1103
1144
  if (! $('[')) return;
1104
1145
 
1105
- if (key = $(/^(?:[_A-Za-z0-9-]|\\.)+/) || $(this.entities.quoted)) {
1106
- if ((op = $(/^[|~*$^]?=/)) &&
1107
- (val = $(this.entities.quoted) || $(/^[\w-]+/))) {
1108
- attr = [key, op, val.toCSS ? val.toCSS() : val].join('');
1109
- } else { attr = key }
1146
+ if (!(key = $(this.entities.variableCurly))) {
1147
+ key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
1148
+ }
1149
+
1150
+ if ((op = $(/^[|~*$^]?=/))) {
1151
+ val = $(this.entities.quoted) || $(/^[\w-]+/) || $(this.entities.variableCurly);
1110
1152
  }
1111
1153
 
1112
- if (! $(']')) return;
1154
+ expect(']');
1113
1155
 
1114
- if (attr) { return "[" + attr + "]" }
1156
+ return new(tree.Attribute)(key, op, val);
1115
1157
  },
1116
1158
 
1117
1159
  //
@@ -1129,7 +1171,7 @@ less.Parser = function Parser(env) {
1129
1171
  // div, .class, body > p {...}
1130
1172
  //
1131
1173
  ruleset: function () {
1132
- var selectors = [], s, rules, match, debugInfo;
1174
+ var selectors = [], s, rules, debugInfo;
1133
1175
 
1134
1176
  save();
1135
1177
 
@@ -1154,31 +1196,39 @@ less.Parser = function Parser(env) {
1154
1196
  restore();
1155
1197
  }
1156
1198
  },
1157
- rule: function () {
1158
- var name, value, c = input.charAt(i), important, match;
1199
+ rule: function (tryAnonymous) {
1200
+ var name, value, c = input.charAt(i), important;
1159
1201
  save();
1160
1202
 
1161
1203
  if (c === '.' || c === '#' || c === '&') { return }
1162
1204
 
1163
1205
  if (name = $(this.variable) || $(this.property)) {
1164
- if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) {
1165
- i += match[0].length - 1;
1166
- value = new(tree.Anonymous)(match[1]);
1167
- } else if (name === "font") {
1168
- value = $(this.font);
1169
- } else {
1170
- value = $(this.value);
1171
- }
1206
+ // prefer to try to parse first if its a variable or we are compressing
1207
+ // but always fallback on the other one
1208
+ value = !tryAnonymous && (env.compress || (name.charAt(0) === '@')) ?
1209
+ ($(this.value) || $(this.anonymousValue)) :
1210
+ ($(this.anonymousValue) || $(this.value));
1211
+
1172
1212
  important = $(this.important);
1173
1213
 
1174
1214
  if (value && $(this.end)) {
1175
- return new(tree.Rule)(name, value, important, memo);
1215
+ return new(tree.Rule)(name, value, important, memo, env.currentFileInfo);
1176
1216
  } else {
1177
1217
  furthest = i;
1178
1218
  restore();
1219
+ if (value && !tryAnonymous) {
1220
+ return this.rule(true);
1221
+ }
1179
1222
  }
1180
1223
  }
1181
1224
  },
1225
+ anonymousValue: function () {
1226
+ var match;
1227
+ if (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j])) {
1228
+ i += match[0].length - 1;
1229
+ return new(tree.Anonymous)(match[1]);
1230
+ }
1231
+ },
1182
1232
 
1183
1233
  //
1184
1234
  // An @import directive
@@ -1192,21 +1242,58 @@ less.Parser = function Parser(env) {
1192
1242
  //
1193
1243
  "import": function () {
1194
1244
  var path, features, index = i;
1195
-
1245
+
1196
1246
  save();
1197
-
1198
- var dir = $(/^@import(?:-(once))?\s+/);
1247
+
1248
+ var dir = $(/^@import?\s+/);
1249
+
1250
+ var options = (dir ? $(this.importOptions) : null) || {};
1199
1251
 
1200
1252
  if (dir && (path = $(this.entities.quoted) || $(this.entities.url))) {
1201
1253
  features = $(this.mediaFeatures);
1202
1254
  if ($(';')) {
1203
- return new(tree.Import)(path, imports, features, (dir[1] === 'once'), index, env.rootpath);
1255
+ features = features && new(tree.Value)(features);
1256
+ return new(tree.Import)(path, features, options, index, env.currentFileInfo);
1204
1257
  }
1205
1258
  }
1206
-
1259
+
1207
1260
  restore();
1208
1261
  },
1209
1262
 
1263
+ importOptions: function() {
1264
+ var o, options = {}, optionName, value;
1265
+
1266
+ // list of options, surrounded by parens
1267
+ if (! $('(')) { return null; }
1268
+ do {
1269
+ if (o = $(this.importOption)) {
1270
+ optionName = o;
1271
+ value = true;
1272
+ switch(optionName) {
1273
+ case "css":
1274
+ optionName = "less";
1275
+ value = false;
1276
+ break;
1277
+ case "once":
1278
+ optionName = "multiple";
1279
+ value = false;
1280
+ break;
1281
+ }
1282
+ options[optionName] = value;
1283
+ if (! $(',')) { break }
1284
+ }
1285
+ } while (o);
1286
+ expect(')');
1287
+ return options;
1288
+ },
1289
+
1290
+ importOption: function() {
1291
+ var opt = $(/^(less|css|multiple|once)/);
1292
+ if (opt) {
1293
+ return opt[1];
1294
+ }
1295
+ },
1296
+
1210
1297
  mediaFeature: function () {
1211
1298
  var e, p, nodes = [];
1212
1299
 
@@ -1215,10 +1302,10 @@ less.Parser = function Parser(env) {
1215
1302
  nodes.push(e);
1216
1303
  } else if ($('(')) {
1217
1304
  p = $(this.property);
1218
- e = $(this.entity);
1305
+ e = $(this.value);
1219
1306
  if ($(')')) {
1220
1307
  if (p && e) {
1221
- nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, true)));
1308
+ nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, env.currentFileInfo, true)));
1222
1309
  } else if (e) {
1223
1310
  nodes.push(new(tree.Paren)(e));
1224
1311
  } else {
@@ -1281,7 +1368,7 @@ less.Parser = function Parser(env) {
1281
1368
  if (value = $(this['import']) || $(this.media)) {
1282
1369
  return value;
1283
1370
  }
1284
-
1371
+
1285
1372
  save();
1286
1373
 
1287
1374
  name = $(/^@[a-z-]+/);
@@ -1346,24 +1433,8 @@ less.Parser = function Parser(env) {
1346
1433
  return directive;
1347
1434
  }
1348
1435
  }
1349
-
1350
- restore();
1351
- },
1352
- font: function () {
1353
- var value = [], expression = [], weight, shorthand, font, e;
1354
1436
 
1355
- while (e = $(this.shorthand) || $(this.entity)) {
1356
- expression.push(e);
1357
- }
1358
- value.push(new(tree.Expression)(expression));
1359
-
1360
- if ($(',')) {
1361
- while (e = $(this.expression)) {
1362
- value.push(e);
1363
- if (! $(',')) { break }
1364
- }
1365
- }
1366
- return new(tree.Value)(value);
1437
+ restore();
1367
1438
  },
1368
1439
 
1369
1440
  //
@@ -1392,27 +1463,44 @@ less.Parser = function Parser(env) {
1392
1463
  }
1393
1464
  },
1394
1465
  sub: function () {
1395
- var e;
1466
+ var a, e;
1396
1467
 
1397
- if ($('(') && (e = $(this.expression)) && $(')')) {
1398
- return e;
1468
+ if ($('(')) {
1469
+ if (a = $(this.addition)) {
1470
+ e = new(tree.Expression)([a]);
1471
+ expect(')');
1472
+ e.parens = true;
1473
+ return e;
1474
+ }
1399
1475
  }
1400
1476
  },
1401
1477
  multiplication: function () {
1402
- var m, a, op, operation;
1478
+ var m, a, op, operation, isSpaced, expression = [];
1403
1479
  if (m = $(this.operand)) {
1404
- while (!peek(/^\/[*\/]/) && (op = ($('/') || $('*'))) && (a = $(this.operand))) {
1405
- operation = new(tree.Operation)(op, [operation || m, a]);
1480
+ isSpaced = isWhitespace(input.charAt(i - 1));
1481
+ while (!peek(/^\/[*\/]/) && (op = ($('/') || $('*')))) {
1482
+ if (a = $(this.operand)) {
1483
+ m.parensInOp = true;
1484
+ a.parensInOp = true;
1485
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1486
+ isSpaced = isWhitespace(input.charAt(i - 1));
1487
+ } else {
1488
+ break;
1489
+ }
1406
1490
  }
1407
1491
  return operation || m;
1408
1492
  }
1409
1493
  },
1410
1494
  addition: function () {
1411
- var m, a, op, operation;
1495
+ var m, a, op, operation, isSpaced;
1412
1496
  if (m = $(this.multiplication)) {
1413
- while ((op = $(/^[-+]\s+/) || (!isWhitespace(input.charAt(i - 1)) && ($('+') || $('-')))) &&
1497
+ isSpaced = isWhitespace(input.charAt(i - 1));
1498
+ while ((op = $(/^[-+]\s+/) || (!isSpaced && ($('+') || $('-')))) &&
1414
1499
  (a = $(this.multiplication))) {
1415
- operation = new(tree.Operation)(op, [operation || m, a]);
1500
+ m.parensInOp = true;
1501
+ a.parensInOp = true;
1502
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1503
+ isSpaced = isWhitespace(input.charAt(i - 1));
1416
1504
  }
1417
1505
  return operation || m;
1418
1506
  }
@@ -1458,8 +1546,13 @@ less.Parser = function Parser(env) {
1458
1546
  var o = $(this.sub) || $(this.entities.dimension) ||
1459
1547
  $(this.entities.color) || $(this.entities.variable) ||
1460
1548
  $(this.entities.call);
1461
- return negate ? new(tree.Operation)('*', [new(tree.Dimension)(-1), o])
1462
- : o;
1549
+
1550
+ if (negate) {
1551
+ o.parensInOp = true;
1552
+ o = new(tree.Negative)(o);
1553
+ }
1554
+
1555
+ return o;
1463
1556
  },
1464
1557
 
1465
1558
  //
@@ -1474,6 +1567,10 @@ less.Parser = function Parser(env) {
1474
1567
 
1475
1568
  while (e = $(this.addition) || $(this.entity)) {
1476
1569
  entities.push(e);
1570
+ // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
1571
+ if (!peek(/^\/[\/*]/) && (delim = $('/'))) {
1572
+ entities.push(new(tree.Anonymous)(delim));
1573
+ }
1477
1574
  }
1478
1575
  if (entities.length > 0) {
1479
1576
  return new(tree.Expression)(entities);
@@ -1482,7 +1579,7 @@ less.Parser = function Parser(env) {
1482
1579
  property: function () {
1483
1580
  var name;
1484
1581
 
1485
- if (name = $(/^(\*?-?[_a-z0-9-]+)\s*:/)) {
1582
+ if (name = $(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/)) {
1486
1583
  return name[1];
1487
1584
  }
1488
1585
  }
@@ -1494,29 +1591,21 @@ if (less.mode === 'browser' || less.mode === 'rhino') {
1494
1591
  //
1495
1592
  // Used by `@import` directives
1496
1593
  //
1497
- less.Parser.importer = function (path, paths, callback, env) {
1498
- if (!/^([a-z-]+:)?\//.test(path) && paths.length > 0) {
1499
- path = paths[0] + path;
1594
+ less.Parser.importer = function (path, currentFileInfo, callback, env) {
1595
+ if (!/^([a-z-]+:)?\//.test(path) && currentFileInfo.currentDirectory) {
1596
+ path = currentFileInfo.currentDirectory + path;
1500
1597
  }
1598
+ var sheetEnv = env.toSheet(path);
1599
+ sheetEnv.processImports = false;
1600
+ sheetEnv.currentFileInfo = currentFileInfo;
1601
+
1501
1602
  // We pass `true` as 3rd argument, to force the reload of the import.
1502
1603
  // This is so we can get the syntax tree as opposed to just the CSS output,
1503
1604
  // as we need this to evaluate the current stylesheet.
1504
- loadStyleSheet({
1505
- href: path,
1506
- title: path,
1507
- type: env.mime,
1508
- contents: env.contents,
1509
- files: env.files,
1510
- rootpath: env.rootpath,
1511
- entryPath: env.entryPath,
1512
- relativeUrls: env.relativeUrls },
1513
- function (e, root, data, sheet, _, path) {
1514
- if (e && typeof(env.errback) === "function") {
1515
- env.errback.call(null, path, paths, callback, env);
1516
- } else {
1605
+ loadStyleSheet(sheetEnv,
1606
+ function (e, root, data, sheet, _, path) {
1517
1607
  callback.call(null, e, root, path);
1518
- }
1519
- }, true);
1608
+ }, true);
1520
1609
  };
1521
1610
  }
1522
1611