less 1.2.2 → 1.3.3

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 (197) hide show
  1. package/.npmignore +1 -0
  2. package/CHANGELOG.md +118 -0
  3. package/CONTRIBUTING.md +50 -0
  4. package/Makefile +14 -2
  5. package/benchmark/benchmark.less +11 -11
  6. package/benchmark/less-benchmark.js +1 -1
  7. package/bin/lessc +77 -21
  8. package/build/amd.js +6 -0
  9. package/build/ecma-5.js +120 -0
  10. package/build/header.js +7 -0
  11. package/build/require-rhino.js +7 -0
  12. package/build/require.js +7 -0
  13. package/lib/less/browser.js +192 -53
  14. package/lib/less/colors.js +1 -0
  15. package/lib/less/functions.js +159 -10
  16. package/lib/less/index.js +125 -56
  17. package/lib/less/lessc_helper.js +62 -0
  18. package/lib/less/parser.js +391 -154
  19. package/lib/less/rhino.js +84 -23
  20. package/lib/less/tree/anonymous.js +15 -1
  21. package/lib/less/tree/assignment.js +3 -1
  22. package/lib/less/tree/call.js +12 -6
  23. package/lib/less/tree/color.js +10 -0
  24. package/lib/less/tree/dimension.js +3 -1
  25. package/lib/less/tree/directive.js +10 -10
  26. package/lib/less/tree/element.js +8 -7
  27. package/lib/less/tree/import.js +17 -14
  28. package/lib/less/tree/media.js +121 -0
  29. package/lib/less/tree/mixin.js +133 -45
  30. package/lib/less/tree/operation.js +5 -0
  31. package/lib/less/tree/quoted.js +15 -1
  32. package/lib/less/tree/ratio.js +13 -0
  33. package/lib/less/tree/rule.js +7 -0
  34. package/lib/less/tree/ruleset.js +236 -37
  35. package/lib/less/tree/selector.js +21 -11
  36. package/lib/less/tree/unicode-descriptor.js +13 -0
  37. package/lib/less/tree/url.js +16 -14
  38. package/lib/less/tree/variable.js +13 -1
  39. package/lib/less/tree.js +28 -0
  40. package/package.json +13 -3
  41. package/test/browser/common.js +74 -0
  42. package/test/browser/css/relative-urls/urls.css +36 -0
  43. package/test/browser/css/rootpath/urls.css +36 -0
  44. package/test/browser/css/rootpath-relative/urls.css +36 -0
  45. package/test/browser/css/urls.css +36 -0
  46. package/test/browser/jasmine-html.js +681 -0
  47. package/test/browser/jasmine.css +82 -0
  48. package/test/browser/jasmine.js +2600 -0
  49. package/test/browser/less/imports/urls.less +4 -0
  50. package/test/browser/less/imports/urls2.less +4 -0
  51. package/test/browser/less/relative-urls/urls.less +33 -0
  52. package/test/browser/less/rootpath/urls.less +33 -0
  53. package/test/browser/less/rootpath-relative/urls.less +33 -0
  54. package/test/browser/less/urls.less +33 -0
  55. package/test/browser/phantom-runner.js +139 -0
  56. package/test/browser/runner-browser.js +3 -0
  57. package/test/browser/runner-main.js +15 -0
  58. package/test/browser/runner-relative-urls.js +4 -0
  59. package/test/browser/runner-rootpath-relative.js +5 -0
  60. package/test/browser/runner-rootpath.js +4 -0
  61. package/test/browser/template.htm +10 -0
  62. package/test/browser-test-prepare.js +29 -0
  63. package/test/css/charsets.css +1 -0
  64. package/test/css/colors.css +22 -0
  65. package/test/css/comments.css +7 -0
  66. package/test/css/css-3.css +57 -2
  67. package/test/css/css-escapes.css +4 -0
  68. package/test/css/css.css +11 -11
  69. package/test/css/debug/linenumbers-all.css +43 -0
  70. package/test/css/debug/linenumbers-comments.css +35 -0
  71. package/test/css/debug/linenumbers-mediaquery.css +35 -0
  72. package/test/css/functions.css +59 -2
  73. package/test/css/ie-filters.css +7 -3
  74. package/test/css/import-once.css +3 -0
  75. package/test/css/import.css +7 -9
  76. package/test/css/javascript.css +3 -2
  77. package/test/css/media.css +168 -0
  78. package/test/css/mixins-args.css +35 -4
  79. package/test/css/mixins-guards.css +13 -0
  80. package/test/css/mixins-important.css +21 -0
  81. package/test/css/mixins-named-args.css +27 -0
  82. package/test/css/mixins-pattern.css +4 -6
  83. package/test/css/mixins.css +50 -0
  84. package/test/css/scope.css +20 -0
  85. package/test/css/selectors.css +76 -0
  86. package/test/css/static-urls/urls.css +42 -0
  87. package/test/css/strings.css +2 -2
  88. package/test/css/urls.css +42 -0
  89. package/test/css/variables.css +0 -1
  90. package/test/css/whitespace.css +4 -0
  91. package/test/less/charsets.less +3 -0
  92. package/test/less/colors.less +27 -0
  93. package/test/less/comments.less +12 -0
  94. package/test/less/css-3.less +54 -6
  95. package/test/less/css-escapes.less +6 -1
  96. package/test/less/css.less +14 -12
  97. package/test/less/debug/import/test.less +25 -0
  98. package/test/less/debug/linenumbers.less +23 -0
  99. package/test/less/errors/bad-variable-declaration1.less +1 -0
  100. package/test/less/errors/bad-variable-declaration1.txt +2 -0
  101. package/test/less/errors/comment-in-selector.less +1 -0
  102. package/test/less/errors/comment-in-selector.txt +2 -0
  103. package/test/less/errors/import-missing.less +1 -0
  104. package/test/less/errors/import-missing.txt +3 -0
  105. package/test/less/errors/import-no-semi.less +1 -0
  106. package/test/less/errors/import-no-semi.txt +2 -0
  107. package/test/less/errors/import-subfolder1.less +1 -0
  108. package/test/less/errors/import-subfolder1.txt +3 -0
  109. package/test/less/errors/import-subfolder2.less +1 -0
  110. package/test/less/errors/import-subfolder2.txt +2 -0
  111. package/test/less/errors/imports/import-subfolder1.less +1 -0
  112. package/test/less/errors/imports/import-subfolder2.less +1 -0
  113. package/test/less/errors/imports/import-test.less +4 -0
  114. package/test/less/errors/imports/subfolder/mixin-not-defined.less +1 -0
  115. package/test/less/errors/imports/subfolder/parse-error-curly-bracket.less +1 -0
  116. package/test/less/errors/javascript-error.less +3 -0
  117. package/test/less/errors/javascript-error.txt +4 -0
  118. package/test/less/errors/mixed-mixin-definition-args-1.less +6 -0
  119. package/test/less/errors/mixed-mixin-definition-args-1.txt +4 -0
  120. package/test/less/errors/mixed-mixin-definition-args-2.less +6 -0
  121. package/test/less/errors/mixed-mixin-definition-args-2.txt +4 -0
  122. package/test/less/errors/mixin-not-defined.less +11 -0
  123. package/test/less/errors/mixin-not-defined.txt +3 -0
  124. package/test/less/errors/mixin-not-matched.less +6 -0
  125. package/test/less/errors/mixin-not-matched.txt +3 -0
  126. package/test/less/errors/mixin-not-matched2.less +6 -0
  127. package/test/less/errors/mixin-not-matched2.txt +3 -0
  128. package/test/less/errors/parse-error-curly-bracket.less +1 -0
  129. package/test/less/errors/parse-error-curly-bracket.txt +2 -0
  130. package/test/less/errors/parse-error-missing-bracket.less +2 -0
  131. package/test/less/errors/parse-error-missing-bracket.txt +2 -0
  132. package/test/less/errors/parse-error-with-import.less +13 -0
  133. package/test/less/errors/parse-error-with-import.txt +4 -0
  134. package/test/less/errors/property-ie5-hack.less +3 -0
  135. package/test/less/errors/property-ie5-hack.txt +4 -0
  136. package/test/less/errors/recursive-variable.less +1 -0
  137. package/test/less/errors/recursive-variable.txt +2 -0
  138. package/test/less/functions.less +64 -2
  139. package/test/less/ie-filters.less +7 -0
  140. package/test/less/import/deeper/import-once-test-a.less +1 -0
  141. package/test/less/import/import-and-relative-paths-test.less +6 -0
  142. package/test/less/import/import-charset-test.less +1 -0
  143. package/test/less/import/import-once-test-c.less +6 -0
  144. package/test/less/import/import-test-a.less +1 -0
  145. package/test/less/import/import-test-c.less +0 -1
  146. package/test/less/import/imports/font.less +8 -0
  147. package/test/less/import/imports/logo.less +5 -0
  148. package/test/less/import/urls.less +1 -0
  149. package/test/less/import-once.less +4 -0
  150. package/test/less/import.less +2 -1
  151. package/test/less/javascript.less +4 -2
  152. package/test/less/media.less +169 -1
  153. package/test/less/mixins-args.less +54 -11
  154. package/test/less/mixins-guards.less +30 -0
  155. package/test/less/mixins-important.less +4 -0
  156. package/test/less/mixins-named-args.less +36 -0
  157. package/test/less/mixins-pattern.less +3 -0
  158. package/test/less/mixins.less +47 -0
  159. package/test/less/scope.less +48 -1
  160. package/test/less/selectors.less +88 -0
  161. package/test/less/static-urls/urls.less +33 -0
  162. package/test/less/strings.less +1 -1
  163. package/test/less/urls.less +33 -0
  164. package/test/less/variables.less +0 -1
  165. package/test/less/whitespace.less +7 -0
  166. package/test/less-test.js +145 -36
  167. package/bench.css +0 -5943
  168. package/benchmark/b1.less +0 -3980
  169. package/dist/less-1.1.0.js +0 -2695
  170. package/dist/less-1.1.0.min.js +0 -16
  171. package/dist/less-1.1.1.js +0 -2710
  172. package/dist/less-1.1.1.min.js +0 -16
  173. package/dist/less-1.1.2.js +0 -2712
  174. package/dist/less-1.1.2.min.js +0 -16
  175. package/dist/less-1.1.3.js +0 -2721
  176. package/dist/less-1.1.3.min.js +0 -16
  177. package/dist/less-1.1.4.js +0 -2769
  178. package/dist/less-1.1.4.min.js +0 -16
  179. package/dist/less-1.1.5.js +0 -2805
  180. package/dist/less-1.1.5.min.js +0 -9
  181. package/dist/less-1.1.6.js +0 -3004
  182. package/dist/less-1.1.6.min.js +0 -9
  183. package/dist/less-1.2.0.js +0 -3293
  184. package/dist/less-1.2.0.min.js +0 -9
  185. package/dist/less-1.2.1.js +0 -3318
  186. package/dist/less-1.2.1.min.js +0 -9
  187. package/dist/less-1.2.2.js +0 -3337
  188. package/dist/less-1.2.2.min.js +0 -9
  189. package/dist/less-rhino-1.1.3.js +0 -2460
  190. package/dist/less-rhino-1.1.5.js +0 -2481
  191. package/foo.less +0 -2
  192. package/lib/less/cssmin.js +0 -355
  193. package/test/html/index.html +0 -10
  194. package/test/html/less.js +0 -3333
  195. package/test/html/lib.less +0 -2
  196. package/test/html/styles.less +0 -8
  197. package/x.less +0 -4
