From d5f29b6b33f15a782b23bd76d56da5ea09d0b770 Mon Sep 17 00:00:00 2001 From: Jake Bellacera Date: Fri, 23 Aug 2013 15:45:09 -0700 Subject: [PATCH 01/29] Adding gsub function --- lib/less/functions.js | 7 +++++++ test/css/functions.css | 1 + test/less/functions.less | 1 + 3 files changed, 9 insertions(+) diff --git a/lib/less/functions.js b/lib/less/functions.js index fbde54ff..9968c59d 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -213,6 +213,13 @@ tree.functions = { escape: function (str) { return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); }, + gsub: function (str, regexp, replacement) { + var str = str.value; + + str = str.replace(new RegExp(regexp.value, "g"), replacement.value); + + return new(tree.Quoted)('"' + str + '"', str) + }, '%': function (quoted /* arg, arg, ...*/) { var args = Array.prototype.slice.call(arguments, 1), str = quoted.value; diff --git a/test/css/functions.css b/test/css/functions.css index eacebd8d..499b614c 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -8,6 +8,7 @@ } #built-in { escaped: -Some::weird(#thing, y); + gsub: "Hello, World!"; lighten: #ffcccc; darken: #330000; saturate: #203c31; diff --git a/test/less/functions.less b/test/less/functions.less index f60dab47..d84ae6bd 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -12,6 +12,7 @@ #built-in { @r: 32; escaped: e("-Some::weird(#thing, y)"); + gsub: gsub("Hello, Foo.", "Foo\.$", "World!"); lighten: lighten(#ff0000, 40%); darken: darken(#ff0000, 40%); saturate: saturate(#29332f, 20%); From d4e15e29f8a75ce2c8256e335b77898769e95c49 Mon Sep 17 00:00:00 2001 From: Jake Bellacera Date: Fri, 23 Aug 2013 15:45:09 -0700 Subject: [PATCH 02/29] Adding gsub function --- lib/less/functions.js | 7 +++++++ test/css/functions.css | 1 + test/less/functions.less | 1 + 3 files changed, 9 insertions(+) diff --git a/lib/less/functions.js b/lib/less/functions.js index fbde54ff..9968c59d 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -213,6 +213,13 @@ tree.functions = { escape: function (str) { return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); }, + gsub: function (str, regexp, replacement) { + var str = str.value; + + str = str.replace(new RegExp(regexp.value, "g"), replacement.value); + + return new(tree.Quoted)('"' + str + '"', str) + }, '%': function (quoted /* arg, arg, ...*/) { var args = Array.prototype.slice.call(arguments, 1), str = quoted.value; diff --git a/test/css/functions.css b/test/css/functions.css index eacebd8d..499b614c 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -8,6 +8,7 @@ } #built-in { escaped: -Some::weird(#thing, y); + gsub: "Hello, World!"; lighten: #ffcccc; darken: #330000; saturate: #203c31; diff --git a/test/less/functions.less b/test/less/functions.less index f60dab47..d84ae6bd 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -12,6 +12,7 @@ #built-in { @r: 32; escaped: e("-Some::weird(#thing, y)"); + gsub: gsub("Hello, Foo.", "Foo\.$", "World!"); lighten: lighten(#ff0000, 40%); darken: darken(#ff0000, 40%); saturate: saturate(#29332f, 20%); From 170042fb02532632a05759f00c9851e0a882dea3 Mon Sep 17 00:00:00 2001 From: Jake Bellacera Date: Fri, 23 Aug 2013 16:26:39 -0700 Subject: [PATCH 03/29] Adding capture group test case --- test/css/functions.css | 1 + test/less/functions.less | 1 + 2 files changed, 2 insertions(+) diff --git a/test/css/functions.css b/test/css/functions.css index 499b614c..3e11d944 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -9,6 +9,7 @@ #built-in { escaped: -Some::weird(#thing, y); gsub: "Hello, World!"; + gsub-captured: "This is a new string."; lighten: #ffcccc; darken: #330000; saturate: #203c31; diff --git a/test/less/functions.less b/test/less/functions.less index d84ae6bd..cf81dc54 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -13,6 +13,7 @@ @r: 32; escaped: e("-Some::weird(#thing, y)"); gsub: gsub("Hello, Foo.", "Foo\.$", "World!"); + gsub-captured: gsub("This is a string.", "(string)\.$", "new $1."); lighten: lighten(#ff0000, 40%); darken: darken(#ff0000, 40%); saturate: saturate(#29332f, 20%); From 089bf1b78a299b029609d66306e5230f08ff311e Mon Sep 17 00:00:00 2001 From: Jake Bellacera Date: Thu, 6 Feb 2014 23:29:14 -0800 Subject: [PATCH 04/29] Adding replace function * renamed gsub function to replace * reordered replace's function arguments to be much more readable * added support for replacement (Regexp) options --- lib/less/functions.js | 8 ++++---- test/css/functions.css | 5 +++-- test/less/functions.less | 5 +++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/less/functions.js b/lib/less/functions.js index 883451a3..96f9c3bf 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -218,12 +218,12 @@ tree.functions = { escape: function (str) { return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); }, - gsub: function (str, regexp, replacement) { - var str = str.value; + replace: function (subject, pattern, replacement, options) { + var str = subject.value; - str = str.replace(new RegExp(regexp.value, "g"), replacement.value); + str = str.replace(new RegExp(pattern.value, options ? options.value : ""), replacement.value); - return new(tree.Quoted)('"' + str + '"', str) + return new(tree.Quoted)('"' + str + '"', str); }, '%': function (quoted /* arg, arg, ...*/) { var args = Array.prototype.slice.call(arguments, 1), diff --git a/test/css/functions.css b/test/css/functions.css index fa5ea89b..0ba7a279 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -8,8 +8,6 @@ } #built-in { escaped: -Some::weird(#thing, y); - gsub: "Hello, World!"; - gsub-captured: "This is a new string."; lighten: #ffcccc; darken: #330000; saturate: #203c31; @@ -47,6 +45,9 @@ contrast-dark-thresh-per: #eeeeee; contrast-high-thresh-per: #eeeeee; contrast-low-thresh-per: #111111; + replace: "Hello, World!"; + replace-captured: "This is a new string."; + replace-options: "2 + 2 = 4"; format: "rgb(32, 128, 64)"; format-string: "hello world"; format-multiple: "hello earth 2"; diff --git a/test/less/functions.less b/test/less/functions.less index eb7df7e2..cac404f4 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -12,8 +12,6 @@ #built-in { @r: 32; escaped: e("-Some::weird(#thing, y)"); - gsub: gsub("Hello, Foo.", "Foo\.$", "World!"); - gsub-captured: gsub("This is a string.", "(string)\.$", "new $1."); lighten: lighten(#ff0000, 40%); darken: darken(#ff0000, 40%); saturate: saturate(#29332f, 20%); @@ -51,6 +49,9 @@ contrast-dark-thresh-per: contrast(#000, #111111, #eeeeee, 50%); contrast-high-thresh-per: contrast(#555, #111111, #eeeeee, 60%); contrast-low-thresh-per: contrast(#555, #111111, #eeeeee, 10%); + replace: replace("Hello, Mars.", "Mars\.", "World!"); + replace-captured: replace("This is a string.", "(string)\.$", "new $1."); + replace-options: replace("One + one = 4", "one", "2", "gi"); format: %("rgb(%d, %d, %d)", @r, 128, 64); format-string: %("hello %s", "world"); format-multiple: %("hello %s %d", "earth", 2); From 98f48bcb7890342304df8f5a2a3b1a07ddb5afcb Mon Sep 17 00:00:00 2001 From: Jake Bellacera Date: Thu, 6 Feb 2014 23:41:18 -0800 Subject: [PATCH 05/29] Replacing replace's options to flags --- lib/less/functions.js | 4 ++-- test/css/functions.css | 2 +- test/less/functions.less | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/less/functions.js b/lib/less/functions.js index 96f9c3bf..7b11959c 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -218,10 +218,10 @@ tree.functions = { escape: function (str) { return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); }, - replace: function (subject, pattern, replacement, options) { + replace: function (subject, pattern, replacement, flags) { var str = subject.value; - str = str.replace(new RegExp(pattern.value, options ? options.value : ""), replacement.value); + str = str.replace(new RegExp(pattern.value, flags ? flags.value : ""), replacement.value); return new(tree.Quoted)('"' + str + '"', str); }, diff --git a/test/css/functions.css b/test/css/functions.css index 0ba7a279..e7ea76c0 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -47,7 +47,7 @@ contrast-low-thresh-per: #111111; replace: "Hello, World!"; replace-captured: "This is a new string."; - replace-options: "2 + 2 = 4"; + replace-with-flags: "2 + 2 = 4"; format: "rgb(32, 128, 64)"; format-string: "hello world"; format-multiple: "hello earth 2"; diff --git a/test/less/functions.less b/test/less/functions.less index cac404f4..7420cc8a 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -51,7 +51,7 @@ contrast-low-thresh-per: contrast(#555, #111111, #eeeeee, 10%); replace: replace("Hello, Mars.", "Mars\.", "World!"); replace-captured: replace("This is a string.", "(string)\.$", "new $1."); - replace-options: replace("One + one = 4", "one", "2", "gi"); + replace-with-flags: replace("One + one = 4", "one", "2", "gi"); format: %("rgb(%d, %d, %d)", @r, 128, 64); format-string: %("hello %s", "world"); format-multiple: %("hello %s %d", "earth", 2); From f7414a1072094240c0b464ddf8f20d67e7cd88de Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 9 Feb 2014 22:20:08 +0000 Subject: [PATCH 06/29] detached rulesets --- build/build.yml | 1 + lib/less/index.js | 1 + lib/less/parser.js | 59 ++++++++++++++++++++++++-------- lib/less/tree/ruleset-call.js | 15 ++++++++ lib/less/tree/ruleset.js | 13 +++++++ test/css/detached-rulesets.css | 7 ++++ test/less/detached-rulesets.less | 16 +++++++++ 7 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 lib/less/tree/ruleset-call.js create mode 100644 test/css/detached-rulesets.css create mode 100644 test/less/detached-rulesets.less diff --git a/build/build.yml b/build/build.yml index dd4236b9..68fa2607 100644 --- a/build/build.yml +++ b/build/build.yml @@ -143,6 +143,7 @@ tree: - <%= build.lib %>/tree/quoted.js - <%= build.lib %>/tree/rule.js - <%= build.lib %>/tree/ruleset.js + - <%= build.lib %>/tree/ruleset-call.js - <%= build.lib %>/tree/selector.js - <%= build.lib %>/tree/unicode-descriptor.js - <%= build.lib %>/tree/url.js diff --git a/lib/less/index.js b/lib/less/index.js index 7aee17c8..b85a84f9 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -120,6 +120,7 @@ require('./tree/media'); require('./tree/unicode-descriptor'); require('./tree/negative'); require('./tree/extend'); +require('./tree/ruleset-call'); var isUrlRe = /^(?:https?:)?\/\//i; diff --git a/lib/less/parser.js b/lib/less/parser.js index 0f19d487..04330f7e 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -729,7 +729,7 @@ less.Parser = function Parser(env) { while (current) { node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() || - mixin.call() || this.comment() || this.directive(); + mixin.call() || this.comment() || this.rulesetCall() || this.directive(); if (node) { root.push(node); } else { @@ -1027,6 +1027,19 @@ less.Parser = function Parser(env) { if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { return name[1]; } }, + // + // The variable part of a variable definition. Used in the `rule` parser + // + // @fink(); + // + rulesetCall: function () { + var name; + + if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) { + return new tree.RulesetCall(name[1]); + } + }, + // // extend syntax - used to extend selectors // @@ -1126,7 +1139,7 @@ less.Parser = function Parser(env) { while (true) { if (isCall) { - arg = parsers.expression(); + arg = parsers.blockRuleset() || parsers.expression(); } else { parsers.comments(); if (input.charAt(i) === '.' && $re(/^\.{3}/)) { @@ -1154,7 +1167,7 @@ less.Parser = function Parser(env) { if (isCall) { // Variable - if (arg.value.length == 1) { + if (arg.value && arg.value.length == 1) { val = arg.value[0]; } } else { @@ -1442,6 +1455,14 @@ less.Parser = function Parser(env) { } }, + blockRuleset: function() { + var block = this.block(); + if (block) { + block = new tree.Ruleset(null, block); + } + return block; + }, + // // div, .class, body > p {...} // @@ -1484,25 +1505,33 @@ less.Parser = function Parser(env) { } }, rule: function (tryAnonymous) { - var name, value, c = input.charAt(i), important, merge; + var name, value, c = input.charAt(i), important, merge, isVariable; save(); if (c === '.' || c === '#' || c === '&') { return; } name = this.variable() || this.ruleProperty(); if (name) { - // prefer to try to parse first if its a variable or we are compressing - // but always fallback on the other one - value = !tryAnonymous && (env.compress || (name.charAt && (name.charAt(0) === '@'))) ? - (this.value() || this.anonymousValue()) : - (this.anonymousValue() || this.value()); - - important = this.important(); + isVariable = typeof name === "string"; - // a name returned by this.ruleProperty() is always an array of the form: - // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"] - // where each item is a tree.Keyword or tree.Variable - merge = name.pop && name.pop().value; + if (isVariable) { + value = this.blockRuleset(); + } + + if (!value) { + // prefer to try to parse first if its a variable or we are compressing + // but always fallback on the other one + value = !tryAnonymous && (env.compress || isVariable) ? + (this.value() || this.anonymousValue()) : + (this.anonymousValue() || this.value()); + + important = this.important(); + + // a name returned by this.ruleProperty() is always an array of the form: + // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"] + // where each item is a tree.Keyword or tree.Variable + merge = !isVariable && name.pop().value; + } if (value && this.end()) { return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo); diff --git a/lib/less/tree/ruleset-call.js b/lib/less/tree/ruleset-call.js new file mode 100644 index 00000000..ec9e98d2 --- /dev/null +++ b/lib/less/tree/ruleset-call.js @@ -0,0 +1,15 @@ +(function (tree) { + +tree.RulesetCall = function (variable) { + this.variable = variable; +}; +tree.RulesetCall.prototype = { + type: "RulesetCall", + accept: function (visitor) { + }, + eval: function (env) { + return new(tree.Variable)(this.variable).eval(env); + } +}; + +})(require('../tree')); diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index e7895760..15cc80d9 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -90,6 +90,19 @@ tree.Ruleset.prototype = { rsRuleCnt += rules.length - 1; i += rules.length-1; ruleset.resetCache(); + } else if (rsRules[i] instanceof tree.RulesetCall) { + /*jshint loopfunc:true */ + rules = rsRules[i].eval(env).rules.filter(function(r) { + if ((r instanceof tree.Rule) && r.variable) { + // do not pollute the scope at all + return false; + } + return true; + }); + rsRules.splice.apply(rsRules, [i, 1].concat(rules)); + rsRuleCnt += rules.length - 1; + i += rules.length-1; + ruleset.resetCache(); } } diff --git a/test/css/detached-rulesets.css b/test/css/detached-rulesets.css new file mode 100644 index 00000000..08e82bc5 --- /dev/null +++ b/test/css/detached-rulesets.css @@ -0,0 +1,7 @@ +.wrap-selector { + color: black; +} +.wrap-selector { + color: black; + background: white; +} diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less new file mode 100644 index 00000000..49360763 --- /dev/null +++ b/test/less/detached-rulesets.less @@ -0,0 +1,16 @@ +@ruleset: { + color: black; + background: white; + }; + +.wrap-mixin(@ruleset) { + .wrap-selector { + @ruleset(); + } +}; + +.wrap-mixin({ + color: black; +}); + +.wrap-mixin(@ruleset); \ No newline at end of file From 55033c77ed7942d5223b07b90e5df119fc5ea3dd Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 11 Feb 2014 22:01:26 +0000 Subject: [PATCH 07/29] more tests and name arguments for caller --- lib/less/parser.js | 18 ++++++++++++++++-- test/css/detached-rulesets.css | 14 ++++++++++++++ test/less/detached-rulesets.less | 19 ++++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 4fa31f58..76f312e5 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1182,7 +1182,17 @@ less.Parser = function Parser(env) { } expressionContainsNamed = true; } - value = expect(parsers.expression); + // we do not support setting a ruleset as a default variable - it doesn't make sense + // and to implement it we need backtracking with multiple saves + value = (isCall && parsers.blockRuleset()) || parsers.expression(); + if (!value) { + if (isCall) { + error("could not understand value for named argument"); + } else { + returner.args = []; + return returner; + } + } nameLoop = (name = val.name); } else if (!isCall && $re(/^\.{3}/)) { returner.variadic = true; @@ -1267,10 +1277,14 @@ less.Parser = function Parser(env) { variadic = argInfo.variadic; // .mixincall("@{a}"); - // looks a bit like a mixin definition.. so we have to be nice and restore + // looks a bit like a mixin definition.. + // also + // .mixincall(@a: {rule: set;}); + // so we have to be nice and restore if (!$char(')')) { furthest = i; restore(); + return; } parsers.comments(); diff --git a/test/css/detached-rulesets.css b/test/css/detached-rulesets.css index 08e82bc5..dc706a62 100644 --- a/test/css/detached-rulesets.css +++ b/test/css/detached-rulesets.css @@ -1,7 +1,21 @@ .wrap-selector { color: black; } +.wrap-selector { + color: red; +} .wrap-selector { color: black; background: white; } +header { + background: blue; +} +@media screen and (min-width: 1200) { + header { + background: red; + } +} +html.lt-ie9 header { + background: red; +} diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less index 49360763..01abdd7e 100644 --- a/test/less/detached-rulesets.less +++ b/test/less/detached-rulesets.less @@ -13,4 +13,21 @@ color: black; }); -.wrap-mixin(@ruleset); \ No newline at end of file +.wrap-mixin(@ruleset: { + color: red; +}); + +.wrap-mixin(@ruleset); + +.desktop-and-old-ie(@rules) { + @media screen and (min-width: 1200) { @rules(); } + html.lt-ie9 & { @rules(); } +} + +header { + background: blue; + + .desktop-and-old-ie({ + background: red; + }); +} From 7f26515630dddc31cf95c3cc198f51acccdff230 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 11 Feb 2014 22:02:48 +0000 Subject: [PATCH 08/29] small simplification --- lib/less/parser.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 76f312e5..064e5f27 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1785,10 +1785,7 @@ less.Parser = function Parser(env) { } if (hasBlock) { - rules = this.block(); - if (rules) { - rules = new(tree.Ruleset)(null, rules); - } + rules = this.blockRuleset(); } if (rules || (!hasBlock && value && $char(';'))) { From ed0e13a0ecd2f2bb31d2874c61d351c54803245d Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 12 Feb 2014 22:34:58 +0000 Subject: [PATCH 09/29] do not chunk inside parens so that we can predict mixin calls containing detached rulesets. Also now save the current chunk (may not be required). --- lib/less/parser.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 064e5f27..fc3c1fe9 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -45,6 +45,7 @@ less.Parser = function Parser(env) { j, // current chunk temp, // temporarily holds a chunk's state, for backtracking memo, // temporarily holds `i`, when backtracking + memoChunk, // `j` furthest, // furthest index the parser has gone to chunks, // chunkified input current, // current chunk @@ -111,8 +112,8 @@ less.Parser = function Parser(env) { } }; - function save() { temp = current; memo = currentPos = i; } - function restore() { current = temp; currentPos = i = memo; } + function save() { temp = current; memo = currentPos = i; memoChunk = j; } + function restore() { current = temp; currentPos = i = memo; j = memoChunk; } function sync() { if (i > currentPos) { @@ -424,7 +425,7 @@ less.Parser = function Parser(env) { if (--level < 0) { return fail("missing opening `{`"); } - if (!level) { emitChunk(); } + if (!level && !parenLevel) { emitChunk(); } continue; case 92: // \ if (parserCurrentIndex < len - 1) { parserCurrentIndex++; continue; } From e3168e343460539556a86023833d1a033e013cba Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 12 Feb 2014 22:40:20 +0000 Subject: [PATCH 10/29] add some more failing tests --- test/less/detached-rulesets.less | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less index 01abdd7e..665fdfba 100644 --- a/test/less/detached-rulesets.less +++ b/test/less/detached-rulesets.less @@ -31,3 +31,18 @@ header { background: red; }); } + +.wrap-mixin-calls-wrap(@ruleset) { + .wrap-mixin(@ruleset); +}; + +.wrap-mixin({ + test: extra-wrap; + .wrap-mixin-calls-wrap({ + test: wrapped-twice; + }); +}); + +.wrap-mixin({ + test-func: unit(90px); +}); From e3576b9c01799ee7e033b7413c8ce1b9bdfbe805 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 12 Feb 2014 23:10:52 +0000 Subject: [PATCH 11/29] implement n level back-tracking and then don't absorb a parenthesis, fixing both issues with 2 new test cases --- lib/less/parser.js | 26 +++++++++++++++++++------- test/css/detached-rulesets.css | 10 ++++++++++ test/less/detached-rulesets.less | 1 + 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index fc3c1fe9..8231bf83 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -43,9 +43,7 @@ less.Parser = function Parser(env) { var input, // LeSS input string i, // current index in `input` j, // current chunk - temp, // temporarily holds a chunk's state, for backtracking - memo, // temporarily holds `i`, when backtracking - memoChunk, // `j` + saveStack = [], // holds state for backtracking furthest, // furthest index the parser has gone to chunks, // chunkified input current, // current chunk @@ -112,8 +110,9 @@ less.Parser = function Parser(env) { } }; - function save() { temp = current; memo = currentPos = i; memoChunk = j; } - function restore() { current = temp; currentPos = i = memo; j = memoChunk; } + function save() { currentPos = i; saveStack.push( { current: current, i: i, j: j }); } + function restore() { var state = saveStack.pop(); current = state.current; currentPos = i = state.i; j = state.j; } + function forget() { saveStack.pop(); } function sync() { if (i > currentPos) { @@ -1126,6 +1125,7 @@ less.Parser = function Parser(env) { } if (parsers.end()) { + forget(); return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important); } } @@ -1297,6 +1297,7 @@ less.Parser = function Parser(env) { ruleset = parsers.block(); if (ruleset) { + forget(); return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic); } else { restore(); @@ -1364,10 +1365,16 @@ less.Parser = function Parser(env) { this.entities.variableCurly(); if (! e) { + save(); if ($char('(')) { if ((v = this.selector()) && $char(')')) { e = new(tree.Paren)(v); + forget(); + } else { + restore(); } + } else { + forget(); } } @@ -1508,6 +1515,7 @@ less.Parser = function Parser(env) { } if (selectors && (rules = this.block())) { + forget(); var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports); if (env.dumpLineNumbers) { ruleset.debugInfo = debugInfo; @@ -1520,7 +1528,7 @@ less.Parser = function Parser(env) { } }, rule: function (tryAnonymous) { - var name, value, c = input.charAt(i), important, merge, isVariable; + var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable; save(); if (c === '.' || c === '#' || c === '&') { return; } @@ -1549,7 +1557,7 @@ less.Parser = function Parser(env) { } if (value && this.end()) { - return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo); + return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo); } else { furthest = i; restore(); @@ -1557,6 +1565,8 @@ less.Parser = function Parser(env) { return this.rule(true); } } + } else { + forget(); } }, anonymousValue: function () { @@ -1590,6 +1600,7 @@ less.Parser = function Parser(env) { if (dir && (path = this.entities.quoted() || this.entities.url())) { features = this.mediaFeatures(); if ($char(';')) { + forget(); features = features && new(tree.Value)(features); return new(tree.Import)(path, features, options, index, env.currentFileInfo); } @@ -1790,6 +1801,7 @@ less.Parser = function Parser(env) { } if (rules || (!hasBlock && value && $char(';'))) { + forget(); return new(tree.Directive)(name, value, rules, index, env.currentFileInfo, env.dumpLineNumbers ? getDebugInfo(index, input, env) : null); } diff --git a/test/css/detached-rulesets.css b/test/css/detached-rulesets.css index dc706a62..e6b646a4 100644 --- a/test/css/detached-rulesets.css +++ b/test/css/detached-rulesets.css @@ -19,3 +19,13 @@ header { html.lt-ie9 header { background: red; } +.wrap-selector { + test: extra-wrap; +} +.wrap-selector .wrap-selector { + test: wrapped-twice; +} +.wrap-selector { + test-func: 90; + test-arithmetic: 18px; +} diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less index 665fdfba..3829f681 100644 --- a/test/less/detached-rulesets.less +++ b/test/less/detached-rulesets.less @@ -45,4 +45,5 @@ header { .wrap-mixin({ test-func: unit(90px); + test-arithmetic: unit((9+9), px); }); From ef3c63fb9ae920f1e969b707ba296ac6d930e579 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 12 Feb 2014 23:34:14 +0000 Subject: [PATCH 12/29] Test out some theoretical back tracking and fix bugs --- lib/less/parser.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 8231bf83..46c74cfc 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1138,6 +1138,8 @@ less.Parser = function Parser(env) { expressions = [], argsSemiColon = [], argsComma = [], isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg; + save(); + while (true) { if (isCall) { arg = parsers.blockRuleset() || parsers.expression(); @@ -1183,13 +1185,17 @@ less.Parser = function Parser(env) { } expressionContainsNamed = true; } + // we do not support setting a ruleset as a default variable - it doesn't make sense - // and to implement it we need backtracking with multiple saves + // However if we do want to add it, there is nothing blocking it, just don't error + // and remove isCall dependency below value = (isCall && parsers.blockRuleset()) || parsers.expression(); + if (!value) { if (isCall) { error("could not understand value for named argument"); } else { + restore(); returner.args = []; return returner; } @@ -1238,6 +1244,7 @@ less.Parser = function Parser(env) { } } + forget(); returner.args = isSemiColonSeperated ? argsSemiColon : argsComma; return returner; }, @@ -1302,6 +1309,8 @@ less.Parser = function Parser(env) { } else { restore(); } + } else { + forget(); } } }, @@ -1529,10 +1538,11 @@ less.Parser = function Parser(env) { }, rule: function (tryAnonymous) { var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable; - save(); if (c === '.' || c === '#' || c === '&') { return; } + save(); + name = this.variable() || this.ruleProperty(); if (name) { isVariable = typeof name === "string"; @@ -1557,6 +1567,7 @@ less.Parser = function Parser(env) { } if (value && this.end()) { + forget(); return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo); } else { furthest = i; From 15174c0860010be3969fed3008b2c79ce2cae8ad Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 12 Feb 2014 23:38:35 +0000 Subject: [PATCH 13/29] small approx 1% speed improvement --- lib/less/parser.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/less/parser.js b/lib/less/parser.js index 46c74cfc..7e0c4a22 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -737,6 +737,9 @@ less.Parser = function Parser(env) { break; } } + if (peekChar('}')) { + break; + } } return root; From e0692fa199fc6828f0136da05948cb2c0194b611 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 13 Feb 2014 20:36:34 +0000 Subject: [PATCH 14/29] add scope tests to the detached ruleset test-set --- test/css/detached-rulesets.css | 13 +++++++++++++ test/less/detached-rulesets.less | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/test/css/detached-rulesets.css b/test/css/detached-rulesets.css index e6b646a4..b98bf60b 100644 --- a/test/css/detached-rulesets.css +++ b/test/css/detached-rulesets.css @@ -1,12 +1,19 @@ .wrap-selector { color: black; + one: 1px; + visible-one: visible; + visible-two: visible; } .wrap-selector { color: red; + visible-one: visible; + visible-two: visible; } .wrap-selector { color: black; background: white; + visible-one: visible; + visible-two: visible; } header { background: blue; @@ -21,11 +28,17 @@ html.lt-ie9 header { } .wrap-selector { test: extra-wrap; + visible-one: visible; + visible-two: visible; } .wrap-selector .wrap-selector { test: wrapped-twice; + visible-one: visible; + visible-two: visible; } .wrap-selector { test-func: 90; test-arithmetic: 18px; + visible-one: visible; + visible-two: visible; } diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less index 3829f681..57c2cb43 100644 --- a/test/less/detached-rulesets.less +++ b/test/less/detached-rulesets.less @@ -3,14 +3,23 @@ background: white; }; +@a: 1px; .wrap-mixin(@ruleset) { + @a: hidden and if you see this in the output its a bug; + @b: visible; .wrap-selector { + @c: visible; @ruleset(); + visible-one: @b; + visible-two: @c; } }; .wrap-mixin({ color: black; + one: @a; + @b: hidden and if you see this in the output its a bug; + @c: hidden and if you see this in the output its a bug; }); .wrap-mixin(@ruleset: { From b46ca11286e3c4fb8eb351ff01725cc9c4223add Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 13 Feb 2014 21:42:32 +0000 Subject: [PATCH 15/29] error tests and test detached rulesets without a mixin call --- lib/less/tree/rule.js | 17 ++++++++++++++--- test/css/detached-rulesets.css | 3 +++ test/less/detached-rulesets.less | 7 +++++++ test/less/errors/detached-ruleset-1.less | 6 ++++++ test/less/errors/detached-ruleset-1.txt | 4 ++++ test/less/errors/detached-ruleset-2.less | 6 ++++++ test/less/errors/detached-ruleset-2.txt | 4 ++++ test/less/errors/detached-ruleset-3.less | 4 ++++ test/less/errors/detached-ruleset-3.txt | 4 ++++ test/less/errors/detached-ruleset-4.less | 5 +++++ test/less/errors/detached-ruleset-4.txt | 3 +++ test/less/errors/detached-ruleset-5.less | 4 ++++ test/less/errors/detached-ruleset-5.txt | 3 +++ test/less/errors/detached-ruleset-6.less | 5 +++++ test/less/errors/detached-ruleset-6.txt | 4 ++++ 15 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 test/less/errors/detached-ruleset-1.less create mode 100644 test/less/errors/detached-ruleset-1.txt create mode 100644 test/less/errors/detached-ruleset-2.less create mode 100644 test/less/errors/detached-ruleset-2.txt create mode 100644 test/less/errors/detached-ruleset-3.less create mode 100644 test/less/errors/detached-ruleset-3.txt create mode 100644 test/less/errors/detached-ruleset-4.less create mode 100644 test/less/errors/detached-ruleset-4.txt create mode 100644 test/less/errors/detached-ruleset-5.less create mode 100644 test/less/errors/detached-ruleset-5.txt create mode 100644 test/less/errors/detached-ruleset-6.less create mode 100644 test/less/errors/detached-ruleset-6.txt diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index a2d14f2b..ac90049c 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -30,7 +30,7 @@ tree.Rule.prototype = { }, toCSS: tree.toCSS, eval: function (env) { - var strictMathBypass = false, name = this.name; + var strictMathBypass = false, name = this.name, evaldValue; if (typeof name !== "string") { // expand 'primitive' name directly to get // things faster (~10% for benchmark.less): @@ -43,14 +43,25 @@ tree.Rule.prototype = { env.strictMath = true; } try { + evaldValue = this.value.eval(env); + + if (!this.variable && evaldValue.type === "Ruleset") { + console.log(this.index); + throw { message: "Rulesets cannot be evaluated on a property.", + index: this.index, filename: this.currentFileInfo.filename }; + } + return new(tree.Rule)(name, - this.value.eval(env), + evaldValue, this.important, this.merge, this.index, this.currentFileInfo, this.inline); } catch(e) { - e.index = e.index || this.index; + if (typeof e.index !== 'number') { + e.index = this.index; + e.filename = this.currentFileInfo.filename; + } throw e; } finally { diff --git a/test/css/detached-rulesets.css b/test/css/detached-rulesets.css index b98bf60b..d723a0c4 100644 --- a/test/css/detached-rulesets.css +++ b/test/css/detached-rulesets.css @@ -42,3 +42,6 @@ html.lt-ie9 header { visible-one: visible; visible-two: visible; } +.without-mixins { + b: 1; +} diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less index 57c2cb43..787cc19b 100644 --- a/test/less/detached-rulesets.less +++ b/test/less/detached-rulesets.less @@ -56,3 +56,10 @@ header { test-func: unit(90px); test-arithmetic: unit((9+9), px); }); +// without mixins +@ruleset-2: { + b: 1; +}; +.without-mixins { + @ruleset-2(); +} diff --git a/test/less/errors/detached-ruleset-1.less b/test/less/errors/detached-ruleset-1.less new file mode 100644 index 00000000..ac5b8db0 --- /dev/null +++ b/test/less/errors/detached-ruleset-1.less @@ -0,0 +1,6 @@ +@a: { + b: 1; +}; +.a { + a: @a; +} \ No newline at end of file diff --git a/test/less/errors/detached-ruleset-1.txt b/test/less/errors/detached-ruleset-1.txt new file mode 100644 index 00000000..7407741c --- /dev/null +++ b/test/less/errors/detached-ruleset-1.txt @@ -0,0 +1,4 @@ +SyntaxError: Rulesets cannot be evaluated on a property. in {path}detached-ruleset-1.less on line 5, column 3: +4 .a { +5 a: @a; +6 } diff --git a/test/less/errors/detached-ruleset-2.less b/test/less/errors/detached-ruleset-2.less new file mode 100644 index 00000000..51a7af6b --- /dev/null +++ b/test/less/errors/detached-ruleset-2.less @@ -0,0 +1,6 @@ +@a: { + b: 1; +}; +.a { + a: @a(); +} \ No newline at end of file diff --git a/test/less/errors/detached-ruleset-2.txt b/test/less/errors/detached-ruleset-2.txt new file mode 100644 index 00000000..f18e0935 --- /dev/null +++ b/test/less/errors/detached-ruleset-2.txt @@ -0,0 +1,4 @@ +ParseError: Unrecognised input in {path}detached-ruleset-2.less on line 5, column 3: +4 .a { +5 a: @a(); +6 } diff --git a/test/less/errors/detached-ruleset-3.less b/test/less/errors/detached-ruleset-3.less new file mode 100644 index 00000000..c50119d9 --- /dev/null +++ b/test/less/errors/detached-ruleset-3.less @@ -0,0 +1,4 @@ +@a: { + b: 1; +}; +@a(); \ No newline at end of file diff --git a/test/less/errors/detached-ruleset-3.txt b/test/less/errors/detached-ruleset-3.txt new file mode 100644 index 00000000..15d281fa --- /dev/null +++ b/test/less/errors/detached-ruleset-3.txt @@ -0,0 +1,4 @@ +SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}detached-ruleset-3.less on line 2, column 3: +1 @a: { +2 b: 1; +3 }; diff --git a/test/less/errors/detached-ruleset-4.less b/test/less/errors/detached-ruleset-4.less new file mode 100644 index 00000000..14ac314b --- /dev/null +++ b/test/less/errors/detached-ruleset-4.less @@ -0,0 +1,5 @@ +.mixin-definition(@a: { + b: 1; +}) { + @a(); +} \ No newline at end of file diff --git a/test/less/errors/detached-ruleset-4.txt b/test/less/errors/detached-ruleset-4.txt new file mode 100644 index 00000000..d6d6526d --- /dev/null +++ b/test/less/errors/detached-ruleset-4.txt @@ -0,0 +1,3 @@ +ParseError: Unrecognised input in {path}detached-ruleset-4.less on line 1, column 18: +1 .mixin-definition(@a: { +2 b: 1; diff --git a/test/less/errors/detached-ruleset-5.less b/test/less/errors/detached-ruleset-5.less new file mode 100644 index 00000000..174ebf35 --- /dev/null +++ b/test/less/errors/detached-ruleset-5.less @@ -0,0 +1,4 @@ +.mixin-definition(@b) { + @a(); +} +.mixin-definition({color: red;}); \ No newline at end of file diff --git a/test/less/errors/detached-ruleset-5.txt b/test/less/errors/detached-ruleset-5.txt new file mode 100644 index 00000000..56189795 --- /dev/null +++ b/test/less/errors/detached-ruleset-5.txt @@ -0,0 +1,3 @@ +SyntaxError: variable @a is undefined in {path}detached-ruleset-5.less on line 4, column 1: +3 } +4 .mixin-definition({color: red;}); diff --git a/test/less/errors/detached-ruleset-6.less b/test/less/errors/detached-ruleset-6.less new file mode 100644 index 00000000..121099f7 --- /dev/null +++ b/test/less/errors/detached-ruleset-6.less @@ -0,0 +1,5 @@ +.a { + b: { + color: red; + }; +} \ No newline at end of file diff --git a/test/less/errors/detached-ruleset-6.txt b/test/less/errors/detached-ruleset-6.txt new file mode 100644 index 00000000..07840445 --- /dev/null +++ b/test/less/errors/detached-ruleset-6.txt @@ -0,0 +1,4 @@ +ParseError: Unrecognised input in {path}detached-ruleset-6.less on line 2, column 3: +1 .a { +2 b: { +3 color: red; From c730829d1d609f250b522775fa1745537f6b77a4 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 16 Feb 2014 17:50:51 +0000 Subject: [PATCH 16/29] Fix one issue with media queries and detached rulesets, one to go --- lib/less/tree/media.js | 2 ++ lib/less/tree/rule.js | 3 +-- test/css/detached-rulesets.css | 17 +++++++++++++ test/less/detached-rulesets.less | 41 ++++++++++++++++++++++++++++++-- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index c3b85107..4531cf4c 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -147,6 +147,8 @@ tree.Media.prototype = { } }, bubbleSelectors: function (selectors) { + if (!selectors) + return; this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])]; } }; diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index ac90049c..5567b076 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -2,7 +2,7 @@ tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline) { this.name = name; - this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]); + this.value = (value instanceof tree.Value || value instanceof tree.Ruleset) ? value : new(tree.Value)([value]); this.important = important ? ' ' + important.trim() : ''; this.merge = merge; this.index = index; @@ -46,7 +46,6 @@ tree.Rule.prototype = { evaldValue = this.value.eval(env); if (!this.variable && evaldValue.type === "Ruleset") { - console.log(this.index); throw { message: "Rulesets cannot be evaluated on a property.", index: this.index, filename: this.currentFileInfo.filename }; } diff --git a/test/css/detached-rulesets.css b/test/css/detached-rulesets.css index d723a0c4..b9642d4e 100644 --- a/test/css/detached-rulesets.css +++ b/test/css/detached-rulesets.css @@ -45,3 +45,20 @@ html.lt-ie9 header { .without-mixins { b: 1; } +@media (orientation: portrait) { + /* .wrap-media-mixin({ + @media tv { + .triple-wrapped-mq { + triple: true; + } + } + });*/ +} +@media (orientation: portrait) and tv { + .my-selector { + background-color: black; + } +} +.a { + test: test; +} diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less index 787cc19b..5af2a745 100644 --- a/test/less/detached-rulesets.less +++ b/test/less/detached-rulesets.less @@ -10,8 +10,8 @@ .wrap-selector { @c: visible; @ruleset(); - visible-one: @b; - visible-two: @c; + visible-one: @b; + visible-two: @c; } }; @@ -63,3 +63,40 @@ header { .without-mixins { @ruleset-2(); } +@my-ruleset: { + .my-selector { + @media tv { + background-color: black; + } + } + }; +@media (orientation:portrait) { + @my-ruleset(); + // doesn't work yet +/* .wrap-media-mixin({ + @media tv { + .triple-wrapped-mq { + triple: true; + } + } + });*/ +} +.wrap-media-mixin(@ruleset) { + @media widescreen { + @media print { + @ruleset(); + } + @ruleset(); + } + @ruleset(); +} +// unlocking mixins +@my-mixins: { + .mixin() { + test: test; + } +}; +@my-mixins(); +.a { + .mixin(); +} \ No newline at end of file From baba33ea6a1a4b9a42fa07870c14581a78e05559 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 17 Feb 2014 19:15:47 +0000 Subject: [PATCH 17/29] Fix some bugs with detached rulesets and media queries --- build/build.yml | 1 + lib/less/index.js | 1 + lib/less/parser.js | 14 +++++++++++--- lib/less/tree/detached-ruleset.js | 21 +++++++++++++++++++++ lib/less/tree/rule.js | 2 +- lib/less/tree/ruleset-call.js | 3 ++- lib/less/tree/ruleset.js | 3 +++ test/css/detached-rulesets.css | 25 ++++++++++++++++--------- test/less/detached-rulesets.less | 10 ++++++---- 9 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 lib/less/tree/detached-ruleset.js diff --git a/build/build.yml b/build/build.yml index 68fa2607..b5a2234a 100644 --- a/build/build.yml +++ b/build/build.yml @@ -127,6 +127,7 @@ tree: - <%= build.lib %>/tree/color.js - <%= build.lib %>/tree/comment.js - <%= build.lib %>/tree/condition.js + - <%= build.lib %>/tree/detached-ruleset.js - <%= build.lib %>/tree/dimension.js - <%= build.lib %>/tree/directive.js - <%= build.lib %>/tree/element.js diff --git a/lib/less/index.js b/lib/less/index.js index b85a84f9..a18b8179 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -94,6 +94,7 @@ var less = { require('./tree/color'); require('./tree/directive'); +require('./tree/detached-ruleset'); require('./tree/operation'); require('./tree/dimension'); require('./tree/keyword'); diff --git a/lib/less/parser.js b/lib/less/parser.js index 7e0c4a22..032381dc 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -1145,7 +1145,7 @@ less.Parser = function Parser(env) { while (true) { if (isCall) { - arg = parsers.blockRuleset() || parsers.expression(); + arg = parsers.detachedRuleset() || parsers.expression(); } else { parsers.comments(); if (input.charAt(i) === '.' && $re(/^\.{3}/)) { @@ -1192,7 +1192,7 @@ less.Parser = function Parser(env) { // we do not support setting a ruleset as a default variable - it doesn't make sense // However if we do want to add it, there is nothing blocking it, just don't error // and remove isCall dependency below - value = (isCall && parsers.blockRuleset()) || parsers.expression(); + value = (isCall && parsers.detachedRuleset()) || parsers.expression(); if (!value) { if (isCall) { @@ -1491,11 +1491,19 @@ less.Parser = function Parser(env) { blockRuleset: function() { var block = this.block(); + if (block) { block = new tree.Ruleset(null, block); } return block; }, + + detachedRuleset: function() { + var blockRuleset = this.blockRuleset(); + if (blockRuleset) { + return new tree.DetachedRuleset(blockRuleset); + } + }, // // div, .class, body > p {...} @@ -1551,7 +1559,7 @@ less.Parser = function Parser(env) { isVariable = typeof name === "string"; if (isVariable) { - value = this.blockRuleset(); + value = this.detachedRuleset(); } if (!value) { diff --git a/lib/less/tree/detached-ruleset.js b/lib/less/tree/detached-ruleset.js new file mode 100644 index 00000000..87e76f26 --- /dev/null +++ b/lib/less/tree/detached-ruleset.js @@ -0,0 +1,21 @@ +(function (tree) { + +tree.DetachedRuleset = function (ruleset, frames) { + this.ruleset = ruleset; + this.frames = frames; +}; +tree.DetachedRuleset.prototype = { + type: "DetachedRuleset", + accept: function (visitor) { + this.ruleset = visitor.visit(this.ruleset); + }, + eval: function (env) { + // TODO - handle mixin definition like this + var frames = this.frames || env.frames.slice(0); + return new tree.DetachedRuleset(this.ruleset, frames); + }, + callEval: function (env) { + return this.ruleset.eval(new(tree.evalEnv)(env, this.frames.concat(env.frames))); + } +}; +})(require('../tree')); diff --git a/lib/less/tree/rule.js b/lib/less/tree/rule.js index 5567b076..4e1ccde6 100644 --- a/lib/less/tree/rule.js +++ b/lib/less/tree/rule.js @@ -45,7 +45,7 @@ tree.Rule.prototype = { try { evaldValue = this.value.eval(env); - if (!this.variable && evaldValue.type === "Ruleset") { + if (!this.variable && evaldValue.type === "DetachedRuleset") { throw { message: "Rulesets cannot be evaluated on a property.", index: this.index, filename: this.currentFileInfo.filename }; } diff --git a/lib/less/tree/ruleset-call.js b/lib/less/tree/ruleset-call.js index ec9e98d2..a543c55e 100644 --- a/lib/less/tree/ruleset-call.js +++ b/lib/less/tree/ruleset-call.js @@ -8,7 +8,8 @@ tree.RulesetCall.prototype = { accept: function (visitor) { }, eval: function (env) { - return new(tree.Variable)(this.variable).eval(env); + var detachedRuleset = new(tree.Variable)(this.variable).eval(env); + return detachedRuleset.callEval(env); } }; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 15cc80d9..3ec0706d 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -358,6 +358,9 @@ tree.Ruleset.prototype = { toCSS: tree.toCSS, markReferenced: function () { + if (!this.selectors) { + return; + } for (var s = 0; s < this.selectors.length; s++) { this.selectors[s].markReferenced(); } diff --git a/test/css/detached-rulesets.css b/test/css/detached-rulesets.css index b9642d4e..300c08d0 100644 --- a/test/css/detached-rulesets.css +++ b/test/css/detached-rulesets.css @@ -1,6 +1,7 @@ .wrap-selector { color: black; one: 1px; + four: magic-frame; visible-one: visible; visible-two: visible; } @@ -45,20 +46,26 @@ html.lt-ie9 header { .without-mixins { b: 1; } -@media (orientation: portrait) { - /* .wrap-media-mixin({ - @media tv { - .triple-wrapped-mq { - triple: true; - } - } - });*/ -} @media (orientation: portrait) and tv { .my-selector { background-color: black; } } +@media (orientation: portrait) and widescreen and print and tv { + .triple-wrapped-mq { + triple: true; + } +} +@media (orientation: portrait) and widescreen and tv { + .triple-wrapped-mq { + triple: true; + } +} +@media (orientation: portrait) and tv { + .triple-wrapped-mq { + triple: true; + } +} .a { test: test; } diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less index 5af2a745..7e124117 100644 --- a/test/less/detached-rulesets.less +++ b/test/less/detached-rulesets.less @@ -7,6 +7,7 @@ .wrap-mixin(@ruleset) { @a: hidden and if you see this in the output its a bug; @b: visible; + @d: magic-frame; // same behaviour as mixin calls - falls back to this frame .wrap-selector { @c: visible; @ruleset(); @@ -20,6 +21,7 @@ one: @a; @b: hidden and if you see this in the output its a bug; @c: hidden and if you see this in the output its a bug; + four: @d; }); .wrap-mixin(@ruleset: { @@ -72,14 +74,13 @@ header { }; @media (orientation:portrait) { @my-ruleset(); - // doesn't work yet -/* .wrap-media-mixin({ + .wrap-media-mixin({ @media tv { .triple-wrapped-mq { triple: true; } } - });*/ + }); } .wrap-media-mixin(@ruleset) { @media widescreen { @@ -99,4 +100,5 @@ header { @my-mixins(); .a { .mixin(); -} \ No newline at end of file +} +// Same fallback frame behaviour as mixin calls \ No newline at end of file From 88b44dfc43d9dd9e11a8993be8635ed85d359715 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 17 Feb 2014 19:50:43 +0000 Subject: [PATCH 18/29] make mixin definitions have similar coding style to detached rulesets for grabbing frames --- lib/less/tree/detached-ruleset.js | 3 +-- lib/less/tree/mixin.js | 13 ++++++++----- lib/less/tree/ruleset.js | 6 +++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/less/tree/detached-ruleset.js b/lib/less/tree/detached-ruleset.js index 87e76f26..0a5512aa 100644 --- a/lib/less/tree/detached-ruleset.js +++ b/lib/less/tree/detached-ruleset.js @@ -10,12 +10,11 @@ tree.DetachedRuleset.prototype = { this.ruleset = visitor.visit(this.ruleset); }, eval: function (env) { - // TODO - handle mixin definition like this var frames = this.frames || env.frames.slice(0); return new tree.DetachedRuleset(this.ruleset, frames); }, callEval: function (env) { - return this.ruleset.eval(new(tree.evalEnv)(env, this.frames.concat(env.frames))); + return this.ruleset.eval(this.frames ? new(tree.evalEnv)(env, this.frames.concat(env.frames)) : env); } }; })(require('../tree')); diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 1d903e87..506561ba 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -103,7 +103,7 @@ tree.mixin.Call.prototype = { mixin.originalRuleset = mixins[m].originalRuleset || mixins[m]; } Array.prototype.push.apply( - rules, mixin.eval(env, args, this.important).rules); + rules, mixin.evalCall(env, args, this.important).rules); } catch (e) { throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack }; } @@ -150,7 +150,7 @@ tree.mixin.Call.prototype = { } }; -tree.mixin.Definition = function (name, params, rules, condition, variadic) { +tree.mixin.Definition = function (name, params, rules, condition, variadic, frames) { this.name = name; this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])]; this.params = params; @@ -164,7 +164,7 @@ tree.mixin.Definition = function (name, params, rules, condition, variadic) { else { return count; } }, 0); this.parent = tree.Ruleset.prototype; - this.frames = []; + this.frames = frames; }; tree.mixin.Definition.prototype = { type: "MixinDefinition", @@ -258,9 +258,12 @@ tree.mixin.Definition.prototype = { return frame; }, - eval: function (env, args, important) { + eval: function (env) { + return new tree.mixin.Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || env.frames.slice(0)); + }, + evalCall: function (env, args, important) { var _arguments = [], - mixinFrames = this.frames.concat(env.frames), + mixinFrames = this.frames ? this.frames.concat(env.frames) : env.frames, frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments), rules, ruleset; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 3ec0706d..7571b0e7 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -66,8 +66,8 @@ tree.Ruleset.prototype = { // so they can be evaluated like closures when the time comes. var rsRules = ruleset.rules, rsRuleCnt = rsRules ? rsRules.length : 0; for (i = 0; i < rsRuleCnt; i++) { - if (rsRules[i] instanceof tree.mixin.Definition) { - rsRules[i].frames = envFrames.slice(0); + if (rsRules[i] instanceof tree.mixin.Definition || rsRules[i] instanceof tree.DetachedRuleset) { + rsRules[i] = rsRules[i].eval(env); } } @@ -109,7 +109,7 @@ tree.Ruleset.prototype = { // Evaluate everything else for (i = 0; i < rsRules.length; i++) { rule = rsRules[i]; - if (! (rule instanceof tree.mixin.Definition)) { + if (! (rule instanceof tree.mixin.Definition || rule instanceof tree.DetachedRuleset)) { rsRules[i] = rule = rule.eval ? rule.eval(env) : rule; // for rulesets, check if it is a css guard and can be removed if (rule instanceof tree.Ruleset && rule.selectors && rule.selectors.length === 1) { From 42aff6f35c8890039c8dee6d65dfd04c94336904 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 17 Feb 2014 19:53:35 +0000 Subject: [PATCH 19/29] remove bad comment --- test/less/detached-rulesets.less | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/less/detached-rulesets.less b/test/less/detached-rulesets.less index 7e124117..6a98d890 100644 --- a/test/less/detached-rulesets.less +++ b/test/less/detached-rulesets.less @@ -100,5 +100,4 @@ header { @my-mixins(); .a { .mixin(); -} -// Same fallback frame behaviour as mixin calls \ No newline at end of file +} \ No newline at end of file From d31e2d57407c483d6a3270f7d0fe4ae9a9d4099c Mon Sep 17 00:00:00 2001 From: Luke Page Date: Mon, 17 Feb 2014 20:25:18 +0000 Subject: [PATCH 20/29] upgrade dependencies --- bin/lessc | 6 +++--- package.json | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bin/lessc b/bin/lessc index 9c27f5f0..8b2fc2bd 100755 --- a/bin/lessc +++ b/bin/lessc @@ -231,13 +231,13 @@ args = args.filter(function (arg) { case "--advanced": cleancssOptions.noAdvanced = false; break; - case "--selectors-merge-mode": - cleancssOptions.selectorsMergeMode = cleanOptionArgs[1]; + case "--compatability": + cleancssOptions.compatability = cleanOptionArgs[1]; break; default: console.log("unrecognised clean-css option '" + cleanOptionArgs[0] + "'"); console.log("we support only arguments that make sense for less, '--keep-line-breaks', '-b'"); - console.log("'--s0', '--s1', '--advanced', '--skip-advanced', '--selectors-merge-mode'"); + console.log("'--s0', '--s1', '--advanced', '--skip-advanced', '--compatability'"); continueProcessing = false; currentErrorcode = 1; break; diff --git a/package.json b/package.json index 45b0dd9c..d79d9f9a 100644 --- a/package.json +++ b/package.json @@ -41,24 +41,24 @@ }, "optionalDependencies": { "mime": "1.2.x", - "request": ">=2.12.0", + "request": ">=2.33.0", "mkdirp": "~0.3.5", - "clean-css": "2.0.x", + "clean-css": "2.1.x", "source-map": "0.1.x" }, "devDependencies": { "diff": "~1.0", - "grunt": "~0.4.1", + "grunt": "~0.4.2", "grunt-contrib-clean": "~0.5.0", "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-connect": "~0.3.0", + "grunt-contrib-connect": "~0.6.0", "grunt-contrib-jasmine": "~0.5.2", - "grunt-contrib-jshint": "~0.7.2", - "grunt-contrib-uglify": "~0.2.7", - "grunt-shell": "~0.3.1", - "http-server": "~0.5.5", - "matchdep": "~0.1.2", - "time-grunt": "~0.1.1" + "grunt-contrib-jshint": "~0.8.0", + "grunt-contrib-uglify": "~0.3.2", + "grunt-shell": "~0.6.4", + "http-server": "~0.6.1", + "matchdep": "~0.3.0", + "time-grunt": "~0.2.9" }, "keywords": [ "compile less", From f3b9713ba30572a29fbed0d51345343a12dcb87b Mon Sep 17 00:00:00 2001 From: Luke Page Date: Tue, 18 Feb 2014 07:21:22 +0000 Subject: [PATCH 21/29] correct spelling of compatibility option --- bin/lessc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/lessc b/bin/lessc index 8b2fc2bd..b36c062a 100755 --- a/bin/lessc +++ b/bin/lessc @@ -231,13 +231,13 @@ args = args.filter(function (arg) { case "--advanced": cleancssOptions.noAdvanced = false; break; - case "--compatability": - cleancssOptions.compatability = cleanOptionArgs[1]; + case "--compatibility": + cleancssOptions.compatibility = cleanOptionArgs[1]; break; default: console.log("unrecognised clean-css option '" + cleanOptionArgs[0] + "'"); console.log("we support only arguments that make sense for less, '--keep-line-breaks', '-b'"); - console.log("'--s0', '--s1', '--advanced', '--skip-advanced', '--compatability'"); + console.log("'--s0', '--s1', '--advanced', '--skip-advanced', '--compatibility'"); continueProcessing = false; currentErrorcode = 1; break; From d3c0b204c7c2d7071474a4c1b3a4dc5560b3be35 Mon Sep 17 00:00:00 2001 From: seven-phases-max Date: Tue, 18 Feb 2014 15:22:14 +0400 Subject: [PATCH 22/29] minor `replace` func improvement: preserve quote char and escaped flag. --- lib/less/functions.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/less/functions.js b/lib/less/functions.js index 413ae98e..552f052c 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -222,8 +222,7 @@ tree.functions = { var str = subject.value; str = str.replace(new RegExp(pattern.value, flags ? flags.value : ""), replacement.value); - - return new(tree.Quoted)('"' + str + '"', str); + return new(tree.Quoted)(subject.quote || '', str, subject.escaped); }, '%': function (quoted /* arg, arg, ...*/) { var args = Array.prototype.slice.call(arguments, 1), From 768a5cbc8459d82c4cc725719024900b6b264850 Mon Sep 17 00:00:00 2001 From: seven-phases-max Date: Tue, 18 Feb 2014 18:56:46 +0400 Subject: [PATCH 23/29] minor `%` func improvement: preserve quote char and escaped flag, updated tests. --- lib/less/functions.js | 18 +++++++++--------- test/css/functions.css | 5 +++++ test/less/functions.less | 7 ++++++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/less/functions.js b/lib/less/functions.js index 552f052c..2c269a98 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -218,25 +218,25 @@ tree.functions = { escape: function (str) { return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); }, - replace: function (subject, pattern, replacement, flags) { - var str = subject.value; + replace: function (string, pattern, replacement, flags) { + var result = string.value; - str = str.replace(new RegExp(pattern.value, flags ? flags.value : ""), replacement.value); - return new(tree.Quoted)(subject.quote || '', str, subject.escaped); + result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value); + return new(tree.Quoted)(string.quote || '', result, string.escaped); }, - '%': function (quoted /* arg, arg, ...*/) { + '%': function (string /* arg, arg, ...*/) { var args = Array.prototype.slice.call(arguments, 1), - str = quoted.value; + result = string.value; for (var i = 0; i < args.length; i++) { /*jshint loopfunc:true */ - str = str.replace(/%[sda]/i, function(token) { + result = result.replace(/%[sda]/i, function(token) { var value = token.match(/s/i) ? args[i].value : args[i].toCSS(); return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value; }); } - str = str.replace(/%%/g, '%'); - return new(tree.Quoted)('"' + str + '"', str); + result = result.replace(/%%/g, '%'); + return new(tree.Quoted)(string.quote || '', result, string.escaped); }, unit: function (val, unit) { if(!(val instanceof tree.Dimension)) { diff --git a/test/css/functions.css b/test/css/functions.css index 277bb1bd..17509c18 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -48,10 +48,15 @@ replace: "Hello, World!"; replace-captured: "This is a new string."; replace-with-flags: "2 + 2 = 4"; + replace-single-quoted: 'foo-2'; + replace-escaped-string: bar-2; + replace-keyword: baz-2; format: "rgb(32, 128, 64)"; format-string: "hello world"; format-multiple: "hello earth 2"; format-url-encode: "red is %23ff0000"; + format-single-quoted: 'hello single world'; + format-escaped-string: hello escaped world; eformat: rgb(32, 128, 64); unitless: 12; unit: 14em; diff --git a/test/less/functions.less b/test/less/functions.less index ba9fe07c..87686bee 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -52,10 +52,15 @@ replace: replace("Hello, Mars.", "Mars\.", "World!"); replace-captured: replace("This is a string.", "(string)\.$", "new $1."); replace-with-flags: replace("One + one = 4", "one", "2", "gi"); + replace-single-quoted: replace('foo-1', "1", "2"); + replace-escaped-string: replace(~"bar-1", "1", "2"); + replace-keyword: replace(baz-1, "1", "2"); format: %("rgb(%d, %d, %d)", @r, 128, 64); format-string: %("hello %s", "world"); format-multiple: %("hello %s %d", "earth", 2); - format-url-encode: %('red is %A', #ff0000); + format-url-encode: %("red is %A", #ff0000); + format-single-quoted: %('hello %s', "single world"); + format-escaped-string: %(~"hello %s", "escaped world"); eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64)); unitless: unit(12px); From 3d338ceff177926001216bed9c9a9254efbc09ff Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 20 Feb 2014 07:15:30 +0000 Subject: [PATCH 24/29] Update README.md --- build/README.md | 304 +----------------------------------------------- 1 file changed, 2 insertions(+), 302 deletions(-) diff --git a/build/README.md b/build/README.md index 2ac5171a..26655812 100644 --- a/build/README.md +++ b/build/README.md @@ -14,265 +14,16 @@ Options for adding Less.js to your project: * [Download the latest release][download] * Clone the repo: `git clone git://github.com/less/less.js.git` - - -## Feature Highlights -LESS extends CSS with dynamic features such as: - -* [nesting](#nesting) -* [variables](#variables) -* [operations](#operations) -* [mixins](#mixins) -* [extend](#extend) (selector inheritance) - -To learn about the many other features Less.js has to offer please visit [http://lesscss.org](http://lesscss.org) and [the Less.js wiki][wiki] - - -### Examples -#### nesting -Take advantage of nesting to make code more readable and maintainable. This: - -```less -.nav > li > a { - border: 1px solid #f5f5f5; - &:hover { - border-color: #ddd; - } -} -``` - -renders to: - -```css -.nav > li > a { - border: 1px solid #f5f5f5; -} -.nav > li > a:hover { - border-color: #ddd; -} -``` - - -#### variables -Updated commonly used values from a single location. - -```less -// Variables ("inline" comments like this can be used) -@link-color: #428bca; // appears as "sea blue" - -/* Or "block comments" that span - multiple lines, like this */ -a { - color: @link-color; // use the variable in styles -} -``` - -Variables can also be used in `@import` statements, URLs, selector names, and more. - - - -#### operations -Continuing with the same example above, we can use our variables even easier to maintain with _operations_, which enables the use of addition, subraction, multiplication and division in your styles: - -```less -// Variables -@link-color: #428bca; -@link-color-hover: darken(@link-color, 10%); - -// Styles -a { - color: @link-color; -} -a:hover { - color: @link-color-hover; -} -``` -renders to: - -```css -a { - color: #428bca; -} -a:hover { - color: #3071a9; -} -``` - -#### mixins -##### "implicit" mixins -Mixins enable you to apply the styles of one selector inside another selector like this: - -```less -// Variables -@link-color: #428bca; - -// Any "regular" class... -.link { - color: @link-color; -} -a { - font-weight: bold; - .link; // ...can be used as an "implicit" mixin -} -``` - -renders to: - -```css -.link { - color: #428bca; -} -a { - font-weight: bold; - color: #428bca; -} -``` - -So any selector can be an "implicit mixin". We'll show you a DRYer way to do this below. - - - -##### parametric mixins -Mixins can also accept parameters: - -```less -// Transition mixin -.transition(@transition) { - -webkit-transition: @transition; - -moz-transition: @transition; - -o-transition: @transition; - transition: @transition; -} -``` - -used like this: - -```less -// Variables -@link-color: #428bca; -@link-color-hover: darken(@link-color, 10%); - -//Transition mixin would be anywhere here - -a { - font-weight: bold; - color: @link-color; - .transition(color .2s ease-in-out); - // Hover state - &:hover { - color: @link-color-hover; - } -} -``` - -renders to: - -```css -a { - font-weight: bold; - color: #428bca; - -webkit-transition: color 0.2s ease-in-out; - -moz-transition: color 0.2s ease-in-out; - -o-transition: color 0.2s ease-in-out; - transition: color 0.2s ease-in-out; -} -a:hover { - color: #3071a9; -} -``` - - -#### extend -The `extend` feature can be thought of as the _inverse_ of mixins. It accomplishes the goal of "borrowing styles", but rather than copying all the rules of _Selector A_ over to _Selector B_, `extend` copies the name of the _inheriting selector_ (_Selector B_) over to the _extending selector_ (_Selector A_). So continuing with the example used for [mixins](#mixins) above, extend works like this: - -```less -// Variables -@link-color: #428bca; - -.link { - color: @link-color; -} -a:extend(.link) { - font-weight: bold; -} -// Can also be written as -a { - &:extend(.link); - font-weight: bold; -} -``` - -renders to: - -```css -.link, a { - color: #428bca; -} -a { - font-weight: bold; -} -``` - -## Usage - -### Compiling and Parsing -Invoke the compiler from node: - -```javascript -var less = require('less'); - -less.render('.class { width: (1 + 1) }', function (e, css) { - console.log(css); -}); -``` - -Outputs: - -```css -.class { - width: 2; -} -``` - -You may also manually invoke the parser and compiler: - -```javascript -var parser = new(less.Parser); - -parser.parse('.class { width: (1 + 1) }', function (err, tree) { - if (err) { return console.error(err) } - console.log(tree.toCSS()); -}); -``` - - -### Configuration -You may also pass options to the compiler: - -```javascript -var parser = new(less.Parser)({ - paths: ['.', './src/less'], // Specify search paths for @import directives - filename: 'style.less' // Specify a filename, for better error messages -}); - -parser.parse('.class { width: (1 + 1) }', function (e, tree) { - tree.toCSS({ compress: true }); // Minify CSS output -}); -``` - ## More information -For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org) or [the less wiki][wiki]. +For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org). Here are other resources for using Less.js: * [stackoverflow.com][so] is a great place to get answers about Less. -* [node.js tools](https://github.com/less/less.js/wiki/Converting-LESS-to-CSS) for converting Less to CSS -* [GUI compilers for Less](https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js) * [Less.js Issues][issues] for reporting bugs - ## Contributing Please read [CONTRIBUTING.md](./CONTRIBUTING.md). Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). @@ -284,57 +35,7 @@ Please report documentation issues in [the documentation project](https://github ### Development -#### Install Less.js - -Start by either [downloading this project][download] manually, or in the command line: - -```shell -git clone https://github.com/less/less.js.git "less" -``` -and then `cd less`. - - -#### Install dependencies - -To install all the dependencies for less development, run: - -```shell -npm install -``` - -If you haven't run grunt before, install grunt-cli globally so you can just run `grunt` - -```shell -npm install grunt-cli -g -``` - -You should now be able to build Less.js, run tests, benchmarking, and other tasks listed in the Gruntfile. - -## Using Less.js Grunt - -Tests, benchmarking and building is done using Grunt `<%= pkg.devDependencies.grunt %>`. If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to install and use Grunt plugins, which are necessary for development with Less.js. - -The Less.js [Gruntfile](Gruntfile.js) is configured with the following "convenience tasks" : - -#### test - `grunt` -Runs jshint, nodeunit and headless jasmine tests using [phantomjs](http://code.google.com/p/phantomjs/). You must have phantomjs installed for the jasmine tests to run. - -#### test - `grunt benchmark` -Runs the benchmark suite. - -#### build for testing browser - 'grunt browser' -This builds less.js and puts it in 'test/browser/less.js' - -#### build - `grunt stable | grunt beta | grunt alpha` -Builds Less.js from from the `/lib/less` source files. This is done by the developer releasing a new release, do not do this if you are creating a pull request. - -#### readme - `grunt readme` -Build the README file from [a template](build/README.md) to ensure that metadata is up-to-date and (more likely to be) correct. - -Please review the [Gruntfile](Gruntfile.js) to become acquainted with the other available tasks. - -**Please note** that if you have any issues installing dependencies or running any of the Gruntfile commands, please make sure to uninstall any previous versions, both in the local node_modules directory, and clear your global npm cache, and then try running `npm install` again. After that if you still have issues, please let us know about it so we can help. - +Read [Developing Less](http://lesscss.org/usage/#developing-less). ## Release History See the [changelog](CHANGELOG.md) @@ -347,5 +48,4 @@ Licensed under the [Apache License](LICENSE). [so]: http://stackoverflow.com/questions/tagged/twitter-bootstrap+less "StackOverflow.com" [issues]: https://github.com/less/less.js/issues "GitHub Issues for Less.js" -[wiki]: https://github.com/less/less.js/wiki "The official wiki for Less.js" [download]: https://github.com/less/less.js/zipball/master "Download Less.js" From 6d5473e9884c91eee6eae43a7aa8d49460c6d5ab Mon Sep 17 00:00:00 2001 From: Luke Page Date: Thu, 20 Feb 2014 07:16:16 +0000 Subject: [PATCH 25/29] update readme --- README.md | 304 +----------------------------------------------------- 1 file changed, 2 insertions(+), 302 deletions(-) diff --git a/README.md b/README.md index c7d3ec68..0697a246 100644 --- a/README.md +++ b/README.md @@ -14,265 +14,16 @@ Options for adding Less.js to your project: * [Download the latest release][download] * Clone the repo: `git clone git://github.com/less/less.js.git` - - -## Feature Highlights -LESS extends CSS with dynamic features such as: - -* [nesting](#nesting) -* [variables](#variables) -* [operations](#operations) -* [mixins](#mixins) -* [extend](#extend) (selector inheritance) - -To learn about the many other features Less.js has to offer please visit [http://lesscss.org](http://lesscss.org) and [the Less.js wiki][wiki] - - -### Examples -#### nesting -Take advantage of nesting to make code more readable and maintainable. This: - -```less -.nav > li > a { - border: 1px solid #f5f5f5; - &:hover { - border-color: #ddd; - } -} -``` - -renders to: - -```css -.nav > li > a { - border: 1px solid #f5f5f5; -} -.nav > li > a:hover { - border-color: #ddd; -} -``` - - -#### variables -Updated commonly used values from a single location. - -```less -// Variables ("inline" comments like this can be used) -@link-color: #428bca; // appears as "sea blue" - -/* Or "block comments" that span - multiple lines, like this */ -a { - color: @link-color; // use the variable in styles -} -``` - -Variables can also be used in `@import` statements, URLs, selector names, and more. - - - -#### operations -Continuing with the same example above, we can use our variables even easier to maintain with _operations_, which enables the use of addition, subraction, multiplication and division in your styles: - -```less -// Variables -@link-color: #428bca; -@link-color-hover: darken(@link-color, 10%); - -// Styles -a { - color: @link-color; -} -a:hover { - color: @link-color-hover; -} -``` -renders to: - -```css -a { - color: #428bca; -} -a:hover { - color: #3071a9; -} -``` - -#### mixins -##### "implicit" mixins -Mixins enable you to apply the styles of one selector inside another selector like this: - -```less -// Variables -@link-color: #428bca; - -// Any "regular" class... -.link { - color: @link-color; -} -a { - font-weight: bold; - .link; // ...can be used as an "implicit" mixin -} -``` - -renders to: - -```css -.link { - color: #428bca; -} -a { - font-weight: bold; - color: #428bca; -} -``` - -So any selector can be an "implicit mixin". We'll show you a DRYer way to do this below. - - - -##### parametric mixins -Mixins can also accept parameters: - -```less -// Transition mixin -.transition(@transition) { - -webkit-transition: @transition; - -moz-transition: @transition; - -o-transition: @transition; - transition: @transition; -} -``` - -used like this: - -```less -// Variables -@link-color: #428bca; -@link-color-hover: darken(@link-color, 10%); - -//Transition mixin would be anywhere here - -a { - font-weight: bold; - color: @link-color; - .transition(color .2s ease-in-out); - // Hover state - &:hover { - color: @link-color-hover; - } -} -``` - -renders to: - -```css -a { - font-weight: bold; - color: #428bca; - -webkit-transition: color 0.2s ease-in-out; - -moz-transition: color 0.2s ease-in-out; - -o-transition: color 0.2s ease-in-out; - transition: color 0.2s ease-in-out; -} -a:hover { - color: #3071a9; -} -``` - - -#### extend -The `extend` feature can be thought of as the _inverse_ of mixins. It accomplishes the goal of "borrowing styles", but rather than copying all the rules of _Selector A_ over to _Selector B_, `extend` copies the name of the _inheriting selector_ (_Selector B_) over to the _extending selector_ (_Selector A_). So continuing with the example used for [mixins](#mixins) above, extend works like this: - -```less -// Variables -@link-color: #428bca; - -.link { - color: @link-color; -} -a:extend(.link) { - font-weight: bold; -} -// Can also be written as -a { - &:extend(.link); - font-weight: bold; -} -``` - -renders to: - -```css -.link, a { - color: #428bca; -} -a { - font-weight: bold; -} -``` - -## Usage - -### Compiling and Parsing -Invoke the compiler from node: - -```javascript -var less = require('less'); - -less.render('.class { width: (1 + 1) }', function (e, css) { - console.log(css); -}); -``` - -Outputs: - -```css -.class { - width: 2; -} -``` - -You may also manually invoke the parser and compiler: - -```javascript -var parser = new(less.Parser); - -parser.parse('.class { width: (1 + 1) }', function (err, tree) { - if (err) { return console.error(err) } - console.log(tree.toCSS()); -}); -``` - - -### Configuration -You may also pass options to the compiler: - -```javascript -var parser = new(less.Parser)({ - paths: ['.', './src/less'], // Specify search paths for @import directives - filename: 'style.less' // Specify a filename, for better error messages -}); - -parser.parse('.class { width: (1 + 1) }', function (e, tree) { - tree.toCSS({ compress: true }); // Minify CSS output -}); -``` - ## More information -For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org) or [the less wiki][wiki]. +For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org). Here are other resources for using Less.js: * [stackoverflow.com][so] is a great place to get answers about Less. -* [node.js tools](https://github.com/less/less.js/wiki/Converting-LESS-to-CSS) for converting Less to CSS -* [GUI compilers for Less](https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js) * [Less.js Issues][issues] for reporting bugs - ## Contributing Please read [CONTRIBUTING.md](./CONTRIBUTING.md). Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). @@ -284,57 +35,7 @@ Please report documentation issues in [the documentation project](https://github ### Development -#### Install Less.js - -Start by either [downloading this project][download] manually, or in the command line: - -```shell -git clone https://github.com/less/less.js.git "less" -``` -and then `cd less`. - - -#### Install dependencies - -To install all the dependencies for less development, run: - -```shell -npm install -``` - -If you haven't run grunt before, install grunt-cli globally so you can just run `grunt` - -```shell -npm install grunt-cli -g -``` - -You should now be able to build Less.js, run tests, benchmarking, and other tasks listed in the Gruntfile. - -## Using Less.js Grunt - -Tests, benchmarking and building is done using Grunt `~0.4.1`. If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to install and use Grunt plugins, which are necessary for development with Less.js. - -The Less.js [Gruntfile](Gruntfile.js) is configured with the following "convenience tasks" : - -#### test - `grunt` -Runs jshint, nodeunit and headless jasmine tests using [phantomjs](http://code.google.com/p/phantomjs/). You must have phantomjs installed for the jasmine tests to run. - -#### test - `grunt benchmark` -Runs the benchmark suite. - -#### build for testing browser - 'grunt browser' -This builds less.js and puts it in 'test/browser/less.js' - -#### build - `grunt stable | grunt beta | grunt alpha` -Builds Less.js from from the `/lib/less` source files. This is done by the developer releasing a new release, do not do this if you are creating a pull request. - -#### readme - `grunt readme` -Build the README file from [a template](build/README.md) to ensure that metadata is up-to-date and (more likely to be) correct. - -Please review the [Gruntfile](Gruntfile.js) to become acquainted with the other available tasks. - -**Please note** that if you have any issues installing dependencies or running any of the Gruntfile commands, please make sure to uninstall any previous versions, both in the local node_modules directory, and clear your global npm cache, and then try running `npm install` again. After that if you still have issues, please let us know about it so we can help. - +Read [Developing Less](http://lesscss.org/usage/#developing-less). ## Release History See the [changelog](CHANGELOG.md) @@ -347,5 +48,4 @@ Licensed under the [Apache License](LICENSE). [so]: http://stackoverflow.com/questions/tagged/twitter-bootstrap+less "StackOverflow.com" [issues]: https://github.com/less/less.js/issues "GitHub Issues for Less.js" -[wiki]: https://github.com/less/less.js/wiki "The official wiki for Less.js" [download]: https://github.com/less/less.js/zipball/master "Download Less.js" From bf9c59025bab7401299e8feb3bb26b3b039499c0 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 21 Feb 2014 11:03:38 +0000 Subject: [PATCH 26/29] Fixed that in some situations a mixin call into a referenced file wouldn't import media queries. Fixes #1469 --- lib/less/tree/media.js | 1 + test/css/import-reference.css | 27 ++++++++++++++++++++++---- test/less/import-reference.less | 11 +++++++---- test/less/import/import-reference.less | 8 ++++++++ 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index 4531cf4c..095e43e8 100644 --- a/lib/less/tree/media.js +++ b/lib/less/tree/media.js @@ -74,6 +74,7 @@ tree.Media.prototype = { }, markReferenced: function () { var i, rules = this.rules[0].rules; + this.rules[0].markReferenced(); this.isReferenced = true; for (i = 0; i < rules.length; i++) { if (rules[i].markReferenced) { diff --git a/test/css/import-reference.css b/test/css/import-reference.css index d61df816..f25f4b1d 100644 --- a/test/css/import-reference.css +++ b/test/css/import-reference.css @@ -1,7 +1,17 @@ -/* - The media statement above is invalid (no selector) - We should ban invalid media queries with properties and no selector? -*/ +input[type="text"].class#id[attr=32]:not(1) { + color: white; +} +div#id.class[a=1][b=2].class:not(1) { + color: white; +} +@media print { + .class { + color: blue; + } + .class .sub { + width: 42; + } +} .visible { color: red; } @@ -47,3 +57,12 @@ .visible { extend: test; } +.test-mediaq-import { + color: green; + test: 340px; +} +@media (max-size: 450px) { + .test-mediaq-import { + color: red; + } +} diff --git a/test/less/import-reference.less b/test/less/import-reference.less index cf9da168..93160ab5 100644 --- a/test/less/import-reference.less +++ b/test/less/import-reference.less @@ -1,10 +1,6 @@ @import (reference) url("import-once.less"); @import (reference) url("css-3.less"); @import (reference) url("media.less"); -/* - The media statement above is invalid (no selector) - We should ban invalid media queries with properties and no selector? -*/ @import (reference) url("import/import-reference.less"); .b { @@ -15,4 +11,11 @@ .visible:extend(.z all) { extend: test; +} + +.test-mediaq-import { + .mixin-with-mediaq(340px); +} + +.class:extend(.class all) { } \ No newline at end of file diff --git a/test/less/import/import-reference.less b/test/less/import/import-reference.less index 9eac45fc..c77f692e 100644 --- a/test/less/import/import-reference.less +++ b/test/less/import/import-reference.less @@ -40,4 +40,12 @@ pulled-in: yes; } /* comment pulled in */ +} +@max-size: 450px; +.mixin-with-mediaq(@num) { + color: green; + test: @num; + @media (max-size: @max-size) { + color: red; + } } \ No newline at end of file From 7c90acaae62fbe1d26c4778974703d857bc6b213 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 21 Feb 2014 11:22:42 +0000 Subject: [PATCH 27/29] Fix a small scope issue with mixins when using parent selectors, introduced in 1.6.2. Fixes #1877 --- lib/less/tree/ruleset.js | 33 +++++++++++-------- test/css/scope.css | 3 ++ .../errors/mixin-not-visible-in-scope-1.less | 9 +++++ .../errors/mixin-not-visible-in-scope-1.txt | 4 +++ test/less/scope.less | 25 ++++++++++++++ 5 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 test/less/errors/mixin-not-visible-in-scope-1.less create mode 100644 test/less/errors/mixin-not-visible-in-scope-1.txt diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 7571b0e7..d657976d 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -111,20 +111,25 @@ tree.Ruleset.prototype = { rule = rsRules[i]; if (! (rule instanceof tree.mixin.Definition || rule instanceof tree.DetachedRuleset)) { rsRules[i] = rule = rule.eval ? rule.eval(env) : rule; - // for rulesets, check if it is a css guard and can be removed - if (rule instanceof tree.Ruleset && rule.selectors && rule.selectors.length === 1) { - // check if it can be folded in (e.g. & where) - if (rule.selectors[0].isJustParentSelector()) { - rsRules.splice(i--, 1); - // cannot call if there is no selector, so we can just continue - if (!rule.selectors[0].evaldCondition) { - continue; - } - for(var j = 0; j < rule.rules.length; j++) { - subRule = rule.rules[j]; - if (!(subRule instanceof tree.Rule) || !subRule.variable) { - rsRules.splice(++i, 0, subRule); - } + } + } + + // Evaluate everything else + for (i = 0; i < rsRules.length; i++) { + rule = rsRules[i]; + // for rulesets, check if it is a css guard and can be removed + if (rule instanceof tree.Ruleset && rule.selectors && rule.selectors.length === 1) { + // check if it can be folded in (e.g. & where) + if (rule.selectors[0].isJustParentSelector()) { + rsRules.splice(i--, 1); + // cannot call if there is no selector, so we can just continue + if (!rule.selectors[0].evaldCondition) { + continue; + } + for(var j = 0; j < rule.rules.length; j++) { + subRule = rule.rules[j]; + if (!(subRule instanceof tree.Rule) || !subRule.variable) { + rsRules.splice(++i, 0, subRule); } } } diff --git a/test/css/scope.css b/test/css/scope.css index baa05523..0e4c17d5 100644 --- a/test/css/scope.css +++ b/test/css/scope.css @@ -33,3 +33,6 @@ scope: 'top level'; sub-scope-only: 'inside'; } +#parentSelectorScope { + prop: #ffffff; +} diff --git a/test/less/errors/mixin-not-visible-in-scope-1.less b/test/less/errors/mixin-not-visible-in-scope-1.less new file mode 100644 index 00000000..2842613e --- /dev/null +++ b/test/less/errors/mixin-not-visible-in-scope-1.less @@ -0,0 +1,9 @@ +.something { + & { + .a {value: a} + } + + & { + .b {.a} // was Err. before 1.6.2 + } +} \ No newline at end of file diff --git a/test/less/errors/mixin-not-visible-in-scope-1.txt b/test/less/errors/mixin-not-visible-in-scope-1.txt new file mode 100644 index 00000000..15e64dc2 --- /dev/null +++ b/test/less/errors/mixin-not-visible-in-scope-1.txt @@ -0,0 +1,4 @@ +NameError: .a is undefined in {path}mixin-not-visible-in-scope-1.less on line 7, column 13: +6 & { +7 .b {.a} // was Err. before 1.6.2 +8 } diff --git a/test/less/scope.less b/test/less/scope.less index 36d37061..475b1f6d 100644 --- a/test/less/scope.less +++ b/test/less/scope.less @@ -76,4 +76,29 @@ @subScopeOnly: 'inside'; //use the mixin .mixinNoParam(); +} +#parentSelectorScope { + @col: white; + & { + @col: black; + } + prop: @col; + & { + @col: black; + } +} +.test-empty-mixin() { +} +#parentSelectorScopeMixins { + & { + .test-empty-mixin() { + should: never seee 1; + } + } + .test-empty-mixin(); + & { + .test-empty-mixin() { + should: never seee 2; + } + } } \ No newline at end of file From ca06fee982cb32b0f3253e3f9eb611f622e34ab5 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 21 Feb 2014 11:32:28 +0000 Subject: [PATCH 28/29] when in lint mode, report errors from creating css. Fixes #1786 --- bin/lessc | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/bin/lessc b/bin/lessc index b36c062a..7530b307 100755 --- a/bin/lessc +++ b/bin/lessc @@ -352,8 +352,9 @@ var parseLessFile = function (e, data) { sys.print(file + " ") } sys.print("\n"); - } else if(!options.lint) { + } else { try { + if (options.lint) { writeSourceMap = function() {} } var css = tree.toCSS({ silent: options.silent, verbose: options.verbose, @@ -374,15 +375,17 @@ var parseLessFile = function (e, data) { strictUnits: options.strictUnits, urlArgs: options.urlArgs }); - if (output) { - ensureDirectory(output); - fs.writeFileSync(output, css, 'utf8'); - if (options.verbose) { - console.log('lessc: wrote ' + output); - } - } else { - sys.print(css); - } + if(!options.lint) { + if (output) { + ensureDirectory(output); + fs.writeFileSync(output, css, 'utf8'); + if (options.verbose) { + console.log('lessc: wrote ' + output); + } + } else { + sys.print(css); + } + } } catch (e) { less.writeError(e, options); currentErrorcode = 2; From 5ab0e086657d926a1bec4c5fb6af6740de178b7d Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 21 Feb 2014 12:37:40 +0000 Subject: [PATCH 29/29] Do not evaluate css with guards if the guards fail. Fixes #1873 --- lib/less/tree/ruleset.js | 20 ++++++++++++++------ test/less/css-guards.less | 3 +++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index d657976d..19023004 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -20,7 +20,8 @@ tree.Ruleset.prototype = { }, eval: function (env) { var thisSelectors = this.selectors, selectors, - selCnt, i, defaultFunc = tree.defaultFunc; + selCnt, selector, i, defaultFunc = tree.defaultFunc, hasOnePassingSelector = false; + if (thisSelectors && (selCnt = thisSelectors.length)) { selectors = []; defaultFunc.error({ @@ -28,9 +29,15 @@ tree.Ruleset.prototype = { message: "it is currently only allowed in parametric mixin guards," }); for (i = 0; i < selCnt; i++) { - selectors.push(thisSelectors[i].eval(env)); + selector = thisSelectors[i].eval(env); + selectors.push(selector); + if (selector.evaldCondition) { + hasOnePassingSelector = true; + } } defaultFunc.reset(); + } else { + hasOnePassingSelector = true; } var rules = this.rules ? this.rules.slice(0) : null, @@ -45,6 +52,10 @@ tree.Ruleset.prototype = { if(this.debugInfo) { ruleset.debugInfo = this.debugInfo; } + + if (!hasOnePassingSelector) { + rules.length = 0; + } // push the current ruleset to the frames stack var envFrames = env.frames; @@ -122,10 +133,7 @@ tree.Ruleset.prototype = { // check if it can be folded in (e.g. & where) if (rule.selectors[0].isJustParentSelector()) { rsRules.splice(i--, 1); - // cannot call if there is no selector, so we can just continue - if (!rule.selectors[0].evaldCondition) { - continue; - } + for(var j = 0; j < rule.rules.length; j++) { subRule = rule.rules[j]; if (!(subRule instanceof tree.Rule) || !subRule.variable) { diff --git a/test/less/css-guards.less b/test/less/css-guards.less index 6dd1cd68..85ec8d29 100644 --- a/test/less/css-guards.less +++ b/test/less/css-guards.less @@ -97,3 +97,6 @@ .scope-check(); @k:4px; } +.errors-if-called when (@c = never) { + .mixin-doesnt-exist(); +} \ No newline at end of file