less 2.1.2 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +1 -0
  2. package/.idea/jsLinters/jscs.xml +4 -0
  3. package/.idea/jsLinters/jshint.xml +58 -4
  4. package/.idea/workspace.xml +499 -760
  5. package/.jscsrc +73 -0
  6. package/.jshintrc +0 -2
  7. package/.travis.yml +1 -0
  8. package/CHANGELOG.md +59 -0
  9. package/Gruntfile.js +37 -3
  10. package/appveyor.yml +5 -0
  11. package/bin/lessc +383 -316
  12. package/bower.json +1 -1
  13. package/dist/less.js +1638 -1244
  14. package/dist/less.min.js +7 -7
  15. package/lib/less/contexts.js +3 -3
  16. package/lib/less/data/unit-conversions.js +3 -3
  17. package/lib/less/environment/abstract-file-manager.js +8 -8
  18. package/lib/less/environment/environment.js +2 -2
  19. package/lib/less/functions/color-blending.js +3 -3
  20. package/lib/less/functions/color.js +20 -12
  21. package/lib/less/functions/data-uri.js +26 -18
  22. package/lib/less/functions/math.js +1 -1
  23. package/lib/less/functions/number.js +2 -2
  24. package/lib/less/functions/string.js +3 -1
  25. package/lib/less/functions/svg.js +12 -15
  26. package/lib/less/functions/types.js +33 -10
  27. package/lib/less/import-manager.js +14 -9
  28. package/lib/less/index.js +2 -1
  29. package/lib/less/less-error.js +1 -1
  30. package/lib/less/logger.js +2 -2
  31. package/lib/less/parse-tree.js +48 -42
  32. package/lib/less/parse.js +68 -0
  33. package/lib/less/parser/parser-input.js +6 -5
  34. package/lib/less/parser/parser.js +76 -29
  35. package/lib/less/plugin-manager.js +30 -3
  36. package/lib/less/render.js +11 -37
  37. package/lib/less/source-map-builder.js +4 -0
  38. package/lib/less/source-map-output.js +6 -6
  39. package/lib/less/transform-tree.js +4 -4
  40. package/lib/less/tree/anonymous.js +1 -1
  41. package/lib/less/tree/assignment.js +0 -1
  42. package/lib/less/tree/call.js +1 -1
  43. package/lib/less/tree/color.js +6 -6
  44. package/lib/less/tree/condition.js +9 -5
  45. package/lib/less/tree/debug-info.js +6 -2
  46. package/lib/less/tree/dimension.js +5 -5
  47. package/lib/less/tree/directive.js +25 -6
  48. package/lib/less/tree/element.js +1 -1
  49. package/lib/less/tree/expression.js +2 -2
  50. package/lib/less/tree/extend.js +3 -3
  51. package/lib/less/tree/import.js +2 -2
  52. package/lib/less/tree/index.js +1 -0
  53. package/lib/less/tree/javascript.js +2 -2
  54. package/lib/less/tree/media.js +20 -19
  55. package/lib/less/tree/mixin-call.js +5 -6
  56. package/lib/less/tree/mixin-definition.js +33 -11
  57. package/lib/less/tree/quoted.js +3 -3
  58. package/lib/less/tree/rule.js +3 -4
  59. package/lib/less/tree/ruleset.js +290 -177
  60. package/lib/less/tree/selector.js +3 -2
  61. package/lib/less/tree/unit.js +2 -0
  62. package/lib/less/tree/url.js +5 -2
  63. package/lib/less/tree/value.js +2 -2
  64. package/lib/less/tree/variable.js +1 -1
  65. package/lib/less/visitors/extend-visitor.js +72 -36
  66. package/lib/less/visitors/import-sequencer.js +20 -14
  67. package/lib/less/visitors/import-visitor.js +15 -12
  68. package/lib/less/visitors/to-css-visitor.js +41 -9
  69. package/lib/less-browser/add-default-options.js +5 -1
  70. package/lib/less-browser/bootstrap.js +10 -8
  71. package/lib/less-browser/cache.js +3 -3
  72. package/lib/less-browser/file-manager.js +96 -96
  73. package/lib/less-browser/index.js +206 -205
  74. package/lib/less-browser/log-listener.js +2 -2
  75. package/lib/less-browser/utils.js +6 -6
  76. package/lib/less-node/file-manager.js +58 -30
  77. package/lib/less-node/image-size.js +34 -0
  78. package/lib/less-node/index.js +6 -3
  79. package/lib/less-node/lessc-helper.js +3 -2
  80. package/lib/less-node/plugin-loader.js +3 -3
  81. package/lib/less-node/url-file-manager.js +6 -3
  82. package/lib/less-rhino/index.js +15 -13
  83. package/lib/source-map/source-map-0.1.31.js +1 -1
  84. package/package.json +10 -7
  85. package/test/browser/common.js +13 -11
  86. package/test/browser/css/relative-urls/urls.css +1 -0
  87. package/test/browser/css/urls.css +6 -3
  88. package/test/browser/jasmine-jsreporter.js +2 -3
  89. package/test/browser/less/nested-gradient-with-svg-gradient/mixin-consumer.less +5 -0
  90. package/test/browser/less/nested-gradient-with-svg-gradient/svg-gradient-mixin.less +15 -0
  91. package/test/browser/less/relative-urls/urls.less +1 -0
  92. package/test/browser/less/urls.less +1 -0
  93. package/test/browser/runner-browser-options.js +23 -23
  94. package/test/browser/runner-errors-options.js +0 -1
  95. package/test/browser/runner-errors-spec.js +1 -2
  96. package/test/browser/runner-global-vars-spec.js +1 -1
  97. package/test/browser/runner-legacy-options.js +0 -1
  98. package/test/browser/runner-main-options.js +5 -5
  99. package/test/browser/runner-main-spec.js +1 -1
  100. package/test/browser/runner-no-js-errors-spec.js +0 -1
  101. package/test/browser/runner-production-options.js +0 -1
  102. package/test/browser/runner-relative-urls-options.js +0 -1
  103. package/test/browser/runner-rootpath-options.js +0 -1
  104. package/test/browser/runner-rootpath-relative-options.js +0 -1
  105. package/test/browser/runner-strict-units-options.js +0 -1
  106. package/test/copy-bom.js +72 -0
  107. package/test/css/comments.css +1 -0
  108. package/test/css/css-3.css +2 -0
  109. package/test/css/functions.css +10 -0
  110. package/test/css/import-interpolation.css +7 -0
  111. package/test/css/import-reference.css +25 -0
  112. package/test/css/include-path/include-path.css +6 -0
  113. package/test/css/mixins-important.css +12 -0
  114. package/test/css/selectors.css +15 -1
  115. package/test/css/static-urls/urls.css +1 -0
  116. package/test/css/url-args/urls.css +3 -3
  117. package/test/css/urls.css +13 -4
  118. package/test/css/variables.css +7 -1
  119. package/test/data/image.svg +3 -0
  120. package/test/index.js +15 -10
  121. package/test/less/comments.less +1 -0
  122. package/test/less/css-3.less +3 -0
  123. package/test/less/css-guards.less +2 -1
  124. package/test/less/errors/detached-ruleset-2.txt +1 -1
  125. package/test/less/errors/import-missing.txt +1 -1
  126. package/test/less/errors/parens-error-1.txt +1 -1
  127. package/test/less/errors/parens-error-2.txt +1 -1
  128. package/test/less/errors/parens-error-3.txt +1 -1
  129. package/test/less/errors/single-character.less +1 -0
  130. package/test/less/errors/single-character.txt +2 -0
  131. package/test/less/functions.less +18 -0
  132. package/test/less/import/import-interpolation.less +1 -0
  133. package/test/less/import/import-reference.less +38 -0
  134. package/test/less/import/imports/logo.less +1 -0
  135. package/test/less/import-reference.less +3 -1
  136. package/test/less/import.less +2 -0
  137. package/test/less/include-path/include-path.less +6 -0
  138. package/test/less/mixins-important.less +28 -0
  139. package/test/less/nested-gradient-with-svg-gradient/mixin-consumer.less +5 -0
  140. package/test/less/nested-gradient-with-svg-gradient/svg-gradient-mixin.less +15 -0
  141. package/test/less/selectors.less +29 -4
  142. package/test/less/sourcemaps-empty/empty.less +0 -0
  143. package/test/less/sourcemaps-empty/var-defs.less +1 -0
  144. package/test/less/urls.less +10 -1
  145. package/test/less/variables.less +17 -3
  146. package/test/less-test.js +133 -52
  147. package/test/modify-vars.js +14 -12
  148. package/test/browser/less.js +0 -9473