@@ -10,46 +10,75 @@ tree.mixin.Call = function (elements, args, index, filename, important) {
10
10
  };
11
11
  tree.mixin.Call.prototype = {
12
12
  eval: function (env) {
13
- var mixins, args, rules = [], match = false;
13
+ var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound;
14
14
 
15
- for (var i = 0; i < env.frames.length; i++) {
15
+ args = this.arguments && this.arguments.map(function (a) {
16
+ return { name: a.name, value: a.value.eval(env) };
17
+ });
18
+
19
+ for (i = 0; i < env.frames.length; i++) {
16
20
  if ((mixins = env.frames[i].find(this.selector)).length > 0) {
17
- args = this.arguments && this.arguments.map(function (a) { return a.eval(env) });
18
- for (var m = 0; m < mixins.length; m++) {
19
- if (mixins[m].match(args, env)) {
20
- try {
21
- Array.prototype.push.apply(
22
- rules, mixins[m].eval(env, this.arguments, this.important).rules);
23
- match = true;
24
- } catch (e) {
25
- throw { message: e.message, index: e.index, filename: this.filename, stack: e.stack, call: this.index };
21
+ isOneFound = true;
22
+ for (m = 0; m < mixins.length; m++) {
23
+ mixin = mixins[m];
24
+ isRecursive = false;
25
+ for(f = 0; f < env.frames.length; f++) {
26
+ if ((!(mixin instanceof tree.mixin.Definition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) {
27
+ isRecursive = true;
28
+ break;
26
29
  }
27
30
  }
31
+ if (isRecursive) {
32
+ continue;
33
+ }
34
+ if (mixin.matchArgs(args, env)) {
35
+ if (!mixin.matchCondition || mixin.matchCondition(args, env)) {
36
+ try {
37
+ Array.prototype.push.apply(
38
+ rules, mixin.eval(env, args, this.important).rules);
39
+ } catch (e) {
40
+ throw { message: e.message, index: this.index, filename: this.filename, stack: e.stack };
41
+ }
42
+ }
43
+ match = true;
44
+ }
28
45
  }
29
46
  if (match) {
30
47
  return rules;
31
- } else {
32
- throw { type: 'Runtime',
33
- message: 'No matching definition was found for `' +
34
- this.selector.toCSS().trim() + '(' +
35
- this.arguments.map(function (a) {
36
- return a.toCSS();
37
- }).join(', ') + ")`",
38
- index: this.index, filename: this.filename };
39
48
  }
40
49
  }
41
50
  }
42
- throw { type: 'Name',
51
+ if (isOneFound) {
52
+ throw { type: 'Runtime',
53
+ message: 'No matching definition was found for `' +
54
+ this.selector.toCSS().trim() + '(' +
55
+ (args ? args.map(function (a) {
56
+ var argValue = "";
57
+ if (a.name) {
58
+ argValue += a.name + ":";
59
+ }
60
+ if (a.value.toCSS) {
61
+ argValue += a.value.toCSS();
62
+ } else {
63
+ argValue += "???";
64
+ }
65
+ return argValue;
66
+ }).join(', ') : "") + ")`",
67
+ index: this.index, filename: this.filename };
68
+ } else {
69
+ throw { type: 'Name',
43
70
  message: this.selector.toCSS().trim() + " is undefined",
44
71
  index: this.index, filename: this.filename };
72
+ }
45
73
  }
46
74
  };
47
75
 
48
- tree.mixin.Definition = function (name, params, rules, condition) {
76
+ tree.mixin.Definition = function (name, params, rules, condition, variadic) {
49
77
  this.name = name;
50
78
  this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])];
51
79
  this.params = params;
52
80
  this.condition = condition;
81
+ this.variadic = variadic;
53
82
  this.arity = params.length;
54
83
  this.rules = rules;
55
84
  this._lookups = {};
@@ -67,52 +96,111 @@ tree.mixin.Definition.prototype = {
67
96
  find: function () { return this.parent.find.apply(this, arguments) },
68
97
  rulesets: function () { return this.parent.rulesets.apply(this) },
69
98
 
70
- evalParams: function (env, args) {
71
- var frame = new(tree.Ruleset)(null, []);
99
+ evalParams: function (env, mixinEnv, args, evaldArguments) {
100
+ var frame = new(tree.Ruleset)(null, []), varargs, arg, params = this.params.slice(0), i, j, val, name, isNamedFound, argIndex;
101
+
102
+ if (args) {
103
+ args = args.slice(0);
104
+
105
+ for(i = 0; i < args.length; i++) {
106
+ arg = args[i];
107
+ if (name = (arg && arg.name)) {
108
+ isNamedFound = false;
109
+ for(j = 0; j < params.length; j++) {
110
+ if (!evaldArguments[j] && name === params[j].name) {
111
+ evaldArguments[j] = arg.value.eval(env);
112
+ frame.rules.unshift(new(tree.Rule)(name, arg.value.eval(env)));
113
+ isNamedFound = true;
114
+ break;
115
+ }
116
+ }
117
+ if (isNamedFound) {
118
+ args.splice(i, 1);
119
+ i--;
120
+ continue;
121
+ } else {
122
+ throw { type: 'Runtime', message: "Named argument for " + this.name +
123
+ ' ' + args[i].name + ' not found' };
124
+ }
125
+ }
126
+ }
127
+ }
128
+ argIndex = 0;
129
+ for (i = 0; i < params.length; i++) {
130
+ if (evaldArguments[i]) continue;
131
+
132
+ arg = args && args[argIndex];
72
133
 
73
- for (var i = 0, val; i < this.params.length; i++) {
74
- if (this.params[i].name) {
75
- if (val = (args && args[i]) || this.params[i].value) {
76
- frame.rules.unshift(new(tree.Rule)(this.params[i].name, val.eval(env)));
134
+ if (name = params[i].name) {
135
+ if (params[i].variadic && args) {
136
+ varargs = [];
137
+ for (j = argIndex; j < args.length; j++) {
138
+ varargs.push(args[j].value.eval(env));
139
+ }
140
+ frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env)));
77
141
  } else {
78
- throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
142
+ val = arg && arg.value;
143
+ if (val) {
144
+ val = val.eval(env);
145
+ } else if (params[i].value) {
146
+ val = params[i].value.eval(mixinEnv);
147
+ } else {
148
+ throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
79
149
  ' (' + args.length + ' for ' + this.arity + ')' };
150
+ }
151
+
152
+ frame.rules.unshift(new(tree.Rule)(name, val));
153
+ evaldArguments[i] = val;
154
+ }
155
+ }
156
+
157
+ if (params[i].variadic && args) {
158
+ for (j = argIndex; j < args.length; j++) {
159
+ evaldArguments[j] = args[j].value.eval(env);
80
160
  }
81
161
  }
162
+ argIndex++;
82
163
  }
164
+
83
165
  return frame;
84
166
  },
85
167
  eval: function (env, args, important) {
86
- var frame = this.evalParams(env, args), context, _arguments = [], rules;
168
+ var _arguments = [],
169
+ mixinFrames = this.frames.concat(env.frames),
170
+ frame = this.evalParams(env, {frames: mixinFrames}, args, _arguments),
171
+ context, rules, start, ruleset;
87
172
 
88
- for (var i = 0; i < Math.max(this.params.length, args && args.length); i++) {
89
- _arguments.push(args[i] || this.params[i].value);
90
- }
91
173
  frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
92
174
 
93
175
  rules = important ?
94
- this.rules.map(function (r) {
95
- return new(tree.Rule)(r.name, r.value, '!important', r.index);
96
- }) : this.rules.slice(0);
176
+ this.parent.makeImportant.apply(this).rules : this.rules.slice(0);
97
177
 
98
- return new(tree.Ruleset)(null, rules).eval({
99
- frames: [this, frame].concat(this.frames, env.frames)
178
+ ruleset = new(tree.Ruleset)(null, rules).eval({
179
+ frames: [this, frame].concat(mixinFrames)
100
180
  });
181
+ ruleset.originalRuleset = this;
182
+ return ruleset;
101
183
  },
102
- match: function (args, env) {
103
- var argsLength = (args && args.length) || 0, len, frame;
104
-
105
- if (argsLength < this.required) { return false }
106
- if ((this.required > 0) && (argsLength > this.params.length)) { return false }
184
+ matchCondition: function (args, env) {
107
185
  if (this.condition && !this.condition.eval({
108
- frames: [this.evalParams(env, args)].concat(env.frames)
186
+ frames: [this.evalParams(env, {frames: this.frames.concat(env.frames)}, args, [])].concat(env.frames)
109
187
  })) { return false }
188
+ return true;
189
+ },
190
+ matchArgs: function (args, env) {
191
+ var argsLength = (args && args.length) || 0, len, frame;
192
+
193
+ if (! this.variadic) {
194
+ if (argsLength < this.required) { return false }
195
+ if (argsLength > this.params.length) { return false }
196
+ if ((this.required > 0) && (argsLength > this.params.length)) { return false }
197
+ }
110
198
 
111
199
  len = Math.min(argsLength, this.arity);
112
200
 
113
201
  for (var i = 0; i < len; i++) {
114
- if (!this.params[i].name) {
115
- if (args[i].eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
202
+ if (!this.params[i].name && !this.params[i].variadic) {
203
+ if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
116
204
  return false;
117
205
  }
118
206
  }
@@ -17,6 +17,11 @@ tree.Operation.prototype.eval = function (env) {
17
17
  message: "Can't substract or divide a color from a number" };
18
18
  }
19
19
  }
20
+ if (!a.operate) {
21
+ throw { name: "OperationError",
22
+ message: "Operation on an invalid type" };
23
+ }
24
+
20
25
  return a.operate(this.op, b);
21
26
  };
22
27
 
@@ -20,9 +20,23 @@ tree.Quoted.prototype = {
20
20
  return new(tree.JavaScript)(exp, that.index, true).eval(env).value;
21
21
  }).replace(/@\{([\w-]+)\}/g, function (_, name) {
22
22
  var v = new(tree.Variable)('@' + name, that.index).eval(env);
23
- return ('value' in v) ? v.value : v.toCSS();
23
+ return (v instanceof tree.Quoted) ? v.value : v.toCSS();
24
24
  });
25
25
  return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index);
26
+ },
27
+ compare: function (x) {
28
+ if (!x.toCSS) {
29
+ return -1;
30
+ }
31
+
32
+ var left = this.toCSS(),
33
+ right = x.toCSS();
34
+
35
+ if (left === right) {
36
+ return 0;
37
+ }
38
+
39
+ return left < right ? -1 : 1;
26
40
  }
27
41
  };
28
42
 
@@ -0,0 +1,13 @@
1
+ (function (tree) {
2
+
3
+ tree.Ratio = function (value) {
4
+ this.value = value;
5
+ };
6
+ tree.Ratio.prototype = {
7
+ toCSS: function (env) {
8
+ return this.value;
9
+ },
10
+ eval: function () { return this }
11
+ };
12
+
13
+ })(require('../tree'));
@@ -27,6 +27,13 @@ tree.Rule.prototype.eval = function (context) {
27
27
  this.index, this.inline);
28
28
  };
29
29
 
30
+ tree.Rule.prototype.makeImportant = function () {
31
+ return new(tree.Rule)(this.name,
32
+ this.value,
33
+ "!important",
34
+ this.index, this.inline);
35
+ };
36
+
30
37
  tree.Shorthand = function (a, b) {
31
38
  this.a = a;
32
39
  this.b = b;
@@ -1,29 +1,31 @@
1
1
  (function (tree) {
2
2
 
3
- tree.Ruleset = function (selectors, rules) {
3
+ tree.Ruleset = function (selectors, rules, strictImports) {
4
4
  this.selectors = selectors;
5
5
  this.rules = rules;
6
6
  this._lookups = {};
7
+ this.strictImports = strictImports;
7
8
  };
8
9
  tree.Ruleset.prototype = {
9
10
  eval: function (env) {
10
11
  var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env) });
11
- var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0));
12
-
12
+ var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0), this.strictImports);
13
+ var rules;
14
+
15
+ ruleset.originalRuleset = this;
13
16
  ruleset.root = this.root;
14
17
  ruleset.allowImports = this.allowImports;
15
18
 
19
+ if(this.debugInfo) {
20
+ ruleset.debugInfo = this.debugInfo;
21
+ }
22
+
16
23
  // push the current ruleset to the frames stack
17
24
  env.frames.unshift(ruleset);
18
25
 
19
26
  // Evaluate imports
20
- if (ruleset.root || ruleset.allowImports) {
21
- for (var i = 0; i < ruleset.rules.length; i++) {
22
- if (ruleset.rules[i] instanceof tree.Import) {
23
- Array.prototype.splice
24
- .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
25
- }
26
- }
27
+ if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) {
28
+ ruleset.evalImports(env);
27
29
  }
28
30
 
29
31
  // Store the frames around mixin definitions,
@@ -33,12 +35,16 @@ tree.Ruleset.prototype = {
33
35
  ruleset.rules[i].frames = env.frames.slice(0);
34
36
  }
35
37
  }
38
+
39
+ var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0;
36
40
 
37
41
  // Evaluate mixin calls.
38
42
  for (var i = 0; i < ruleset.rules.length; i++) {
39
43
  if (ruleset.rules[i] instanceof tree.mixin.Call) {
40
- Array.prototype.splice
41
- .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
44
+ rules = ruleset.rules[i].eval(env);
45
+ ruleset.rules.splice.apply(ruleset.rules, [i, 1].concat(rules));
46
+ i += rules.length-1;
47
+ ruleset.resetCache();
42
48
  }
43
49
  }
44
50
 
@@ -53,12 +59,47 @@ tree.Ruleset.prototype = {
53
59
 
54
60
  // Pop the stack
55
61
  env.frames.shift();
62
+
63
+ if (env.mediaBlocks) {
64
+ for(var i = mediaBlockCount; i < env.mediaBlocks.length; i++) {
65
+ env.mediaBlocks[i].bubbleSelectors(selectors);
66
+ }
67
+ }
56
68
 
57
69
  return ruleset;
58
70
  },
59
- match: function (args) {
71
+ evalImports: function(env) {
72
+ var i, rules;
73
+ for (i = 0; i < this.rules.length; i++) {
74
+ if (this.rules[i] instanceof tree.Import) {
75
+ rules = this.rules[i].eval(env);
76
+ if (typeof rules.length === "number") {
77
+ this.rules.splice.apply(this.rules, [i, 1].concat(rules));
78
+ i+= rules.length-1;
79
+ } else {
80
+ this.rules.splice(i, 1, rules);
81
+ }
82
+ this.resetCache();
83
+ }
84
+ }
85
+ },
86
+ makeImportant: function() {
87
+ return new tree.Ruleset(this.selectors, this.rules.map(function (r) {
88
+ if (r.makeImportant) {
89
+ return r.makeImportant();
90
+ } else {
91
+ return r;
92
+ }
93
+ }), this.strictImports);
94
+ },
95
+ matchArgs: function (args) {
60
96
  return !args || args.length === 0;
61
97
  },
98
+ resetCache: function () {
99
+ this._rulesets = null;
100
+ this._variables = null;
101
+ this._lookups = {};
102
+ },
62
103
  variables: function () {
63
104
  if (this._variables) { return this._variables }
64
105
  else {
@@ -113,25 +154,41 @@ tree.Ruleset.prototype = {
113
154
  toCSS: function (context, env) {
114
155
  var css = [], // The CSS output
115
156
  rules = [], // node.Rule instances
157
+ _rules = [], //
116
158
  rulesets = [], // node.Ruleset instances
117
159
  paths = [], // Current selectors
118
160
  selector, // The fully rendered selector
161
+ debugInfo, // Line number debugging
119
162
  rule;
120
163
 
121
164
  if (! this.root) {
122
- if (context.length === 0) {
123
- paths = this.selectors.map(function (s) { return [s] });
124
- } else {
125
- this.joinSelectors(paths, context, this.selectors);
126
- }
165
+ this.joinSelectors(paths, context, this.selectors);
127
166
  }
128
167
 
129
168
  // Compile rules and rulesets
130
169
  for (var i = 0; i < this.rules.length; i++) {
131
170
  rule = this.rules[i];
132
171
 
133
- if (rule.rules || (rule instanceof tree.Directive)) {
172
+ if (rule.rules || (rule instanceof tree.Media)) {
134
173
  rulesets.push(rule.toCSS(paths, env));
174
+ } else if (rule instanceof tree.Directive) {
175
+ var cssValue = rule.toCSS(paths, env);
176
+ // Output only the first @charset definition as such - convert the others
177
+ // to comments in case debug is enabled
178
+ if (rule.name === "@charset") {
179
+ // Only output the debug info together with subsequent @charset definitions
180
+ // a comment (or @media statement) before the actual @charset directive would
181
+ // be considered illegal css as it has to be on the first line
182
+ if (env.charset) {
183
+ if (rule.debugInfo) {
184
+ rulesets.push(tree.debugInfo(env, rule));
185
+ rulesets.push(new tree.Comment("/* "+cssValue.replace(/\n/g, "")+" */\n").toCSS(env));
186
+ }
187
+ continue;
188
+ }
189
+ env.charset = true;
190
+ }
191
+ rulesets.push(cssValue);
135
192
  } else if (rule instanceof tree.Comment) {
136
193
  if (!rule.silent) {
137
194
  if (this.root) {
@@ -158,13 +215,22 @@ tree.Ruleset.prototype = {
158
215
  css.push(rules.join(env.compress ? '' : '\n'));
159
216
  } else {
160
217
  if (rules.length > 0) {
218
+ debugInfo = tree.debugInfo(env, this);
161
219
  selector = paths.map(function (p) {
162
220
  return p.map(function (s) {
163
221
  return s.toCSS(env);
164
222
  }).join('').trim();
165
- }).join( env.compress ? ',' : ',\n');
223
+ }).join(env.compress ? ',' : ',\n');
224
+
225
+ // Remove duplicates
226
+ for (var i = rules.length - 1; i >= 0; i--) {
227
+ if (_rules.indexOf(rules[i]) === -1) {
228
+ _rules.unshift(rules[i]);
229
+ }
230
+ }
231
+ rules = _rules;
166
232
 
167
- css.push(selector,
233
+ css.push(debugInfo + selector +
168
234
  (env.compress ? '{' : ' {\n ') +
169
235
  rules.join(env.compress ? '' : '\n ') +
170
236
  (env.compress ? '}' : '\n}\n'));
@@ -172,7 +238,7 @@ tree.Ruleset.prototype = {
172
238
  }
173
239
  css.push(rulesets);
174
240
 
175
- return css.join('') + (env.compress ? '\n' : '');
241
+ return css.join('') + (env.compress ? '\n' : '');
176
242
  },
177
243
 
178
244
  joinSelectors: function (paths, context, selectors) {
@@ -182,33 +248,166 @@ tree.Ruleset.prototype = {
182
248
  },
183
249
 
184
250
  joinSelector: function (paths, context, selector) {
185
- var before = [], after = [], beforeElements = [],
186
- afterElements = [], hasParentSelector = false, el;
187
251
 
188
- for (var i = 0; i < selector.elements.length; i++) {
252
+ var i, j, k,
253
+ hasParentSelector, newSelectors, el, sel, parentSel,
254
+ newSelectorPath, afterParentJoin, newJoinedSelector,
255
+ newJoinedSelectorEmpty, lastSelector, currentElements,
256
+ selectorsMultiplied;
257
+
258
+ for (i = 0; i < selector.elements.length; i++) {
189
259
  el = selector.elements[i];
190
- if (el.combinator.value.charAt(0) === '&') {
260
+ if (el.value === '&') {
191
261
  hasParentSelector = true;
192
262
  }
193
- if (hasParentSelector) afterElements.push(el);
194
- else beforeElements.push(el);
195
263
  }
264
+
265
+ if (!hasParentSelector) {
266
+ if (context.length > 0) {
267
+ for(i = 0; i < context.length; i++) {
268
+ paths.push(context[i].concat(selector));
269
+ }
270
+ }
271
+ else {
272
+ paths.push([selector]);
273
+ }
274
+ return;
275
+ }
276
+
277
+ // The paths are [[Selector]]
278
+ // The first list is a list of comma seperated selectors
279
+ // The inner list is a list of inheritance seperated selectors
280
+ // e.g.
281
+ // .a, .b {
282
+ // .c {
283
+ // }
284
+ // }
285
+ // == [[.a] [.c]] [[.b] [.c]]
286
+ //
196
287
 
197
- if (! hasParentSelector) {
198
- afterElements = beforeElements;
199
- beforeElements = [];
288
+ // the elements from the current selector so far
289
+ currentElements = [];
290
+ // the current list of new selectors to add to the path.
291
+ // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
292
+ // by the parents
293
+ newSelectors = [[]];
294
+
295
+ for (i = 0; i < selector.elements.length; i++) {
296
+ el = selector.elements[i];
297
+ // non parent reference elements just get added
298
+ if (el.value !== "&") {
299
+ currentElements.push(el);
300
+ } else {
301
+ // the new list of selectors to add
302
+ selectorsMultiplied = [];
303
+
304
+ // merge the current list of non parent selector elements
305
+ // on to the current list of selectors to add
306
+ if (currentElements.length > 0) {
307
+ this.mergeElementsOnToSelectors(currentElements, newSelectors);
308
+ }
309
+
310
+ // loop through our current selectors
311
+ for(j = 0; j < newSelectors.length; j++) {
312
+ sel = newSelectors[j];
313
+ // if we don't have any parent paths, the & might be in a mixin so that it can be used
314
+ // whether there are parents or not
315
+ if (context.length == 0) {
316
+ // the combinator used on el should now be applied to the next element instead so that
317
+ // it is not lost
318
+ if (sel.length > 0) {
319
+ sel[0].elements = sel[0].elements.slice(0);
320
+ sel[0].elements.push(new(tree.Element)(el.combinator, '', 0)); //new Element(el.Combinator, ""));
321
+ }
322
+ selectorsMultiplied.push(sel);
323
+ }
324
+ else {
325
+ // and the parent selectors
326
+ for(k = 0; k < context.length; k++) {
327
+ parentSel = context[k];
328
+ // We need to put the current selectors
329
+ // then join the last selector's elements on to the parents selectors
330
+
331
+ // our new selector path
332
+ newSelectorPath = [];
333
+ // selectors from the parent after the join
334
+ afterParentJoin = [];
335
+ newJoinedSelectorEmpty = true;
336
+
337
+ //construct the joined selector - if & is the first thing this will be empty,
338
+ // if not newJoinedSelector will be the last set of elements in the selector
339
+ if (sel.length > 0) {
340
+ newSelectorPath = sel.slice(0);
341
+ lastSelector = newSelectorPath.pop();
342
+ newJoinedSelector = new(tree.Selector)(lastSelector.elements.slice(0));
343
+ newJoinedSelectorEmpty = false;
344
+ }
345
+ else {
346
+ newJoinedSelector = new(tree.Selector)([]);
347
+ }
348
+
349
+ //put together the parent selectors after the join
350
+ if (parentSel.length > 1) {
351
+ afterParentJoin = afterParentJoin.concat(parentSel.slice(1));
352
+ }
353
+
354
+ if (parentSel.length > 0) {
355
+ newJoinedSelectorEmpty = false;
356
+
357
+ // join the elements so far with the first part of the parent
358
+ newJoinedSelector.elements.push(new(tree.Element)(el.combinator, parentSel[0].elements[0].value, 0));
359
+ newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1));
360
+ }
361
+
362
+ if (!newJoinedSelectorEmpty) {
363
+ // now add the joined selector
364
+ newSelectorPath.push(newJoinedSelector);
365
+ }
366
+
367
+ // and the rest of the parent
368
+ newSelectorPath = newSelectorPath.concat(afterParentJoin);
369
+
370
+ // add that to our new set of selectors
371
+ selectorsMultiplied.push(newSelectorPath);
372
+ }
373
+ }
374
+ }
375
+
376
+ // our new selectors has been multiplied, so reset the state
377
+ newSelectors = selectorsMultiplied;
378
+ currentElements = [];
379
+ }
200
380
  }
201
381
 
202
- if (beforeElements.length > 0) {
203
- before.push(new(tree.Selector)(beforeElements));
382
+ // if we have any elements left over (e.g. .a& .b == .b)
383
+ // add them on to all the current selectors
384
+ if (currentElements.length > 0) {
385
+ this.mergeElementsOnToSelectors(currentElements, newSelectors);
204
386
  }
205
387
 
206
- if (afterElements.length > 0) {
207
- after.push(new(tree.Selector)(afterElements));
388
+ for(i = 0; i < newSelectors.length; i++) {
389
+ paths.push(newSelectors[i]);
390
+ }
391
+ },
392
+
393
+ mergeElementsOnToSelectors: function(elements, selectors) {
394
+ var i, sel;
395
+
396
+ if (selectors.length == 0) {
397
+ selectors.push([ new(tree.Selector)(elements) ]);
398
+ return;
208
399
  }
209
400
 
210
- for (var c = 0; c < context.length; c++) {
211
- paths.push(before.concat(context[c]).concat(after));
401
+ for(i = 0; i < selectors.length; i++) {
402
+ sel = selectors[i];
403
+
404
+ // if the previous thing in sel is a parent this needs to join on to it
405
+ if (sel.length > 0) {
406
+ sel[sel.length - 1] = new(tree.Selector)(sel[sel.length - 1].elements.concat(elements));
407
+ }
408
+ else {
409
+ sel.push(new(tree.Selector)(elements));
410
+ }
212
411
  }
213
412
  }
214
413
  };