diff --git a/build/build.yml b/build/build.yml index dd4236b99..b5a2234a8 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 @@ -143,6 +144,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 7aee17c89..a18b81790 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'); @@ -120,6 +121,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 b88270fca..032381dc2 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -43,8 +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 + saveStack = [], // holds state for backtracking furthest, // furthest index the parser has gone to chunks, // chunkified input current, // current chunk @@ -111,8 +110,9 @@ less.Parser = function Parser(env) { } }; - function save() { temp = current; memo = currentPos = i; } - function restore() { current = temp; currentPos = i = memo; } + 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) { @@ -424,7 +424,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; } @@ -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 { @@ -737,6 +737,9 @@ less.Parser = function Parser(env) { break; } } + if (peekChar('}')) { + break; + } } return root; @@ -1027,6 +1030,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 // @@ -1112,6 +1128,7 @@ less.Parser = function Parser(env) { } if (parsers.end()) { + forget(); return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important); } } @@ -1124,9 +1141,11 @@ less.Parser = function Parser(env) { expressions = [], argsSemiColon = [], argsComma = [], isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg; + save(); + while (true) { if (isCall) { - arg = parsers.expression(); + arg = parsers.detachedRuleset() || parsers.expression(); } else { parsers.comments(); if (input.charAt(i) === '.' && $re(/^\.{3}/)) { @@ -1154,7 +1173,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 { @@ -1169,7 +1188,21 @@ 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 + // 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.detachedRuleset()) || parsers.expression(); + + if (!value) { + if (isCall) { + error("could not understand value for named argument"); + } else { + restore(); + returner.args = []; + return returner; + } + } nameLoop = (name = val.name); } else if (!isCall && $re(/^\.{3}/)) { returner.variadic = true; @@ -1214,6 +1247,7 @@ less.Parser = function Parser(env) { } } + forget(); returner.args = isSemiColonSeperated ? argsSemiColon : argsComma; return returner; }, @@ -1254,10 +1288,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(); @@ -1269,10 +1307,13 @@ less.Parser = function Parser(env) { ruleset = parsers.block(); if (ruleset) { + forget(); return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic); } else { restore(); } + } else { + forget(); } } }, @@ -1336,10 +1377,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(); } } @@ -1442,6 +1489,22 @@ 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 {...} // @@ -1472,6 +1535,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; @@ -1484,28 +1548,38 @@ less.Parser = function Parser(env) { } }, rule: function (tryAnonymous) { - var name, value, c = input.charAt(i), important, merge; - save(); + var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable; if (c === '.' || c === '#' || c === '&') { return; } + save(); + 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.detachedRuleset(); + } + + 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); + forget(); + return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo); } else { furthest = i; restore(); @@ -1513,6 +1587,8 @@ less.Parser = function Parser(env) { return this.rule(true); } } + } else { + forget(); } }, anonymousValue: function () { @@ -1546,6 +1622,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); } @@ -1742,13 +1819,11 @@ 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(';'))) { + forget(); return new(tree.Directive)(name, value, rules, index, env.currentFileInfo, env.dumpLineNumbers ? getDebugInfo(index, input, env) : null); } diff --git a/lib/less/tree/detached-ruleset.js b/lib/less/tree/detached-ruleset.js new file mode 100644 index 000000000..0a5512aae --- /dev/null +++ b/lib/less/tree/detached-ruleset.js @@ -0,0 +1,20 @@ +(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) { + var frames = this.frames || env.frames.slice(0); + return new tree.DetachedRuleset(this.ruleset, frames); + }, + callEval: function (env) { + return this.ruleset.eval(this.frames ? new(tree.evalEnv)(env, this.frames.concat(env.frames)) : env); + } +}; +})(require('../tree')); diff --git a/lib/less/tree/media.js b/lib/less/tree/media.js index c3b85107e..4531cf4cd 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/mixin.js b/lib/less/tree/mixin.js index 1d903e871..506561bac 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/rule.js b/lib/less/tree/rule.js index a2d14f2b6..4e1ccde69 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; @@ -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,24 @@ tree.Rule.prototype = { env.strictMath = true; } try { + evaldValue = this.value.eval(env); + + if (!this.variable && evaldValue.type === "DetachedRuleset") { + 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/lib/less/tree/ruleset-call.js b/lib/less/tree/ruleset-call.js new file mode 100644 index 000000000..a543c55e5 --- /dev/null +++ b/lib/less/tree/ruleset-call.js @@ -0,0 +1,16 @@ +(function (tree) { + +tree.RulesetCall = function (variable) { + this.variable = variable; +}; +tree.RulesetCall.prototype = { + type: "RulesetCall", + accept: function (visitor) { + }, + eval: function (env) { + var detachedRuleset = new(tree.Variable)(this.variable).eval(env); + return detachedRuleset.callEval(env); + } +}; + +})(require('../tree')); diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index e7895760d..7571b0e73 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); } } @@ -90,13 +90,26 @@ 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(); } } // 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) { @@ -345,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 new file mode 100644 index 000000000..300c08d09 --- /dev/null +++ b/test/css/detached-rulesets.css @@ -0,0 +1,71 @@ +.wrap-selector { + color: black; + one: 1px; + four: magic-frame; + 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; +} +@media screen and (min-width: 1200) { + header { + background: red; + } +} +html.lt-ie9 header { + background: red; +} +.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; +} +.without-mixins { + b: 1; +} +@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 new file mode 100644 index 000000000..6a98d890a --- /dev/null +++ b/test/less/detached-rulesets.less @@ -0,0 +1,103 @@ +@ruleset: { + color: black; + background: white; + }; + +@a: 1px; +.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(); + 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; + four: @d; +}); + +.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; + }); +} + +.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); + test-arithmetic: unit((9+9), px); +}); +// without mixins +@ruleset-2: { + b: 1; +}; +.without-mixins { + @ruleset-2(); +} +@my-ruleset: { + .my-selector { + @media tv { + background-color: black; + } + } + }; +@media (orientation:portrait) { + @my-ruleset(); + .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 diff --git a/test/less/errors/detached-ruleset-1.less b/test/less/errors/detached-ruleset-1.less new file mode 100644 index 000000000..ac5b8db03 --- /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 000000000..7407741c8 --- /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 000000000..51a7af6be --- /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 000000000..f18e09353 --- /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 000000000..c50119d99 --- /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 000000000..15d281fa3 --- /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 000000000..14ac314bb --- /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 000000000..d6d6526d4 --- /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 000000000..174ebf354 --- /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 000000000..561897950 --- /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 000000000..121099f75 --- /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 000000000..07840445e --- /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;