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
@@ -2,6 +2,7 @@ var Node = require("./node"),
2
2
  Rule = require("./rule"),
3
3
  Selector = require("./selector"),
4
4
  Element = require("./element"),
5
+ Paren = require("./paren"),
5
6
  contexts = require("../contexts"),
6
7
  defaultFunc = require("../functions/default"),
7
8
  getDebugInfo = require("./debug-info");
@@ -57,7 +58,7 @@ Ruleset.prototype.eval = function (context) {
57
58
  ruleset.firstRoot = this.firstRoot;
58
59
  ruleset.allowImports = this.allowImports;
59
60
 
60
- if(this.debugInfo) {
61
+ if (this.debugInfo) {
61
62
  ruleset.debugInfo = this.debugInfo;
62
63
  }
63
64
 
@@ -107,7 +108,7 @@ Ruleset.prototype.eval = function (context) {
107
108
  });
108
109
  rsRules.splice.apply(rsRules, [i, 1].concat(rules));
109
110
  rsRuleCnt += rules.length - 1;
110
- i += rules.length-1;
111
+ i += rules.length - 1;
111
112
  ruleset.resetCache();
112
113
  } else if (rsRules[i].type === "RulesetCall") {
113
114
  /*jshint loopfunc:true */
@@ -120,7 +121,7 @@ Ruleset.prototype.eval = function (context) {
120
121
  });
121
122
  rsRules.splice.apply(rsRules, [i, 1].concat(rules));
122
123
  rsRuleCnt += rules.length - 1;
123
- i += rules.length-1;
124
+ i += rules.length - 1;
124
125
  ruleset.resetCache();
125
126
  }
126
127
  }
