mirror of
https://github.com/less/less.js.git
synced 2026-01-22 13:48:03 -05:00
Merge branch 'philschatz_patch-1' into expand-operator
This commit is contained in:
1981
lib/less/parser.js
Normal file
1981
lib/less/parser.js
Normal file
File diff suppressed because it is too large
Load Diff
311
lib/less/tree/mixin.js
Normal file
311
lib/less/tree/mixin.js
Normal file
@@ -0,0 +1,311 @@
|
||||
(function (tree) {
|
||||
|
||||
tree.mixin = {};
|
||||
tree.mixin.Call = function (elements, args, index, currentFileInfo, important) {
|
||||
this.selector = new(tree.Selector)(elements);
|
||||
this.arguments = (args && args.length) ? args : null;
|
||||
this.index = index;
|
||||
this.currentFileInfo = currentFileInfo;
|
||||
this.important = important;
|
||||
};
|
||||
tree.mixin.Call.prototype = {
|
||||
type: "MixinCall",
|
||||
accept: function (visitor) {
|
||||
if (this.selector) {
|
||||
this.selector = visitor.visit(this.selector);
|
||||
}
|
||||
if (this.arguments) {
|
||||
this.arguments = visitor.visitArray(this.arguments);
|
||||
}
|
||||
},
|
||||
eval: function (env) {
|
||||
var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule,
|
||||
candidates = [], candidate, conditionResult = [], defaultFunc = tree.defaultFunc,
|
||||
defaultResult, defNone = 0, defTrue = 1, defFalse = 2, count;
|
||||
|
||||
args = this.arguments && this.arguments.map(function (a) {
|
||||
return { name: a.name, value: a.value.eval(env) };
|
||||
});
|
||||
|
||||
for (i = 0; i < env.frames.length; i++) {
|
||||
if ((mixins = env.frames[i].find(this.selector)).length > 0) {
|
||||
isOneFound = true;
|
||||
|
||||
// To make `default()` function independent of definition order we have two "subpasses" here.
|
||||
// At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
|
||||
// and build candidate list with corresponding flags. Then, when we know all possible matches,
|
||||
// we make a final decision.
|
||||
|
||||
for (m = 0; m < mixins.length; m++) {
|
||||
mixin = mixins[m];
|
||||
isRecursive = false;
|
||||
for(f = 0; f < env.frames.length; f++) {
|
||||
if ((!(mixin instanceof tree.mixin.Definition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) {
|
||||
isRecursive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isRecursive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mixin.matchArgs(args, env)) {
|
||||
candidate = {mixin: mixin, group: defNone};
|
||||
|
||||
if (mixin.matchCondition) {
|
||||
for (f = 0; f < 2; f++) {
|
||||
defaultFunc.value(f);
|
||||
conditionResult[f] = mixin.matchCondition(args, env);
|
||||
}
|
||||
if (conditionResult[0] || conditionResult[1]) {
|
||||
if (conditionResult[0] != conditionResult[1]) {
|
||||
candidate.group = conditionResult[1] ?
|
||||
defTrue : defFalse;
|
||||
}
|
||||
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
else {
|
||||
candidates.push(candidate);
|
||||
}
|
||||
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
defaultFunc.reset();
|
||||
|
||||
count = [0, 0, 0];
|
||||
for (m = 0; m < candidates.length; m++) {
|
||||
count[candidates[m].group]++;
|
||||
}
|
||||
|
||||
if (count[defNone] > 0) {
|
||||
defaultResult = defFalse;
|
||||
} else {
|
||||
defaultResult = defTrue;
|
||||
if ((count[defTrue] + count[defFalse]) > 1) {
|
||||
throw { type: 'Runtime',
|
||||
message: 'Ambiguous use of `default()` found when matching for `'
|
||||
+ this.format(args) + '`',
|
||||
index: this.index, filename: this.currentFileInfo.filename };
|
||||
}
|
||||
}
|
||||
|
||||
for (m = 0; m < candidates.length; m++) {
|
||||
candidate = candidates[m].group;
|
||||
if ((candidate === defNone) || (candidate === defaultResult)) {
|
||||
try {
|
||||
mixin = candidates[m].mixin;
|
||||
if (!(mixin instanceof tree.mixin.Definition)) {
|
||||
mixin = new tree.mixin.Definition("", [], mixin.rules, null, false);
|
||||
mixin.originalRuleset = mixins[m].originalRuleset || mixins[m];
|
||||
}
|
||||
Array.prototype.push.apply(
|
||||
rules, mixin.eval(env, args, this.important).rules);
|
||||
} catch (e) {
|
||||
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if (!this.currentFileInfo || !this.currentFileInfo.reference) {
|
||||
for (i = 0; i < rules.length; i++) {
|
||||
rule = rules[i];
|
||||
if (rule.markReferenced) {
|
||||
rule.markReferenced();
|
||||
}
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isOneFound) {
|
||||
throw { type: 'Runtime',
|
||||
message: 'No matching definition was found for `' + this.format(args) + '`',
|
||||
index: this.index, filename: this.currentFileInfo.filename };
|
||||
} else {
|
||||
throw { type: 'Name',
|
||||
message: this.selector.toCSS().trim() + " is undefined",
|
||||
index: this.index, filename: this.currentFileInfo.filename };
|
||||
}
|
||||
},
|
||||
format: function (args) {
|
||||
return this.selector.toCSS().trim() + '(' +
|
||||
(args ? args.map(function (a) {
|
||||
var argValue = "";
|
||||
if (a.name) {
|
||||
argValue += a.name + ":";
|
||||
}
|
||||
if (a.value.toCSS) {
|
||||
argValue += a.value.toCSS();
|
||||
} else {
|
||||
argValue += "???";
|
||||
}
|
||||
return argValue;
|
||||
}).join(', ') : "") + ")";
|
||||
}
|
||||
};
|
||||
|
||||
tree.mixin.Definition = function (name, params, rules, condition, variadic) {
|
||||
this.name = name;
|
||||
this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])];
|
||||
this.params = params;
|
||||
this.condition = condition;
|
||||
this.variadic = variadic;
|
||||
this.arity = params.length;
|
||||
this.rules = rules;
|
||||
this._lookups = {};
|
||||
this.required = params.reduce(function (count, p) {
|
||||
if (!p.name || (p.name && !p.value)) { return count + 1; }
|
||||
else { return count; }
|
||||
}, 0);
|
||||
this.parent = tree.Ruleset.prototype;
|
||||
this.frames = [];
|
||||
};
|
||||
tree.mixin.Definition.prototype = {
|
||||
type: "MixinDefinition",
|
||||
accept: function (visitor) {
|
||||
if (this.params && this.params.length) {
|
||||
this.params = visitor.visitArray(this.params);
|
||||
}
|
||||
this.rules = visitor.visitArray(this.rules);
|
||||
if (this.condition) {
|
||||
this.condition = visitor.visit(this.condition);
|
||||
}
|
||||
},
|
||||
variable: function (name) { return this.parent.variable.call(this, name); },
|
||||
variables: function () { return this.parent.variables.call(this); },
|
||||
find: function () { return this.parent.find.apply(this, arguments); },
|
||||
rulesets: function () { return this.parent.rulesets.apply(this); },
|
||||
|
||||
evalParams: function (env, mixinEnv, args, evaldArguments) {
|
||||
/*jshint boss:true */
|
||||
var frame = new(tree.Ruleset)(null, null),
|
||||
varargs, arg,
|
||||
params = this.params.slice(0),
|
||||
i, j, val, name, isNamedFound, argIndex;
|
||||
|
||||
mixinEnv = new tree.evalEnv(mixinEnv, [frame].concat(mixinEnv.frames));
|
||||
|
||||
if (args) {
|
||||
args = args.slice(0);
|
||||
|
||||
for(i = 0; i < args.length; i++) {
|
||||
arg = args[i];
|
||||
if (name = (arg && arg.name)) {
|
||||
isNamedFound = false;
|
||||
for(j = 0; j < params.length; j++) {
|
||||
if (!evaldArguments[j] && name === params[j].name) {
|
||||
evaldArguments[j] = arg.value.eval(env);
|
||||
frame.prependRule(new(tree.Rule)(name, arg.value.eval(env)));
|
||||
isNamedFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isNamedFound) {
|
||||
args.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
} else {
|
||||
throw { type: 'Runtime', message: "Named argument for " + this.name +
|
||||
' ' + args[i].name + ' not found' };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
argIndex = 0;
|
||||
for (i = 0; i < params.length; i++) {
|
||||
if (evaldArguments[i]) { continue; }
|
||||
|
||||
arg = args && args[argIndex];
|
||||
|
||||
if (name = params[i].name) {
|
||||
if (params[i].variadic && args) {
|
||||
varargs = [];
|
||||
for (j = argIndex; j < args.length; j++) {
|
||||
varargs.push(args[j].value.eval(env));
|
||||
}
|
||||
frame.prependRule(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env)));
|
||||
} else {
|
||||
val = arg && arg.value;
|
||||
if (val) {
|
||||
val = val.eval(env);
|
||||
} else if (params[i].value) {
|
||||
val = params[i].value.eval(mixinEnv);
|
||||
frame.resetCache();
|
||||
} else {
|
||||
throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
|
||||
' (' + args.length + ' for ' + this.arity + ')' };
|
||||
}
|
||||
|
||||
frame.prependRule(new(tree.Rule)(name, val));
|
||||
evaldArguments[i] = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (params[i].variadic && args) {
|
||||
for (j = argIndex; j < args.length; j++) {
|
||||
evaldArguments[j] = args[j].value.eval(env);
|
||||
}
|
||||
}
|
||||
argIndex++;
|
||||
}
|
||||
|
||||
return frame;
|
||||
},
|
||||
eval: function (env, args, important) {
|
||||
var _arguments = [],
|
||||
mixinFrames = this.frames.concat(env.frames),
|
||||
frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments),
|
||||
rules, ruleset;
|
||||
|
||||
frame.prependRule(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
|
||||
|
||||
rules = this.rules.slice(0);
|
||||
|
||||
ruleset = new(tree.Ruleset)(null, rules);
|
||||
ruleset.originalRuleset = this;
|
||||
ruleset = ruleset.eval(new(tree.evalEnv)(env, [this, frame].concat(mixinFrames)));
|
||||
if (important) {
|
||||
ruleset = this.parent.makeImportant.apply(ruleset);
|
||||
}
|
||||
return ruleset;
|
||||
},
|
||||
matchCondition: function (args, env) {
|
||||
if (this.condition && !this.condition.eval(
|
||||
new(tree.evalEnv)(env,
|
||||
[this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])] // the parameter variables
|
||||
.concat(this.frames) // the parent namespace/mixin frames
|
||||
.concat(env.frames)))) { // the current environment frames
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
matchArgs: function (args, env) {
|
||||
var argsLength = (args && args.length) || 0, len;
|
||||
|
||||
if (! this.variadic) {
|
||||
if (argsLength < this.required) { return false; }
|
||||
if (argsLength > this.params.length) { return false; }
|
||||
} else {
|
||||
if (argsLength < (this.required - 1)) { return false; }
|
||||
}
|
||||
|
||||
len = Math.min(argsLength, this.arity);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (!this.params[i].name && !this.params[i].variadic) {
|
||||
if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
Reference in New Issue
Block a user