mirror of
https://github.com/less/less.js.git
synced 2026-02-09 14:35:44 -05:00
171 lines
6.5 KiB
JavaScript
171 lines
6.5 KiB
JavaScript
var Node = require("./node"),
|
|
Selector = require("./selector"),
|
|
MixinDefinition = require("./mixin-definition"),
|
|
defaultFunc = require("../functions/default");
|
|
|
|
var MixinCall = function (elements, args, index, currentFileInfo, important) {
|
|
this.selector = new Selector(elements);
|
|
this.arguments = (args && args.length) ? args : null;
|
|
this.index = index;
|
|
this.currentFileInfo = currentFileInfo;
|
|
this.important = important;
|
|
};
|
|
MixinCall.prototype = new Node();
|
|
MixinCall.prototype.type = "MixinCall";
|
|
MixinCall.prototype.accept = function (visitor) {
|
|
if (this.selector) {
|
|
this.selector = visitor.visit(this.selector);
|
|
}
|
|
if (this.arguments) {
|
|
this.arguments = visitor.visitArray(this.arguments);
|
|
}
|
|
};
|
|
MixinCall.prototype.eval = function (context) {
|
|
var mixins, mixin, mixinPath, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule,
|
|
candidates = [], candidate, conditionResult = [], defaultResult, defFalseEitherCase=-1,
|
|
defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset, noArgumentsFilter;
|
|
|
|
function calcDefGroup(mixin, mixinPath) {
|
|
var p, namespace;
|
|
|
|
for (f = 0; f < 2; f++) {
|
|
conditionResult[f] = true;
|
|
defaultFunc.value(f);
|
|
for(p = 0; p < mixinPath.length && conditionResult[f]; p++) {
|
|
namespace = mixinPath[p];
|
|
if (namespace.matchCondition) {
|
|
conditionResult[f] = conditionResult[f] && namespace.matchCondition(null, context);
|
|
}
|
|
}
|
|
if (mixin.matchCondition) {
|
|
conditionResult[f] = conditionResult[f] && mixin.matchCondition(args, context);
|
|
}
|
|
}
|
|
if (conditionResult[0] || conditionResult[1]) {
|
|
if (conditionResult[0] != conditionResult[1]) {
|
|
return conditionResult[1] ?
|
|
defTrue : defFalse;
|
|
}
|
|
|
|
return defNone;
|
|
}
|
|
return defFalseEitherCase;
|
|
}
|
|
|
|
args = this.arguments && this.arguments.map(function (a) {
|
|
return { name: a.name, value: a.value.eval(context) };
|
|
});
|
|
|
|
noArgumentsFilter = function(rule) {return rule.matchArgs(null, context);};
|
|
|
|
for (i = 0; i < context.frames.length; i++) {
|
|
if ((mixins = context.frames[i].find(this.selector, null, noArgumentsFilter)).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].rule;
|
|
mixinPath = mixins[m].path;
|
|
isRecursive = false;
|
|
for(f = 0; f < context.frames.length; f++) {
|
|
if ((!(mixin instanceof MixinDefinition)) && mixin === (context.frames[f].originalRuleset || context.frames[f])) {
|
|
isRecursive = true;
|
|
break;
|
|
}
|
|
}
|
|
if (isRecursive) {
|
|
continue;
|
|
}
|
|
|
|
if (mixin.matchArgs(args, context)) {
|
|
candidate = {mixin: mixin, group: calcDefGroup(mixin, mixinPath)};
|
|
|
|
if (candidate.group!==defFalseEitherCase) {
|
|
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 MixinDefinition)) {
|
|
originalRuleset = mixin.originalRuleset || mixin;
|
|
mixin = new MixinDefinition("", [], mixin.rules, null, false);
|
|
mixin.originalRuleset = originalRuleset;
|
|
}
|
|
Array.prototype.push.apply(
|
|
rules, mixin.evalCall(context, 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 };
|
|
}
|
|
};
|
|
MixinCall.prototype.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(', ') : "") + ")";
|
|
};
|
|
module.exports = MixinCall;
|