@@ -142,7 +143,7 @@ Ruleset.prototype.eval = function (context) {
142
143
  if (rule.selectors[0].isJustParentSelector()) {
143
144
  rsRules.splice(i--, 1);
144
145
 
145
- for(var j = 0; j < rule.rules.length; j++) {
146
+ for (var j = 0; j < rule.rules.length; j++) {
146
147
  subRule = rule.rules[j];
147
148
  if (!(subRule instanceof Rule) || !subRule.variable) {
148
149
  rsRules.splice(++i, 0, subRule);
@@ -173,7 +174,7 @@ Ruleset.prototype.evalImports = function(context) {
173
174
  importRules = rules[i].eval(context);
174
175
  if (importRules && importRules.length) {
175
176
  rules.splice.apply(rules, [i, 1].concat(importRules));
176
- i+= importRules.length-1;
177
+ i+= importRules.length - 1;
177
178
  } else {
178
179
  rules.splice(i, 1, importRules);
179
180
  }
@@ -182,20 +183,22 @@ Ruleset.prototype.evalImports = function(context) {
182
183
  }
183
184
  };
184
185
  Ruleset.prototype.makeImportant = function() {
185
- return new Ruleset(this.selectors, this.rules.map(function (r) {
186
- if (r.makeImportant) {
187
- return r.makeImportant();
188
- } else {
189
- return r;
190
- }
191
- }), this.strictImports);
186
+ var result = new Ruleset(this.selectors, this.rules.map(function (r) {
187
+ if (r.makeImportant) {
188
+ return r.makeImportant();
189
+ } else {
190
+ return r;
191
+ }
192
+ }), this.strictImports);
193
+
194
+ return result;
192
195
  };
193
196
  Ruleset.prototype.matchArgs = function (args) {
194
197
  return !args || args.length === 0;
195
198
  };
196
199
  // lets you call a css selector with a guard
197
200
  Ruleset.prototype.matchCondition = function (args, context) {
198
- var lastSelector = this.selectors[this.selectors.length-1];
201
+ var lastSelector = this.selectors[this.selectors.length - 1];
199
202
  if (!lastSelector.evaldCondition) {
200
203
  return false;
201
204
  }
@@ -220,9 +223,10 @@ Ruleset.prototype.variables = function () {
220
223
  }
221
224
  // when evaluating variables in an import statement, imports have not been eval'd
222
225
  // so we need to go inside import statements.
223
- if (r.type === "Import" && r.root) {
226
+ // guard against root being a string (in the case of inlined less)
227
+ if (r.type === "Import" && r.root && r.root.variables) {
224
228
  var vars = r.root.variables();
225
- for(var name in vars) {
229
+ for (var name in vars) {
226
230
  if (vars.hasOwnProperty(name)) {
227
231
  hash[name] = vars[name];
228
232
  }
@@ -253,7 +257,11 @@ Ruleset.prototype.rulesets = function () {
253
257
  };
254
258
  Ruleset.prototype.prependRule = function (rule) {
255
259
  var rules = this.rules;
256
- if (rules) { rules.unshift(rule); } else { this.rules = [ rule ]; }
260
+ if (rules) {
261
+ rules.unshift(rule);
262
+ } else {
263
+ this.rules = [ rule ];
264
+ }
257
265
  };
258
266
  Ruleset.prototype.find = function (selector, self, filter) {
259
267
  self = self || this;
@@ -268,13 +276,13 @@ Ruleset.prototype.find = function (selector, self, filter) {
268
276
  match = selector.match(rule.selectors[j]);
269
277
  if (match) {
270
278
  if (selector.elements.length > match) {
271
- if (!filter || filter(rule)) {
272
- foundMixins = rule.find(new Selector(selector.elements.slice(match)), self, filter);
273
- for (var i = 0; i < foundMixins.length; ++i) {
274
- foundMixins[i].path.push(rule);
279
+ if (!filter || filter(rule)) {
280
+ foundMixins = rule.find(new Selector(selector.elements.slice(match)), self, filter);
281
+ for (var i = 0; i < foundMixins.length; ++i) {
282
+ foundMixins[i].path.push(rule);
283
+ }
284
+ Array.prototype.push.apply(rules, foundMixins);
275
285
  }
276
- Array.prototype.push.apply(rules, foundMixins);
277
- }
278
286
  } else {
279
287
  rules.push({ rule: rule, path: []});
280
288
  }
@@ -307,19 +315,17 @@ Ruleset.prototype.genCSS = function (context, output) {
307
315
  sep;
308
316
 
309
317
  function isRulesetLikeNode(rule, root) {
310
- // if it has nested rules, then it should be treated like a ruleset
311
- // medias and comments do not have nested rules, but should be treated like rulesets anyway
312
- // some directives and anonymous nodes are ruleset like, others are not
313
- if (typeof rule.isRulesetLike === "boolean")
314
- {
315
- return rule.isRulesetLike;
316
- } else if (typeof rule.isRulesetLike === "function")
317
- {
318
- return rule.isRulesetLike(root);
319
- }
318
+ // if it has nested rules, then it should be treated like a ruleset
319
+ // medias and comments do not have nested rules, but should be treated like rulesets anyway
320
+ // some directives and anonymous nodes are ruleset like, others are not
321
+ if (typeof rule.isRulesetLike === "boolean") {
322
+ return rule.isRulesetLike;
323
+ } else if (typeof rule.isRulesetLike === "function") {
324
+ return rule.isRulesetLike(root);
325
+ }
320
326
 
321
- //anything else is assumed to be a rule
322
- return false;
327
+ //anything else is assumed to be a rule
328
+ return false;
323
329
  }
324
330
 
325
331
  for (i = 0; i < this.rules.length; i++) {
@@ -413,189 +419,296 @@ Ruleset.prototype.genCSS = function (context, output) {
413
419
  }
414
420
  };
415
421
  Ruleset.prototype.markReferenced = function () {
416
- if (!this.selectors) {
417
- return;
422
+ var s;
423
+ if (this.selectors) {
424
+ for (s = 0; s < this.selectors.length; s++) {
425
+ this.selectors[s].markReferenced();
426
+ }
418
427
  }
419
- for (var s = 0; s < this.selectors.length; s++) {
420
- this.selectors[s].markReferenced();
428
+
429
+ if (this.rules) {
430
+ for (s = 0; s < this.rules.length; s++) {
431
+ if (this.rules[s].markReferenced) {
432
+ this.rules[s].markReferenced();
433
+ }
434
+ }
421
435
  }
422
436
  };
437
+ Ruleset.prototype.getIsReferenced = function() {
438
+ var i, j, path, selector;
439
+
440
+ if (this.paths) {
441
+ for (i = 0; i < this.paths.length; i++) {
442
+ path = this.paths[i];
443
+ for (j = 0; j < path.length; j++) {
444
+ if (path[j].getIsReferenced && path[j].getIsReferenced()) {
445
+ return true;
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ if (this.selectors) {
452
+ for (i = 0; i < this.selectors.length; i++) {
453
+ selector = this.selectors[i];
454
+ if (selector.getIsReferenced && selector.getIsReferenced()) {
455
+ return true;
456
+ }
457
+ }
458
+ }
459
+ return false;
460
+ };
461
+
423
462
  Ruleset.prototype.joinSelectors = function (paths, context, selectors) {
424
463
  for (var s = 0; s < selectors.length; s++) {
425
464
  this.joinSelector(paths, context, selectors[s]);
426
465
  }
427
466
  };
428
- Ruleset.prototype.joinSelector = function (paths, context, selector) {
429
-
430
- var i, j, k,
431
- hasParentSelector, newSelectors, el, sel, parentSel,
432
- newSelectorPath, afterParentJoin, newJoinedSelector,
433
- newJoinedSelectorEmpty, lastSelector, currentElements,
434
- selectorsMultiplied;
435
467
 
436
- for (i = 0; i < selector.elements.length; i++) {
437
- el = selector.elements[i];
438
- if (el.value === '&') {
439
- hasParentSelector = true;
440
- }
441
- }
468
+ Ruleset.prototype.joinSelector = function (paths, context, selector) {
442
469
 
443
- if (!hasParentSelector) {
444
- if (context.length > 0) {
445
- for (i = 0; i < context.length; i++) {
446
- paths.push(context[i].concat(selector));
470
+ function createParenthesis(elementsToPak, originalElement) {
471
+ var replacementParen, j;
472
+ if (elementsToPak.length === 0) {
473
+ replacementParen = new Paren(elementsToPak[0]);
474
+ } else {
475
+ var insideParent = [];
476
+ for (j = 0; j < elementsToPak.length; j++) {
477
+ insideParent.push(new Element(null, elementsToPak[j], originalElement.index, originalElement.currentFileInfo));
447
478
  }
479
+ replacementParen = new Paren(new Selector(insideParent));
448
480
  }
449
- else {
450
- paths.push([selector]);
451
- }
452
- return;
453
- }
454
-
455
- // The paths are [[Selector]]
456
- // The first list is a list of comma separated selectors
457
- // The inner list is a list of inheritance separated selectors
458
- // e.g.
459
- // .a, .b {
460
- // .c {
461
- // }
462
- // }
463
- // == [[.a] [.c]] [[.b] [.c]]
464
- //
465
-
466
- // the elements from the current selector so far
467
- currentElements = [];
468
- // the current list of new selectors to add to the path.
469
- // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
470
- // by the parents
471
- newSelectors = [[]];
472
-
473
- for (i = 0; i < selector.elements.length; i++) {
474
- el = selector.elements[i];
475
- // non parent reference elements just get added
476
- if (el.value !== "&") {
477
- currentElements.push(el);
478
- } else {
479
- // the new list of selectors to add
480
- selectorsMultiplied = [];
481
+ return replacementParen;
482
+ }
483
+
484
+ function createSelector(containedElement, originalElement) {
485
+ var element, selector;
486
+ element = new Element(null, containedElement, originalElement.index, originalElement.currentFileInfo);
487
+ selector = new Selector([element]);
488
+ return selector;
489
+ }
490
+
491
+ // replace all parent selectors inside `inSelector` by content of `context` array
492
+ // resulting selectors are returned inside `paths` array
493
+ // returns true if `inSelector` contained at least one parent selector
494
+ function replaceParentSelector(paths, context, inSelector) {
495
+ // The paths are [[Selector]]
496
+ // The first list is a list of comma separated selectors
497
+ // The inner list is a list of inheritance separated selectors
498
+ // e.g.
499
+ // .a, .b {
500
+ // .c {
501
+ // }
502
+ // }
503
+ // == [[.a] [.c]] [[.b] [.c]]
504
+ //
505
+ var i, j, k, currentElements, newSelectors, selectorsMultiplied, sel, el, hadParentSelector = false, length, lastSelector;
506
+ function findNestedSelector(element) {
507
+ var maybeSelector;
508
+ if (element.value.type !== 'Paren') {
509
+ return null;
510
+ }
481
511
 
482
- // merge the current list of non parent selector elements
483
- // on to the current list of selectors to add
484
- if (currentElements.length > 0) {
485
- this.mergeElementsOnToSelectors(currentElements, newSelectors);
512
+ maybeSelector = element.value.value;
513
+ if (maybeSelector.type !== 'Selector') {
514
+ return null;
486
515
  }
487
516
 
488
- // loop through our current selectors
489
- for (j = 0; j < newSelectors.length; j++) {
490
- sel = newSelectors[j];
491
- // if we don't have any parent paths, the & might be in a mixin so that it can be used
492
- // whether there are parents or not
493
- if (context.length === 0) {
494
- // the combinator used on el should now be applied to the next element instead so that
495
- // it is not lost
496
- if (sel.length > 0) {
497
- sel[0].elements = sel[0].elements.slice(0);
498
- sel[0].elements.push(new Element(el.combinator, '', el.index, el.currentFileInfo));
517
+ return maybeSelector;
518
+ }
519
+
520
+ // the elements from the current selector so far
521
+ currentElements = [];
522
+ // the current list of new selectors to add to the path.
523
+ // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
524
+ // by the parents
525
+ newSelectors = [
526
+ []
527
+ ];
528
+
529
+ for (i = 0; i < inSelector.elements.length; i++) {
530
+ el = inSelector.elements[i];
531
+ // non parent reference elements just get added
532
+ if (el.value !== "&") {
533
+ var nestedSelector = findNestedSelector(el);
534
+ if (nestedSelector != null) {
535
+ // merge the current list of non parent selector elements
536
+ // on to the current list of selectors to add
537
+ mergeElementsOnToSelectors(currentElements, newSelectors);
538
+
539
+ var nestedPaths = [], replaced, replacedNewSelectors = [];
540
+ replaced = replaceParentSelector(nestedPaths, context, nestedSelector);
541
+ hadParentSelector = hadParentSelector || replaced;
542
+ //the nestedPaths array should have only one member - replaceParentSelector does not multiply selectors
543
+ for (k = 0; k < nestedPaths.length; k++) {
544
+ var replacementSelector = createSelector(createParenthesis(nestedPaths[k], el), el);
545
+ addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector, replacedNewSelectors);
499
546
  }
500
- selectorsMultiplied.push(sel);
547
+ newSelectors = replacedNewSelectors;
548
+ currentElements = [];
549
+
550
+ } else {
551
+ currentElements.push(el);
501
552
  }
502
- else {
503
- // and the parent selectors
504
- for (k = 0; k < context.length; k++) {
505
- parentSel = context[k];
506
- // We need to put the current selectors
507
- // then join the last selector's elements on to the parents selectors
508
-
509
- // our new selector path
510
- newSelectorPath = [];
511
- // selectors from the parent after the join
512
- afterParentJoin = [];
513
- newJoinedSelectorEmpty = true;
514
-
515
- //construct the joined selector - if & is the first thing this will be empty,
516
- // if not newJoinedSelector will be the last set of elements in the selector
553
+
554
+ } else {
555
+ hadParentSelector = true;
556
+ // the new list of selectors to add
557
+ selectorsMultiplied = [];
558
+
559
+ // merge the current list of non parent selector elements
560
+ // on to the current list of selectors to add
561
+ mergeElementsOnToSelectors(currentElements, newSelectors);
562
+
563
+ // loop through our current selectors
564
+ for (j = 0; j < newSelectors.length; j++) {
565
+ sel = newSelectors[j];
566
+ // if we don't have any parent paths, the & might be in a mixin so that it can be used
567
+ // whether there are parents or not
568
+ if (context.length === 0) {
569
+ // the combinator used on el should now be applied to the next element instead so that
570
+ // it is not lost
517
571
  if (sel.length > 0) {
518
- newSelectorPath = sel.slice(0);
519
- lastSelector = newSelectorPath.pop();
520
- newJoinedSelector = selector.createDerived(lastSelector.elements.slice(0));
521
- newJoinedSelectorEmpty = false;
572
+ sel[0].elements.push(new Element(el.combinator, '', el.index, el.currentFileInfo));
522
573
  }
523
- else {
524
- newJoinedSelector = selector.createDerived([]);
574
+ selectorsMultiplied.push(sel);
575
+ }
576
+ else {
577
+ // and the parent selectors
578
+ for (k = 0; k < context.length; k++) {
579
+ // We need to put the current selectors
580
+ // then join the last selector's elements on to the parents selectors
581
+ var newSelectorPath = addReplacementIntoPath(sel, context[k], el, inSelector);
582
+ // add that to our new set of selectors
583
+ selectorsMultiplied.push(newSelectorPath);
525
584
  }
585
+ }
586
+ }
526
587
 
527
- //put together the parent selectors after the join
528
- if (parentSel.length > 1) {
529
- afterParentJoin = afterParentJoin.concat(parentSel.slice(1));
530
- }
588
+ // our new selectors has been multiplied, so reset the state
589
+ newSelectors = selectorsMultiplied;
590
+ currentElements = [];
591
+ }
592
+ }
531
593
 
532
- if (parentSel.length > 0) {
533
- newJoinedSelectorEmpty = false;
534
-
535
- // /deep/ is a combinator that is valid without anything in front of it
536
- // so if the & does not have a combinator that is "" or " " then
537
- // and there is a combinator on the parent, then grab that.
538
- // this also allows + a { & .b { .a & { ... though not sure why you would want to do that
539
- var combinator = el.combinator,
540
- parentEl = parentSel[0].elements[0];
541
- if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) {
542
- combinator = parentEl.combinator;
543
- }
544
- // join the elements so far with the first part of the parent
545
- newJoinedSelector.elements.push(new Element(combinator, parentEl.value, el.index, el.currentFileInfo));
546
- newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1));
547
- }
594
+ // if we have any elements left over (e.g. .a& .b == .b)
595
+ // add them on to all the current selectors
596
+ mergeElementsOnToSelectors(currentElements, newSelectors);
548
597
 
549
- if (!newJoinedSelectorEmpty) {
550
- // now add the joined selector
551
- newSelectorPath.push(newJoinedSelector);
552
- }
598
+ for (i = 0; i < newSelectors.length; i++) {
599
+ length = newSelectors[i].length;
600
+ if (length > 0) {
601
+ paths.push(newSelectors[i]);
602
+ lastSelector = newSelectors[i][length - 1];
603
+ newSelectors[i][length - 1] = lastSelector.createDerived(lastSelector.elements, inSelector.extendList);
604
+ }
605
+ }
553
606
 
554
- // and the rest of the parent
555
- newSelectorPath = newSelectorPath.concat(afterParentJoin);
607
+ return hadParentSelector;
608
+ }
556
609
 
557
- // add that to our new set of selectors
558
- selectorsMultiplied.push(newSelectorPath);
559
- }
560
- }
610
+ // joins selector path from `beginningPath` with selector path in `addPath`
611
+ // `replacedElement` contains element that is being replaced by `addPath`
612
+ // returns concatenated path
613
+ function addReplacementIntoPath(beginningPath, addPath, replacedElement, originalSelector) {
614
+ var newSelectorPath, lastSelector, newJoinedSelector;
615
+ // our new selector path
616
+ newSelectorPath = [];
617
+
618
+ //construct the joined selector - if & is the first thing this will be empty,
619
+ // if not newJoinedSelector will be the last set of elements in the selector
620
+ if (beginningPath.length > 0) {
621
+ newSelectorPath = beginningPath.slice(0);
622
+ lastSelector = newSelectorPath.pop();
623
+ newJoinedSelector = originalSelector.createDerived(lastSelector.elements.slice(0));
624
+ }
625
+ else {
626
+ newJoinedSelector = originalSelector.createDerived([]);
627
+ }
628
+
629
+ if (addPath.length > 0) {
630
+ // /deep/ is a combinator that is valid without anything in front of it
631
+ // so if the & does not have a combinator that is "" or " " then
632
+ // and there is a combinator on the parent, then grab that.
633
+ // this also allows + a { & .b { .a & { ... though not sure why you would want to do that
634
+ var combinator = replacedElement.combinator, parentEl = addPath[0].elements[0];
635
+ if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) {
636
+ combinator = parentEl.combinator;
561
637
  }
638
+ // join the elements so far with the first part of the parent
639
+ newJoinedSelector.elements.push(new Element(combinator, parentEl.value, replacedElement.index, replacedElement.currentFileInfo));
640
+ newJoinedSelector.elements = newJoinedSelector.elements.concat(addPath[0].elements.slice(1));
641
+ }
562
642
 
563
- // our new selectors has been multiplied, so reset the state
564
- newSelectors = selectorsMultiplied;
565
- currentElements = [];
643
+ // now add the joined selector - but only if it is not empty
644
+ if (newJoinedSelector.elements.length !== 0) {
645
+ newSelectorPath.push(newJoinedSelector);
566
646
  }
567
- }
568
647
 
569
- // if we have any elements left over (e.g. .a& .b == .b)
570
- // add them on to all the current selectors
571
- if (currentElements.length > 0) {
572
- this.mergeElementsOnToSelectors(currentElements, newSelectors);
648
+ //put together the parent selectors after the join (e.g. the rest of the parent)
649
+ if (addPath.length > 1) {
650
+ newSelectorPath = newSelectorPath.concat(addPath.slice(1));
651
+ }
652
+ return newSelectorPath;
573
653
  }
574
654
 
575
- for (i = 0; i < newSelectors.length; i++) {
576
- if (newSelectors[i].length > 0) {
577
- paths.push(newSelectors[i]);
655
+ // joins selector path from `beginningPath` with every selector path in `addPaths` array
656
+ // `replacedElement` contains element that is being replaced by `addPath`
657
+ // returns array with all concatenated paths
658
+ function addAllReplacementsIntoPath( beginningPath, addPaths, replacedElement, originalSelector, result) {
659
+ var j;
660
+ for (j = 0; j < beginningPath.length; j++) {
661
+ var newSelectorPath = addReplacementIntoPath(beginningPath[j], addPaths, replacedElement, originalSelector);
662
+ result.push(newSelectorPath);
578
663
  }
664
+ return result;
579
665
  }
580
- };
581
- Ruleset.prototype.mergeElementsOnToSelectors = function(elements, selectors) {
582
- var i, sel;
583
666
 
584
- if (selectors.length === 0) {
585
- selectors.push([ new Selector(elements) ]);
586
- return;
667
+ function mergeElementsOnToSelectors(elements, selectors) {
668
+ var i, sel;
669
+
670
+ if (elements.length === 0) {
671
+ return ;
672
+ }
673
+ if (selectors.length === 0) {
674
+ selectors.push([ new Selector(elements) ]);
675
+ return;
676
+ }
677
+
678
+ for (i = 0; i < selectors.length; i++) {
679
+ sel = selectors[i];
680
+
681
+ // if the previous thing in sel is a parent this needs to join on to it
682
+ if (sel.length > 0) {
683
+ sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements));
684
+ }
685
+ else {
686
+ sel.push(new Selector(elements));
687
+ }
688
+ }
587
689
  }
588
690
 
589
- for (i = 0; i < selectors.length; i++) {
590
- sel = selectors[i];
691
+ // joinSelector code follows
692
+ var i, newPaths, hadParentSelector;
591
693
 
592
- // if the previous thing in sel is a parent this needs to join on to it
593
- if (sel.length > 0) {
594
- sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements));
694
+ newPaths = [];
695
+ hadParentSelector = replaceParentSelector(newPaths, context, selector);
696
+
697
+ if (!hadParentSelector) {
698
+ if (context.length > 0) {
699
+ newPaths = [];
700
+ for (i = 0; i < context.length; i++) {
701
+ newPaths.push(context[i].concat(selector));
702
+ }
595
703
  }
596
704
  else {
597
- sel.push(new Selector(elements));
705
+ newPaths = [[selector]];
598
706
  }
599
707
  }
708
+
709
+ for (i = 0; i < newPaths.length; i++) {
710
+ paths.push(newPaths[i]);
711
+ }
712
+
600
713
  };
601
714
  module.exports = Ruleset;
@@ -51,8 +51,9 @@ Selector.prototype.match = function (other) {
51
51
  return olen; // return number of matched elements
52
52
  };
53
53
  Selector.prototype.CacheElements = function() {
54
- if (this._elements)
54
+ if (this._elements) {
55
55
  return;
56
+ }
56
57
 
57
58
  var elements = this.elements.map( function(v) {
58
59
  return v.combinator.value + (v.value.value || v.value);
@@ -90,7 +91,7 @@ Selector.prototype.genCSS = function (context, output) {
90
91
  }
91
92
  if (!this._css) {
92
93
  //TODO caching? speed comparison?
93
- for(i = 0; i < this.elements.length; i++) {
94
+ for (i = 0; i < this.elements.length; i++) {
94
95
  element = this.elements[i];
95
96
  element.genCSS(context, output);
96
97
  }
@@ -23,6 +23,8 @@ Unit.prototype.genCSS = function (context, output) {
23
23
  output.add(this.numerator[0]); // the ideal situation
24
24
  } else if (!strictUnits && this.backupUnit) {
25
25
  output.add(this.backupUnit);
26
+ } else if (!strictUnits && this.denominator.length) {
27
+ output.add(this.denominator[0]);
26
28
  }
27
29
  };
28
30
  Unit.prototype.toString = function () {
@@ -23,9 +23,12 @@ URL.prototype.eval = function (context) {
23
23
  if (!this.isEvald) {
24
24
  // Add the base path if the URL is relative
25
25
  rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
26
- if (rootpath && typeof val.value === "string" && context.isPathRelative(val.value)) {
26
+ if (rootpath &&
27
+ typeof val.value === "string" &&
28
+ context.isPathRelative(val.value)) {
29
+
27
30
  if (!val.quote) {
28
- rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; });
31
+ rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\" + match; });
29
32
  }
30
33
  val.value = rootpath + val.value;
31
34
  }