Skip to content

Commit 2f07cd7

Browse files
committed
Merge pull request #2785 from less/root-functions
Allows root (non-value) functions in Less
2 parents d094d7e + 87d59fa commit 2f07cd7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+265
-25
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ node_js:
44
- "4.0.0"
55
- "0.12"
66
- "0.10"
7+
before_install:
8+
- mkdir travis-phantomjs
9+
- wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2
10+
- tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs
11+
- export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH
712
install:
813
- npm install -g grunt-cli
914
- npm install

lib/less/parser/parser.js

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ var Parser = function Parser(context, imports, fileInfo) {
261261
}
262262

263263
node = mixin.definition() || this.rule() || this.ruleset() ||
264-
mixin.call() || this.rulesetCall() || this.directive();
264+
mixin.call() || this.rulesetCall() || this.entities.call() || this.directive();
265265
if (node) {
266266
root.push(node);
267267
} else {
@@ -370,19 +370,51 @@ var Parser = function Parser(context, imports, fileInfo) {
370370
return new(tree.Call)(name, args, index, fileInfo);
371371
},
372372
arguments: function () {
373-
var args = [], arg;
373+
var argsSemiColon = [], argsComma = [],
374+
expressions = [],
375+
isSemiColonSeparated, value, arg;
376+
377+
parserInput.save();
374378

375379
while (true) {
376-
arg = this.assignment() || parsers.expression();
380+
381+
arg = parsers.detachedRuleset() || this.assignment() || parsers.expression();
382+
377383
if (!arg) {
378384
break;
379385
}
380-
args.push(arg);
381-
if (! parserInput.$char(',')) {
382-
break;
386+
387+
value = arg;
388+
389+
if (arg.value && arg.value.length == 1) {
390+
value = arg.value[0];
391+
}
392+
393+
if (value) {
394+
expressions.push(value);
395+
}
396+
397+
argsComma.push(value);
398+
399+
if (parserInput.$char(',')) {
400+
continue;
401+
}
402+
403+
if (parserInput.$char(';') || isSemiColonSeparated) {
404+
405+
isSemiColonSeparated = true;
406+
407+
if (expressions.length > 1) {
408+
value = new(tree.Value)(expressions);
409+
}
410+
argsSemiColon.push(value);
411+
412+
expressions = [];
383413
}
384414
}
385-
return args;
415+
416+
parserInput.forget();
417+
return isSemiColonSeparated ? argsSemiColon : argsComma;
386418
},
387419
literal: function () {
388420
return this.dimension() ||

lib/less/tree/anonymous.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike,
66
this.mapLines = mapLines;
77
this.currentFileInfo = currentFileInfo;
88
this.rulesetLike = (typeof rulesetLike === 'undefined') ? false : rulesetLike;
9-
9+
this.allowRoot = true;
1010
this.copyVisibilityInfo(visibilityInfo);
1111
};
1212
Anonymous.prototype = new Node();

lib/less/tree/call.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,21 @@ Call.prototype.eval = function (context) {
3131
var args = this.args.map(function (a) { return a.eval(context); }),
3232
result, funcCaller = new FunctionCaller(this.name, context, this.index, this.currentFileInfo);
3333

34-
if (funcCaller.isValid()) { // 1.
34+
if (funcCaller.isValid()) {
3535
try {
3636
result = funcCaller.call(args);
37-
if (result != null) {
38-
return result;
39-
}
4037
} catch (e) {
4138
throw { type: e.type || "Runtime",
4239
message: "error evaluating function `" + this.name + "`" +
4340
(e.message ? ': ' + e.message : ''),
4441
index: this.index, filename: this.currentFileInfo.filename };
4542
}
43+
44+
if (result != null) {
45+
result.index = this.index;
46+
result.currentFileInfo = this.currentFileInfo;
47+
return result;
48+
}
4649
}
4750

4851
return new Call(this.name, args, this.index, this.currentFileInfo);

lib/less/tree/comment.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var Comment = function (value, isLineComment, index, currentFileInfo) {
55
this.value = value;
66
this.isLineComment = isLineComment;
77
this.currentFileInfo = currentFileInfo;
8+
this.allowRoot = true;
89
};
910
Comment.prototype = new Node();
1011
Comment.prototype.type = "Comment";

lib/less/tree/directive.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var Directive = function (name, value, rules, index, currentFileInfo, debugInfo,
2323
this.debugInfo = debugInfo;
2424
this.isRooted = isRooted || false;
2525
this.copyVisibilityInfo(visibilityInfo);
26+
this.allowRoot = true;
2627
};
2728

2829
Directive.prototype = new Node();

lib/less/tree/extend.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var Extend = function Extend(selector, option, index, currentFileInfo, visibilit
99
this.parent_ids = [this.object_id];
1010
this.currentFileInfo = currentFileInfo || {};
1111
this.copyVisibilityInfo(visibilityInfo);
12+
this.allowRoot = true;
1213

1314
switch(option) {
1415
case "all":

lib/less/tree/import.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var Import = function (path, features, options, index, currentFileInfo, visibili
2323
this.path = path;
2424
this.features = features;
2525
this.currentFileInfo = currentFileInfo;
26+
this.allowRoot = true;
2627

2728
if (this.options.less !== undefined || this.options.inline) {
2829
this.css = !this.options.less || this.options.inline;

lib/less/tree/media.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var Media = function (value, features, index, currentFileInfo, visibilityInfo) {
1515
this.rules = [new Ruleset(selectors, value)];
1616
this.rules[0].allowImports = true;
1717
this.copyVisibilityInfo(visibilityInfo);
18+
this.allowRoot = true;
1819
};
1920
Media.prototype = new Directive();
2021
Media.prototype.type = "Media";

lib/less/tree/mixin-call.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var MixinCall = function (elements, args, index, currentFileInfo, important) {
99
this.index = index;
1010
this.currentFileInfo = currentFileInfo;
1111
this.important = important;
12+
this.allowRoot = true;
1213
};
1314
MixinCall.prototype = new Node();
1415
MixinCall.prototype.type = "MixinCall";

lib/less/tree/mixin-definition.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var Definition = function (name, params, rules, condition, variadic, frames, vis
2727
this.optionalParameters = optionalParameters;
2828
this.frames = frames;
2929
this.copyVisibilityInfo(visibilityInfo);
30+
this.allowRoot = true;
3031
};
3132
Definition.prototype = new Ruleset();
3233
Definition.prototype.type = "MixinDefinition";

lib/less/tree/rule.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var Rule = function (name, value, important, merge, index, currentFileInfo, inli
1212
this.inline = inline || false;
1313
this.variable = (variable !== undefined) ? variable
1414
: (name.charAt && (name.charAt(0) === '@'));
15+
this.allowRoot = true;
1516
};
1617

1718
function evalName(context, name) {

lib/less/tree/ruleset-call.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var Node = require("./node"),
33

44
var RulesetCall = function (variable) {
55
this.variable = variable;
6+
this.allowRoot = true;
67
};
78
RulesetCall.prototype = new Node();
89
RulesetCall.prototype.type = "RulesetCall";

lib/less/tree/ruleset.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var Ruleset = function (selectors, rules, strictImports, visibilityInfo) {
1414
this._lookups = {};
1515
this.strictImports = strictImports;
1616
this.copyVisibilityInfo(visibilityInfo);
17+
this.allowRoot = true;
1718
};
1819
Ruleset.prototype = new Node();
1920
Ruleset.prototype.type = "Ruleset";

lib/less/visitors/to-css-visitor.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -199,23 +199,34 @@ ToCSSVisitor.prototype = {
199199
return directiveNode;
200200
},
201201

202-
checkPropertiesInRoot: function(rules) {
203-
var ruleNode;
202+
checkValidNodes: function(rules, isRoot) {
203+
if (!rules) {
204+
return;
205+
}
206+
204207
for (var i = 0; i < rules.length; i++) {
205-
ruleNode = rules[i];
206-
if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
207-
throw { message: "properties must be inside selector blocks, they cannot be in the root.",
208-
index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null};
208+
var ruleNode = rules[i];
209+
if (isRoot && ruleNode instanceof tree.Rule && !ruleNode.variable) {
210+
throw { message: "Properties must be inside selector blocks. They cannot be in the root",
211+
index: ruleNode.index, filename: ruleNode.currentFileInfo && ruleNode.currentFileInfo.filename};
212+
}
213+
if (ruleNode instanceof tree.Call) {
214+
throw { message: "Function '" + ruleNode.name + "' is undefined",
215+
index: ruleNode.index, filename: ruleNode.currentFileInfo && ruleNode.currentFileInfo.filename};
216+
}
217+
if (!ruleNode.allowRoot) {
218+
throw { message: ruleNode.type + " node returned by a function is not valid here",
219+
index: ruleNode.index, filename: ruleNode.currentFileInfo && ruleNode.currentFileInfo.filename};
209220
}
210221
}
211222
},
212223

213224
visitRuleset: function (rulesetNode, visitArgs) {
214225
//at this point rulesets are nested into each other
215226
var rule, rulesets = [];
216-
if (rulesetNode.firstRoot) {
217-
this.checkPropertiesInRoot(rulesetNode.rules);
218-
}
227+
228+
this.checkValidNodes(rulesetNode.rules, rulesetNode.firstRoot);
229+
219230
if (! rulesetNode.root) {
220231
//remove invisible paths
221232
this._compileRulesetPaths(rulesetNode);

test/css/plugin.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@charset "utf-8";
12
.other {
23
trans: transitive;
34
}
@@ -42,3 +43,7 @@
4243
result: local;
4344
}
4445
}
46+
.root {
47+
prop: value;
48+
}
49+
@arbitrary value after ();
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
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+
SyntaxError: Properties must be inside selector blocks. They cannot be in the root in {path}detached-ruleset-3.less on line 2, column 3:
22
1 @a: {
33
2 b: 1;
44
3 };

test/less/errors/functions-1.less

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-undefined();

test/less/errors/functions-1.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Function 'test-undefined' is undefined in {path}functions-1.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-undefined();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-keyword();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Keyword node returned by a function is not valid here in {path}functions-10-keyword.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-keyword();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-operation();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Operation node returned by a function is not valid here in {path}functions-11-operation.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-operation();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-quoted();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Quoted node returned by a function is not valid here in {path}functions-12-quoted.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-quoted();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-selector();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Selector node returned by a function is not valid here in {path}functions-13-selector.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-selector();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-url();

test/less/errors/functions-14-url.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Url node returned by a function is not valid here in {path}functions-14-url.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-url();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-value();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Value node returned by a function is not valid here in {path}functions-15-value.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-value();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-alpha();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Alpha node returned by a function is not valid here in {path}functions-2-alpha.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-alpha();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-assignment();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Assignment node returned by a function is not valid here in {path}functions-3-assignment.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-assignment();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-call();

test/less/errors/functions-4-call.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Function 'foo' is undefined in {path}functions-4-call.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-call();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rgba(0,0,0,0);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SyntaxError: Color node returned by a function is not valid here in {path}functions-5-color-2.less on line 1, column 1:
2+
1 rgba(0,0,0,0);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-color();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Color node returned by a function is not valid here in {path}functions-5-color.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-color();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-condition();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Condition node returned by a function is not valid here in {path}functions-6-condition.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-condition();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-dimension();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Dimension node returned by a function is not valid here in {path}functions-7-dimension.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-dimension();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-element();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Element node returned by a function is not valid here in {path}functions-8-element.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-element();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@plugin "../plugin/plugin-tree-nodes";
2+
test-expression();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SyntaxError: Expression node returned by a function is not valid here in {path}functions-9-expression.less on line 2, column 1:
2+
1 @plugin "../plugin/plugin-tree-nodes";
3+
2 test-expression();

test/less/errors/property-in-root.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root.less on line 2, column 3:
1+
SyntaxError: Properties must be inside selector blocks. They cannot be in the root in {path}property-in-root.less on line 2, column 3:
22
1 .a() {
33
2 prop:1;
44
3 }
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root.less on line 2, column 3:
1+
SyntaxError: Properties must be inside selector blocks. They cannot be in the root in {path}property-in-root.less on line 2, column 3:
22
1 .a() {
33
2 prop:1;
44
3 }
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root3.less on line 1, column 1:
1+
SyntaxError: Properties must be inside selector blocks. They cannot be in the root in {path}property-in-root3.less on line 1, column 1:
22
1 prop:1;
33
2 .a {

0 commit comments

Comments
 (0)