@@ -24,9 +24,9 @@ Value.prototype.eval = function (context) {
24
24
  };
25
25
  Value.prototype.genCSS = function (context, output) {
26
26
  var i;
27
- for(i = 0; i < this.value.length; i++) {
27
+ for (i = 0; i < this.value.length; i++) {
28
28
  this.value[i].genCSS(context, output);
29
- if (i+1 < this.value.length) {
29
+ if (i + 1 < this.value.length) {
30
30
  output.add((context && context.compress) ? ',' : ', ');
31
31
  }
32
32
  }
@@ -27,7 +27,7 @@ Variable.prototype.eval = function (context) {
27
27
  var v = frame.variable(name);
28
28
  if (v) {
29
29
  if (v.important) {
30
- var importantScope = context.importantScope[context.importantScope.length-1];
30
+ var importantScope = context.importantScope[context.importantScope.length - 1];
31
31
  importantScope.important = v.important;
32
32
  }
33
33
  return v.value.eval(context);
@@ -1,5 +1,6 @@
1
1
  var tree = require("../tree"),
2
- Visitor = require("./visitor");
2
+ Visitor = require("./visitor"),
3
+ logger = require("../logger");
3
4
 
4
5
  /*jshint loopfunc:true */
5
6
 
@@ -30,7 +31,7 @@ ExtendFinderVisitor.prototype = {
30
31
 
31
32
  // get &:extend(.a); rules which apply to all selectors in this ruleset
32
33
  var rules = rulesetNode.rules, ruleCnt = rules ? rules.length : 0;
33
- for(i = 0; i < ruleCnt; i++) {
34
+ for (i = 0; i < ruleCnt; i++) {
34
35
  if (rulesetNode.rules[i] instanceof tree.Extend) {
35
36
  allSelectorsExtendList.push(rules[i]);
36
37
  rulesetNode.extendOnEveryPath = true;
@@ -40,7 +41,7 @@ ExtendFinderVisitor.prototype = {
40
41
  // now find every selector and apply the extends that apply to all extends
41
42
  // and the ones which apply to an individual extend
42
43
  var paths = rulesetNode.paths;
43
- for(i = 0; i < paths.length; i++) {
44
+ for (i = 0; i < paths.length; i++) {
44
45
  var selectorPath = paths[i],
45
46
  selector = selectorPath[selectorPath.length - 1],
46
47
  selExtendList = selector.extendList;
@@ -54,13 +55,13 @@ ExtendFinderVisitor.prototype = {
54
55
  });
55
56
  }
56
57
 
57
- for(j = 0; j < extendList.length; j++) {
58
+ for (j = 0; j < extendList.length; j++) {
58
59
  this.foundExtends = true;
59
60
  extend = extendList[j];
60
61
  extend.findSelfSelectors(selectorPath);
61
62
  extend.ruleset = rulesetNode;
62
63
  if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
63
- this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
64
+ this.allExtendsStack[this.allExtendsStack.length - 1].push(extend);
64
65
  }
65
66
  }
66
67
 
@@ -94,23 +95,44 @@ var ProcessExtendsVisitor = function() {
94
95
  ProcessExtendsVisitor.prototype = {
95
96
  run: function(root) {
96
97
  var extendFinder = new ExtendFinderVisitor();
98
+ this.extendIndicies = {};
97
99
  extendFinder.run(root);
98
100
  if (!extendFinder.foundExtends) { return root; }
99
101
  root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
100
102
  this.allExtendsStack = [root.allExtends];
101
- return this._visitor.visit(root);
103
+ var newRoot = this._visitor.visit(root);
104
+ this.checkExtendsForNonMatched(root.allExtends);
105
+ return newRoot;
106
+ },
107
+ checkExtendsForNonMatched: function(extendList) {
108
+ var indicies = this.extendIndicies;
109
+ extendList.filter(function(extend) {
110
+ return !extend.hasFoundMatches && extend.parent_ids.length == 1;
111
+ }).forEach(function(extend) {
112
+ var selector = "_unknown_";
113
+ try {
114
+ selector = extend.selector.toCSS({});
115
+ }
116
+ catch(_) {}
117
+
118
+ if (!indicies[extend.index + ' ' + selector]) {
119
+ indicies[extend.index + ' ' + selector] = true;
120
+ logger.warn("extend '" + selector + "' has no matches");
121
+ }
122
+ });
102
123
  },
103
124
  doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
104
125
  //
105
- // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
106
- // the selector we would do normally, but we are also adding an extend with the same target selector
126
+ // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering
127
+ // and pasting the selector we would do normally, but we are also adding an extend with the same target selector
107
128
  // this means this new extend can then go and alter other extends
108
129
  //
109
130
  // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
110
- // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
111
- // we look at each selector at a time, as is done in visitRuleset
131
+ // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already
132
+ // processed if we look at each selector at a time, as is done in visitRuleset
112
133
 
113
- var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
134
+ var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath,
135
+ extend, targetExtend, newExtend;
114
136
 
115
137
  iterationCount = iterationCount || 0;
116
138
 
@@ -120,14 +142,14 @@ ProcessExtendsVisitor.prototype = {
120
142
  // and the second is the target.
121
143
  // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
122
144
  // case when processing media queries
123
- for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
124
- for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
145
+ for (extendIndex = 0; extendIndex < extendsList.length; extendIndex++) {
146
+ for (targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++) {
125
147
 
126
148
  extend = extendsList[extendIndex];
127
149
  targetExtend = extendsListTarget[targetExtendIndex];
128
150
 
129
151
  // look for circular references
130
- if( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ){ continue; }
152
+ if ( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ) { continue; }
131
153
 
132
154
  // find a match in the target extends self selector (the bit before :extend)
133
155
  selectorPath = [targetExtend.selfSelectors[0]];
@@ -135,6 +157,8 @@ ProcessExtendsVisitor.prototype = {
135
157
 
136
158
  if (matches.length) {
137
159
 
160
+ extend.hasFoundMatches = true;
161
+
138
162
  // we found a match, so for each self selector..
139
163
  extend.selfSelectors.forEach(function(selfSelector) {
140
164
 
@@ -146,7 +170,7 @@ ProcessExtendsVisitor.prototype = {
146
170
  newExtend.selfSelectors = newSelector;
147
171
 
148
172
  // add the extend onto the list of extends for that selector
149
- newSelector[newSelector.length-1].extendList = [newExtend];
173
+ newSelector[newSelector.length - 1].extendList = [newExtend];
150
174
 
151
175
  // record that we need to add it.
152
176
  extendsToAdd.push(newExtend);
@@ -180,11 +204,13 @@ ProcessExtendsVisitor.prototype = {
180
204
  selectorTwo = extendsToAdd[0].selector.toCSS();
181
205
  }
182
206
  catch(e) {}
183
- throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
207
+ throw { message: "extend circular reference detected. One of the circular extends is currently:" +
208
+ selectorOne + ":extend(" + selectorTwo + ")"};
184
209
  }
185
210
 
186
- // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
187
- return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
211
+ // now process the new extends on the existing rules so that we can handle a extending b extending c extending
212
+ // d extending e...
213
+ return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount + 1));
188
214
  } else {
189
215
  return extendsToAdd;
190
216
  }
@@ -202,22 +228,24 @@ ProcessExtendsVisitor.prototype = {
202
228
  if (rulesetNode.root) {
203
229
  return;
204
230
  }
205
- var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this, selectorPath;
231
+ var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length - 1],
232
+ selectorsToAdd = [], extendVisitor = this, selectorPath;
206
233
 
207
234
  // look at each selector path in the ruleset, find any extend matches and then copy, find and replace
208
235
 
209
- for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
210
- for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
236
+ for (extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
237
+ for (pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
211
238
  selectorPath = rulesetNode.paths[pathIndex];
212
239
 
213
240
  // extending extends happens initially, before the main pass
214
241
  if (rulesetNode.extendOnEveryPath) { continue; }
215
- var extendList = selectorPath[selectorPath.length-1].extendList;
242
+ var extendList = selectorPath[selectorPath.length - 1].extendList;
216
243
  if (extendList && extendList.length) { continue; }
217
244
 
218
245
  matches = this.findMatch(allExtends[extendIndex], selectorPath);
219
246
 
220
247
  if (matches.length) {
248
+ allExtends[extendIndex].hasFoundMatches = true;
221
249
 
222
250
  allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
223
251
  selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
@@ -239,24 +267,25 @@ ProcessExtendsVisitor.prototype = {
239
267
  potentialMatches = [], potentialMatch, matches = [];
240
268
 
241
269
  // loop through the haystack elements
242
- for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
270
+ for (haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
243
271
  hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
244
272
 
245
- for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
273
+ for (hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
246
274
 
247
275
  haystackElement = hackstackSelector.elements[hackstackElementIndex];
248
276
 
249
277
  // if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
250
278
  if (extend.allowBefore || (haystackSelectorIndex === 0 && hackstackElementIndex === 0)) {
251
- potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
279
+ potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0,
280
+ initialCombinator: haystackElement.combinator});
252
281
  }
253
282
 
254
- for(i = 0; i < potentialMatches.length; i++) {
283
+ for (i = 0; i < potentialMatches.length; i++) {
255
284
  potentialMatch = potentialMatches[i];
256
285
 
257
286
  // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
258
- // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
259
- // what the resulting combinator will be
287
+ // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to
288
+ // work out what the resulting combinator will be
260
289
  targetCombinator = haystackElement.combinator.value;
261
290
  if (targetCombinator === '' && hackstackElementIndex === 0) {
262
291
  targetCombinator = ' ';
@@ -274,7 +303,8 @@ ProcessExtendsVisitor.prototype = {
274
303
  if (potentialMatch) {
275
304
  potentialMatch.finished = potentialMatch.matched === needleElements.length;
276
305
  if (potentialMatch.finished &&
277
- (!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
306
+ (!extend.allowAfter &&
307
+ (hackstackElementIndex + 1 < hackstackSelector.elements.length || haystackSelectorIndex + 1 < haystackSelectorPath.length))) {
278
308
  potentialMatch = null;
279
309
  }
280
310
  }
@@ -320,7 +350,7 @@ ProcessExtendsVisitor.prototype = {
320
350
  if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
321
351
  return false;
322
352
  }
323
- for(var i = 0; i <elementValue1.elements.length; i++) {
353
+ for (var i = 0; i < elementValue1.elements.length; i++) {
324
354
  if (elementValue1.elements[i].combinator.value !== elementValue2.elements[i].combinator.value) {
325
355
  if (i !== 0 || (elementValue1.elements[i].combinator.value || ' ') !== (elementValue2.elements[i].combinator.value || ' ')) {
326
356
  return false;
@@ -358,7 +388,8 @@ ProcessExtendsVisitor.prototype = {
358
388
  );
359
389
 
360
390
  if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
361
- path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
391
+ path[path.length - 1].elements = path[path.length - 1]
392
+ .elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
362
393
  currentSelectorPathElementIndex = 0;
363
394
  currentSelectorPathIndex++;
364
395
  }
@@ -387,7 +418,8 @@ ProcessExtendsVisitor.prototype = {
387
418
  }
388
419
 
389
420
  if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
390
- path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
421
+ path[path.length - 1].elements = path[path.length - 1]
422
+ .elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
391
423
  currentSelectorPathIndex++;
392
424
  }
393
425
 
@@ -398,20 +430,24 @@ ProcessExtendsVisitor.prototype = {
398
430
  visitRulesetOut: function (rulesetNode) {
399
431
  },
400
432
  visitMedia: function (mediaNode, visitArgs) {
401
- var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
433
+ var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length - 1]);
402
434
  newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
403
435
  this.allExtendsStack.push(newAllExtends);
404
436
  },
405
437
  visitMediaOut: function (mediaNode) {
406
- this.allExtendsStack.length = this.allExtendsStack.length - 1;
438
+ var lastIndex = this.allExtendsStack.length - 1;
439
+ this.checkExtendsForNonMatched(this.allExtendsStack[lastIndex]);
440
+ this.allExtendsStack.length = lastIndex;
407
441
  },
408
442
  visitDirective: function (directiveNode, visitArgs) {
409
- var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
443
+ var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length - 1]);
410
444
  newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
411
445
  this.allExtendsStack.push(newAllExtends);
412
446
  },
413
447
  visitDirectiveOut: function (directiveNode) {
414
- this.allExtendsStack.length = this.allExtendsStack.length - 1;
448
+ var lastIndex = this.allExtendsStack.length - 1;
449
+ this.checkExtendsForNonMatched(this.allExtendsStack[lastIndex]);
450
+ this.allExtendsStack.length = lastIndex;
415
451
  }
416
452
  };
417
453
 
@@ -2,6 +2,7 @@ function ImportSequencer(onSequencerEmpty) {
2
2
  this.imports = [];
3
3
  this.variableImports = [];
4
4
  this._onSequencerEmpty = onSequencerEmpty;
5
+ this._currentDepth = 0;
5
6
  }
6
7
 
7
8
  ImportSequencer.prototype.addImport = function(callback) {
@@ -24,23 +25,28 @@ ImportSequencer.prototype.addVariableImport = function(callback) {
24
25
  };
25
26
 
26
27
  ImportSequencer.prototype.tryRun = function() {
27
- while(true) {
28
- while(this.imports.length > 0) {
29
- var importItem = this.imports[0];
30
- if (!importItem.isReady) {
31
- return;
28
+ this._currentDepth++;
29
+ try {
30
+ while (true) {
31
+ while (this.imports.length > 0) {
32
+ var importItem = this.imports[0];
33
+ if (!importItem.isReady) {
34
+ return;
35
+ }
36
+ this.imports = this.imports.slice(1);
37
+ importItem.callback.apply(null, importItem.args);
32
38
  }
33
- this.imports = this.imports.slice(1);
34
- importItem.callback.apply(null, importItem.args);
35
- }
36
- if (this.variableImports.length === 0) {
37
- break;
39
+ if (this.variableImports.length === 0) {
40
+ break;
41
+ }
42
+ var variableImport = this.variableImports[0];
43
+ this.variableImports = this.variableImports.slice(1);
44
+ variableImport();
38
45
  }
39
- var variableImport = this.variableImports[0];
40
- this.variableImports = this.variableImports.slice(1);
41
- variableImport();
46
+ } finally {
47
+ this._currentDepth--;
42
48
  }
43
- if (this._onSequencerEmpty) {
49
+ if (this._currentDepth === 0 && this._onSequencerEmpty) {
44
50
  this._onSequencerEmpty();
45
51
  }
46
52
  };
@@ -11,26 +11,28 @@ var ImportVisitor = function(importer, finish) {
11
11
  this.importCount = 0;
12
12
  this.onceFileDetectionMap = {};
13
13
  this.recursionDetector = {};
14
- this._sequencer = new ImportSequencer();
14
+ this._sequencer = new ImportSequencer(this._onSequencerEmpty.bind(this));
15
15
  };
16
16
 
17
17
  ImportVisitor.prototype = {
18
18
  isReplacing: false,
19
19
  run: function (root) {
20
- var error;
21
20
  try {
22
21
  // process the contents
23
22
  this._visitor.visit(root);
24
23
  }
25
24
  catch(e) {
26
- error = e;
25
+ this.error = e;
27
26
  }
28
27
 
29
28
  this.isFinished = true;
30
29
  this._sequencer.tryRun();
31
- if (this.importCount === 0) {
32
- this._finish(error || this.error);
30
+ },
31
+ _onSequencerEmpty: function() {
32
+ if (!this.isFinished) {
33
+ return;
33
34
  }
35
+ this._finish(this.error);
34
36
  },
35
37
  visitImport: function (importNode, visitArgs) {
36
38
  var inlineCSS = importNode.options.inline;
@@ -55,7 +57,7 @@ ImportVisitor.prototype = {
55
57
 
56
58
  try {
57
59
  evaldImportNode = importNode.evalForImport(context);
58
- } catch(e){
60
+ } catch(e) {
59
61
  if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
60
62
  // attempt to eval properly and treat as css
61
63
  importNode.css = true;
@@ -72,7 +74,7 @@ ImportVisitor.prototype = {
72
74
  // try appending if we haven't determined if it is css or not
73
75
  var tryAppendLessExtension = evaldImportNode.css === undefined;
74
76
 
75
- for(var i = 0; i < importParent.rules.length; i++) {
77
+ for (var i = 0; i < importParent.rules.length; i++) {
76
78
  if (importParent.rules[i] === importNode) {
77
79
  importParent.rules[i] = evaldImportNode;
78
80
  break;
@@ -82,9 +84,13 @@ ImportVisitor.prototype = {
82
84
  var onImported = this.onImported.bind(this, evaldImportNode, context),
83
85
  sequencedOnImported = this._sequencer.addImport(onImported);
84
86
 
85
- this._importer.push(evaldImportNode.getPath(), tryAppendLessExtension, evaldImportNode.currentFileInfo, evaldImportNode.options, sequencedOnImported);
87
+ this._importer.push(evaldImportNode.getPath(), tryAppendLessExtension, evaldImportNode.currentFileInfo,
88
+ evaldImportNode.options, sequencedOnImported);
86
89
  } else {
87
90
  this.importCount--;
91
+ if (this.isFinished) {
92
+ this._sequencer.tryRun();
93
+ }
88
94
  }
89
95
  },
90
96
  onImported: function (importNode, context, e, root, importedAtRoot, fullPath) {
@@ -134,10 +140,7 @@ ImportVisitor.prototype = {
134
140
  importVisitor.importCount--;
135
141
 
136
142
  if (importVisitor.isFinished) {
137
- this._sequencer.tryRun();
138
- if (importVisitor.importCount === 0) {
139
- importVisitor._finish(importVisitor.error);
140
- }
143
+ importVisitor._sequencer.tryRun();
141
144
  }
142
145
  },
143
146
  visitRule: function (ruleNode, visitArgs) {
@@ -46,16 +46,16 @@ ToCSSVisitor.prototype = {
46
46
  },
47
47
 
48
48
  visitDirective: function(directiveNode, visitArgs) {
49
- if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) {
50
- return;
51
- }
52
49
  if (directiveNode.name === "@charset") {
50
+ if (!directiveNode.getIsReferenced()) {
51
+ return;
52
+ }
53
53
  // Only output the debug info together with subsequent @charset definitions
54
54
  // a comment (or @media statement) before the actual @charset directive would
55
55
  // be considered illegal css as it has to be on the first line
56
56
  if (this.charset) {
57
57
  if (directiveNode.debugInfo) {
58
- var comment = new tree.Comment("/* " + directiveNode.toCSS(this._context).replace(/\n/g, "")+" */\n");
58
+ var comment = new tree.Comment("/* " + directiveNode.toCSS(this._context).replace(/\n/g, "") + " */\n");
59
59
  comment.debugInfo = directiveNode.debugInfo;
60
60
  return this._visitor.visit(comment);
61
61
  }
@@ -65,13 +65,45 @@ ToCSSVisitor.prototype = {
65
65
  }
66
66
  if (directiveNode.rules && directiveNode.rules.rules) {
67
67
  this._mergeRules(directiveNode.rules.rules);
68
+ //process childs
69
+ directiveNode.accept(this._visitor);
70
+ visitArgs.visitDeeper = false;
71
+
72
+ // the directive was directly referenced and therefore needs to be shown in the output
73
+ if (directiveNode.getIsReferenced()) {
74
+ return directiveNode;
75
+ }
76
+
77
+ if (!directiveNode.rules.rules) {
78
+ return ;
79
+ }
80
+
81
+ //the directive was not directly referenced
82
+ for (var r = 0; r < directiveNode.rules.rules.length; r++) {
83
+ var rule = directiveNode.rules.rules[r];
84
+ if (rule.getIsReferenced && rule.getIsReferenced()) {
85
+ //the directive contains something that was referenced (likely by extend)
86
+ //therefore it needs to be shown in output too
87
+
88
+ //marking as referenced in case the directive is stored inside another directive
89
+ directiveNode.markReferenced();
90
+ return directiveNode;
91
+ }
92
+ }
93
+ //The directive was not directly referenced and does not contain anything that
94
+ //was referenced. Therefore it must not be shown in output.
95
+ return ;
96
+ } else {
97
+ if (!directiveNode.getIsReferenced()) {
98
+ return;
99
+ }
68
100
  }
69
101
  return directiveNode;
70
102
  },
71
103
 
72
104
  checkPropertiesInRoot: function(rules) {
73
105
  var ruleNode;
74
- for(var i = 0; i < rules.length; i++) {
106
+ for (var i = 0; i < rules.length; i++) {
75
107
  ruleNode = rules[i];
76
108
  if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
77
109
  throw { message: "properties must be inside selector blocks, they cannot be in the root.",
@@ -93,7 +125,7 @@ ToCSSVisitor.prototype = {
93
125
  if (p[0].elements[0].combinator.value === ' ') {
94
126
  p[0].elements[0].combinator = new(tree.Combinator)('');
95
127
  }
96
- for(i = 0; i < p.length; i++) {
128
+ for (i = 0; i < p.length; i++) {
97
129
  if (p[i].getIsReferenced() && p[i].getIsOutput()) {
98
130
  return true;
99
131
  }
@@ -158,7 +190,7 @@ ToCSSVisitor.prototype = {
158
190
  var ruleCache = {},
159
191
  ruleList, rule, i;
160
192
 
161
- for(i = rules.length - 1; i >= 0 ; i--) {
193
+ for (i = rules.length - 1; i >= 0 ; i--) {
162
194
  rule = rules[i];
163
195
  if (rule instanceof tree.Rule) {
164
196
  if (!ruleCache[rule.name]) {
@@ -225,8 +257,8 @@ ToCSSVisitor.prototype = {
225
257
  var spacedGroups = [];
226
258
  var lastSpacedGroup = [];
227
259
  parts.map(function (p) {
228
- if (p.merge==="+") {
229
- if (lastSpacedGroup.length > 0) {
260
+ if (p.merge === "+") {
261
+ if (lastSpacedGroup.length > 0) {
230
262
  spacedGroups.push(toExpression(lastSpacedGroup));
231
263
  }
232
264
  lastSpacedGroup = [];
@@ -7,7 +7,7 @@ module.exports = function(window, options) {
7
7
  addDataAttr(options, browser.currentScript(window));
8
8
 
9
9
  if (options.isFileProtocol === undefined) {
10
- options.isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(window.location.protocol);
10
+ options.isFileProtocol = /^(file|(chrome|safari)(-extension)?|resource|qrc|app):/.test(window.location.protocol);
11
11
  }
12
12
 
13
13
  // Load styles asynchronously (default: false)
@@ -39,4 +39,8 @@ module.exports = function(window, options) {
39
39
  options.useFileCache = true;
40
40
  }
41
41
 
42
+ if (options.onReady === undefined) {
43
+ options.onReady = true;
44
+ }
45
+
42
46
  };
@@ -13,12 +13,14 @@ require("./add-default-options")(window, options);
13
13
 
14
14
  var less = module.exports = require("./index")(window, options);
15
15
 
16
- if (/!watch/.test(window.location.hash)) {
17
- less.watch();
18
- }
19
-
20
- less.pageLoadFinished = less.registerStylesheets().then(
21
- function () {
22
- return less.refresh(less.env === 'development');
16
+ if (options.onReady) {
17
+ if (/!watch/.test(window.location.hash)) {
18
+ less.watch();
23
19
  }
24
- );
20
+
21
+ less.pageLoadFinished = less.registerStylesheets().then(
22
+ function () {
23
+ return less.refresh(less.env === 'development');
24
+ }
25
+ );
26
+ }
@@ -4,19 +4,19 @@ module.exports = function(window, options, logger) {
4
4
  var cache = null;
5
5
  if (options.env !== 'development') {
6
6
  try {
7
- cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
7
+ cache = (typeof window.localStorage === 'undefined') ? null : window.localStorage;
8
8
  } catch (_) {}
9
9
  }
10
10
  return {
11
11
  setCSS: function(path, lastModified, styles) {
12
12
  if (cache) {
13
- logger.info('saving ' + path+ ' to cache.');
13
+ logger.info('saving ' + path + ' to cache.');
14
14
  try {
15
15
  cache.setItem(path, styles);
16
16
  cache.setItem(path + ':timestamp', lastModified);
17
17
  } catch(e) {
18
18
  //TODO - could do with adding more robust error handling
19
- logger.error('failed to save');
19
+ logger.error('failed to save "' + path + '" to local storage for caching.');
20
20
  }
21
21
  }
22
22
  },