less 2.3.3 → 2.4.0

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