Fixes #3434 - memory / runtime improvements (#3572)

* Remove class extends pattern for nodes
* Fix node benchmark
* v3.13.0
* Restore tree caching
This commit is contained in:
Matthew Dean
2020-12-11 18:00:22 -08:00
committed by GitHub
parent 0e268598c8
commit 257efdd688
59 changed files with 9156 additions and 14844 deletions

6058
dist/less.js vendored

File diff suppressed because it is too large Load Diff

4
dist/less.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,5 +3,5 @@
"packages/*"
],
"npmClient": "npm",
"version": "3.12.2"
"version": "3.13.0"
}

View File

@@ -1,7 +1,7 @@
{
"name": "@less/root",
"private": true,
"version": "3.12.2",
"version": "3.13.0",
"description": "Less monorepo",
"homepage": "http://lesscss.org",
"scripts": {

View File

@@ -243,9 +243,6 @@ module.exports = function(grunt) {
benchmark: {
command: "node benchmark/index.js"
},
benchmarkbrowser: {
command: "node test/browser/generator/runner.js benchmark"
},
opts: {
// test running with all current options (using `opts` since `options` means something already)
command: [
@@ -416,15 +413,8 @@ module.exports = function(grunt) {
]);
// Run benchmark
grunt.registerTask("benchmark-node", [
grunt.registerTask("benchmark", [
"shell:testcjs",
"shell:benchmark"
]);
// Run all browser tests
grunt.registerTask("benchmark", [
"browsertest-lessjs",
"connect",
"shell:benchmarkbrowser"
]);
};

File diff suppressed because it is too large Load Diff

View File

@@ -2327,7 +2327,7 @@ p + h1 {
}
.no-parens {
.mixin;
.mixin();
}
.no-args {
@@ -2388,7 +2388,7 @@ body {
@var: 10px;
width: @var;
}
#var-inside { .var-inside; }
#var-inside { .var-inside(); }
.mix-inner (@var) {
border-width: @var;
}
@@ -2525,41 +2525,41 @@ body {
}
#container {
color: black;
.mixin;
.mixout;
#theme > .mixin;
.mixin();
.mixout();
#theme > .mixin();
}
#header {
.milk {
color: white;
.mixin;
#theme > .mixin;
.mixin();
#theme > .mixin();
}
#cookie {
.chips {
#namespace .borders;
#namespace .borders();
.calories {
#container;
#container();
}
}
.borders;
.borders();
}
}
.secure-zone { #namespace .biohazard .man; }
.secure-zone { #namespace .biohazard .man(); }
.direct {
#namespace > .borders;
#namespace > .borders();
}
#operations {
color: #110000 + #000011 + #001100; // #111111
height: 10px / 2px + 6px - 1px * 2; // 9px
height: (10px / 2px) + 6px - 1px * 2; // 9px
width: 2 * 4 - 5em; // 3em
.spacing {
height: 10px / 2px+6px-1px*2;
height: (10px / 2px)+6px-1px*2;
width: 2 * 4-5em;
}
subtraction: 20 - 10 - 5 - 5; // 0
division: 20 / 5 / 4; // 1
division: (20 / 5 / 4); // 1
}
@x: 4;
@@ -2588,7 +2588,7 @@ body {
background-color: #222222 - #fff; // #000000
.other {
color: 2 * #111; // #222222
border-color: #333333 / 3 + #111; // #222222
border-color: (#333333 / 3) + #111; // #222222
}
}
.parens {
@@ -2604,7 +2604,7 @@ body {
padding: (2 * @var) 4 4 (@var * 1px);
width: (@var * @var) * 6;
height: (7 * 7) + (8 * 8);
margin: 4 * (5 + 5) / 2 - (@var * 2);
margin: 4 * ((5 + 5) / 2) - (@var * 2);
//margin: (6 * 6)px;
}
@@ -2657,7 +2657,7 @@ body {
.tiny-scope {
color: @mix; // #989
.mixin;
.mixin();
}
.scope1 {
@@ -3091,7 +3091,7 @@ p + h1 {
}
.no-parens {
.mixin;
.mixin();
}
.no-args {
@@ -3152,7 +3152,7 @@ body {
@var: 10px;
width: @var;
}
#var-inside { .var-inside; }
#var-inside { .var-inside(); }
.mix-inner (@var) {
border-width: @var;
}
@@ -3289,41 +3289,41 @@ body {
}
#container {
color: black;
.mixin;
.mixout;
#theme > .mixin;
.mixin();
.mixout();
#theme > .mixin();
}
#header {
.milk {
color: white;
.mixin;
#theme > .mixin;
.mixin();
#theme > .mixin();
}
#cookie {
.chips {
#namespace .borders;
#namespace .borders();
.calories {
#container;
#container();
}
}
.borders;
.borders();
}
}
.secure-zone { #namespace .biohazard .man; }
.secure-zone { #namespace .biohazard .man(); }
.direct {
#namespace > .borders;
#namespace > .borders();
}
#operations {
color: #110000 + #000011 + #001100; // #111111
height: 10px / 2px + 6px - 1px * 2; // 9px
height: (10px / 2px) + 6px - 1px * 2; // 9px
width: 2 * 4 - 5em; // 3em
.spacing {
height: 10px / 2px+6px-1px*2;
height: (10px / 2px)+6px-1px*2;
width: 2 * 4-5em;
}
subtraction: 20 - 10 - 5 - 5; // 0
division: 20 / 5 / 4; // 1
division: (20 / 5 / 4); // 1
}
@x: 4;
@@ -3352,7 +3352,7 @@ body {
background-color: #222222 - #fff; // #000000
.other {
color: 2 * #111; // #222222
border-color: #333333 / 3 + #111; // #222222
border-color: (#333333 / 3) + #111; // #222222
}
}
.parens {
@@ -3368,7 +3368,7 @@ body {
padding: (2 * @var) 4 4 (@var * 1px);
width: (@var * @var) * 6;
height: (7 * 7) + (8 * 8);
margin: 4 * (5 + 5) / 2 - (@var * 2);
margin: 4 * ((5 + 5) / 2) - (@var * 2);
//margin: (6 * 6)px;
}
@@ -3421,7 +3421,7 @@ body {
.tiny-scope {
color: @mix; // #989
.mixin;
.mixin();
}
.scope1 {
@@ -3855,7 +3855,7 @@ p + h1 {
}
.no-parens {
.mixin;
.mixin();
}
.no-args {
@@ -3916,7 +3916,7 @@ body {
@var: 10px;
width: @var;
}
#var-inside { .var-inside; }
#var-inside { .var-inside(); }
.mix-inner (@var) {
border-width: @var;
}

View File

@@ -1,4 +0,0 @@
var less = {
logLevel: 4,
rewriteUrls: 0
};

View File

@@ -1,3 +0,0 @@
describe('Benchmark', function() {
testLessEqualsInDocument();
});

View File

@@ -2,7 +2,7 @@ var path = require('path'),
fs = require('fs'),
now = require("performance-now");
var less = require('../test/less');
var less = require('../.');
var file = path.join(__dirname, 'benchmark.less');
if (process.argv[2]) { file = path.join(process.cwd(), process.argv[2]) }
@@ -31,7 +31,7 @@ fs.readFile(file, 'utf8', function (e, data) {
less.parse(data, {}, function(err, root, imports, options) {
if (err) {
less.writeError(err);
console.log(err);
process.exit(3);
}
parserEnd = now();

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "less",
"version": "3.12.2",
"version": "3.13.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "less",
"version": "3.12.2",
"version": "3.13.0",
"description": "Leaner CSS",
"homepage": "http://lesscss.org",
"author": {
@@ -54,8 +54,8 @@
"source-map": "~0.6.0"
},
"devDependencies": {
"@less/test-data": "^3.12.2",
"@less/test-import-module": "^3.12.0",
"@less/test-data": "^3.13.0",
"@less/test-import-module": "^3.13.0",
"@typescript-eslint/eslint-plugin": "^3.3.0",
"@typescript-eslint/parser": "^3.3.0",
"benny": "^3.6.12",

View File

@@ -4,7 +4,7 @@ import LessError from './less-error';
import * as utils from './utils';
import logger from './logger';
export default environment => {
export default function(environment) {
// FileInfo = {
// 'rewriteUrls' - option - whether to adjust URL's to be relative
// 'filename' - full resolved filename of current file
@@ -26,7 +26,7 @@ export default environment => {
this.context = context;
// Deprecated? Unused outside of here, could be useful.
this.queue = []; // Files which haven't been imported yet
this.files = []; // List of files imported
this.files = {}; // Holds the imported parse trees.
}
/**
@@ -38,12 +38,11 @@ export default environment => {
* @param callback - callback for when it is imported
*/
push(path, tryAppendExtension, currentFileInfo, importOptions, callback) {
const importManager = this;
const pluginLoader = this.context.pluginManager.Loader;
const importManager = this, pluginLoader = this.context.pluginManager.Loader;
this.queue.push(path);
const fileParsedFunc = (e, root, fullPath) => {
const fileParsedFunc = function (e, root, fullPath) {
importManager.queue.splice(importManager.queue.indexOf(path), 1); // Remove the path from the queue
const importedEqualsRoot = fullPath === importManager.rootFilename;
@@ -52,9 +51,11 @@ export default environment => {
logger.info(`The file ${fullPath} was skipped because it was not found and the import was marked optional.`);
}
else {
const files = importManager.files
if (files.indexOf(fullPath) === -1) {
files.push(fullPath)
// Inline imports aren't cached here.
// If we start to cache them, please make sure they won't conflict with non-inline imports of the
// same name as they used to do before this comment and the condition below have been added.
if (!importManager.files[fullPath] && !importOptions.inline) {
importManager.files[fullPath] = { root, options: importOptions };
}
if (e && !importManager.error) { importManager.error = e; }
callback(e, root, importedEqualsRoot, fullPath);
@@ -75,7 +76,7 @@ export default environment => {
return;
}
const loadFileCallback = loadedFile => {
const loadFileCallback = function(loadedFile) {
let plugin;
const resolvedFilename = loadedFile.filename;
const contents = loadedFile.contents.replace(/^\uFEFF/, '');
@@ -120,9 +121,20 @@ export default environment => {
} else if (importOptions.inline) {
fileParsedFunc(null, contents, resolvedFilename);
} else {
new Parser(newEnv, importManager, newFileInfo).parse(contents, (e, root) => {
fileParsedFunc(e, root, resolvedFilename);
});
// import (multiple) parse trees apparently get altered and can't be cached.
// TODO: investigate why this is
if (importManager.files[resolvedFilename]
&& !importManager.files[resolvedFilename].options.multiple
&& !importOptions.multiple) {
fileParsedFunc(null, importManager.files[resolvedFilename].root, resolvedFilename);
}
else {
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (e, root) {
fileParsedFunc(e, root, resolvedFilename);
});
}
}
};
let loadedFile;

View File

@@ -42,7 +42,7 @@ export default (environment, fileManagers) => {
* It's not clear what should / must be public and why.
*/
const initial = {
version: [3, 12, 2],
version: [3, 13, 0],
data,
tree,
Environment,

View File

@@ -52,8 +52,12 @@ export default SourceMapBuilder => {
result.map = sourceMapBuilder.getExternalSourceMap();
}
const rootFilename = this.imports.rootFilename
result.imports = this.imports.files.filter(file => file !== rootFilename);
result.imports = [];
for (const file in this.imports.files) {
if (this.imports.files.hasOwnProperty(file) && file !== this.imports.rootFilename) {
result.imports.push(file);
}
}
return result;
}
}

View File

@@ -1,37 +1,36 @@
import Node from './node';
import { extend } from './util';
class Anonymous extends Node {
constructor(value, index, currentFileInfo, mapLines, rulesetLike, visibilityInfo) {
super();
this.value = value;
this._index = index;
this._fileInfo = currentFileInfo;
this.mapLines = mapLines;
this.rulesetLike = (typeof rulesetLike === 'undefined') ? false : rulesetLike;
this.allowRoot = true;
this.copyVisibilityInfo(visibilityInfo);
}
eval() {
return new Anonymous(this.value, this._index, this._fileInfo, this.mapLines, this.rulesetLike, this.visibilityInfo());
}
compare(other) {
return other.toCSS && this.toCSS() === other.toCSS() ? 0 : undefined;
}
isRulesetLike() {
return this.rulesetLike;
}
genCSS(context, output) {
this.nodeVisible = Boolean(this.value);
if (this.nodeVisible) {
output.add(this.value, this._fileInfo, this._index, this.mapLines);
}
}
const Anonymous = function(value, index, currentFileInfo, mapLines, rulesetLike, visibilityInfo) {
this.value = value;
this._index = index;
this._fileInfo = currentFileInfo;
this.mapLines = mapLines;
this.rulesetLike = (typeof rulesetLike === 'undefined') ? false : rulesetLike;
this.allowRoot = true;
this.copyVisibilityInfo(visibilityInfo);
}
Anonymous.prototype = new Node();
Anonymous.prototype.eval = function() {
return new Anonymous(this.value, this._index, this._fileInfo, this.mapLines, this.rulesetLike, this.visibilityInfo());
};
Anonymous.prototype.compare = function(other) {
return other.toCSS && this.toCSS() === other.toCSS() ? 0 : undefined;
};
Anonymous.prototype.isRulesetLike = function() {
return this.rulesetLike;
};
Anonymous.prototype.genCSS = function(context, output) {
this.nodeVisible = Boolean(this.value);
if (this.nodeVisible) {
output.add(this.value, this._fileInfo, this._index, this.mapLines);
}
};
Anonymous.prototype.type = 'Anonymous';
export default Anonymous;

View File

@@ -1,33 +1,31 @@
import Node from './node';
class Assignment extends Node {
constructor(key, val) {
super();
const Assignment = function(key, val) {
this.key = key;
this.value = val;
};
this.key = key;
this.value = val;
}
Assignment.prototype = new Node();
accept(visitor) {
this.value = visitor.visit(this.value);
}
Assignment.prototype.accept = function(visitor) {
this.value = visitor.visit(this.value);
};
eval(context) {
if (this.value.eval) {
return new Assignment(this.key, this.value.eval(context));
}
return this;
Assignment.prototype.eval = function(context) {
if (this.value.eval) {
return new Assignment(this.key, this.value.eval(context));
}
return this;
};
genCSS(context, output) {
output.add(`${this.key}=`);
if (this.value.genCSS) {
this.value.genCSS(context, output);
} else {
output.add(this.value);
}
Assignment.prototype.genCSS = function(context, output) {
output.add(`${this.key}=`);
if (this.value.genCSS) {
this.value.genCSS(context, output);
} else {
output.add(this.value);
}
}
};
Assignment.prototype.type = 'Assignment';
export default Assignment;

View File

@@ -3,163 +3,161 @@ import Selector from './selector';
import Ruleset from './ruleset';
import Anonymous from './anonymous';
class AtRule extends Node {
constructor(
name,
value,
rules,
index,
currentFileInfo,
debugInfo,
isRooted,
visibilityInfo
) {
super();
const AtRule = function(
name,
value,
rules,
index,
currentFileInfo,
debugInfo,
isRooted,
visibilityInfo
) {
let i;
let i;
this.name = name;
this.value = (value instanceof Node) ? value : (value ? new Anonymous(value) : value);
if (rules) {
if (Array.isArray(rules)) {
this.rules = rules;
} else {
this.rules = [rules];
this.rules[0].selectors = (new Selector([], null, null, index, currentFileInfo)).createEmptySelectors();
}
for (i = 0; i < this.rules.length; i++) {
this.rules[i].allowImports = true;
}
this.setParent(this.rules, this);
}
this._index = index;
this._fileInfo = currentFileInfo;
this.debugInfo = debugInfo;
this.isRooted = isRooted || false;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
}
accept(visitor) {
const value = this.value;
const rules = this.rules;
if (rules) {
this.rules = visitor.visitArray(rules);
}
if (value) {
this.value = visitor.visit(value);
}
}
isRulesetLike() {
return this.rules || !this.isCharset();
}
isCharset() {
return '@charset' === this.name;
}
genCSS(context, output) {
const value = this.value;
const rules = this.rules;
output.add(this.name, this.fileInfo(), this.getIndex());
if (value) {
output.add(' ');
value.genCSS(context, output);
}
if (rules) {
this.outputRuleset(context, output, rules);
this.name = name;
this.value = (value instanceof Node) ? value : (value ? new Anonymous(value) : value);
if (rules) {
if (Array.isArray(rules)) {
this.rules = rules;
} else {
output.add(';');
this.rules = [rules];
this.rules[0].selectors = (new Selector([], null, null, index, currentFileInfo)).createEmptySelectors();
}
for (i = 0; i < this.rules.length; i++) {
this.rules[i].allowImports = true;
}
this.setParent(this.rules, this);
}
this._index = index;
this._fileInfo = currentFileInfo;
this.debugInfo = debugInfo;
this.isRooted = isRooted || false;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
};
eval(context) {
let mediaPathBackup;
let mediaBlocksBackup;
let value = this.value;
let rules = this.rules;
AtRule.prototype = new Node();
// media stored inside other atrule should not bubble over it
// backpup media bubbling information
mediaPathBackup = context.mediaPath;
mediaBlocksBackup = context.mediaBlocks;
// deleted media bubbling information
context.mediaPath = [];
context.mediaBlocks = [];
if (value) {
value = value.eval(context);
}
if (rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
rules = [rules[0].eval(context)];
rules[0].root = true;
}
// restore media bubbling information
context.mediaPath = mediaPathBackup;
context.mediaBlocks = mediaBlocksBackup;
return new AtRule(this.name, value, rules,
this.getIndex(), this.fileInfo(), this.debugInfo, this.isRooted, this.visibilityInfo());
AtRule.prototype.accept = function(visitor) {
const value = this.value;
const rules = this.rules;
if (rules) {
this.rules = visitor.visitArray(rules);
}
variable(name) {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.variable.call(this.rules[0], name);
}
if (value) {
this.value = visitor.visit(value);
}
};
find(...args) {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.find.apply(this.rules[0], args);
}
AtRule.prototype.isRulesetLike = function() {
return this.rules || !this.isCharset();
};
AtRule.prototype.isCharset = function() {
return '@charset' === this.name;
};
AtRule.prototype.genCSS = function(context, output) {
const value = this.value;
const rules = this.rules;
output.add(this.name, this.fileInfo(), this.getIndex());
if (value) {
output.add(' ');
value.genCSS(context, output);
}
rulesets() {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.rulesets.apply(this.rules[0]);
}
if (rules) {
this.outputRuleset(context, output, rules);
} else {
output.add(';');
}
};
outputRuleset(context, output, rules) {
const ruleCnt = rules.length;
let i;
context.tabLevel = (context.tabLevel | 0) + 1;
AtRule.prototype.eval = function(context) {
let mediaPathBackup;
let mediaBlocksBackup;
let value = this.value;
let rules = this.rules;
// Compressed
if (context.compress) {
output.add('{');
for (i = 0; i < ruleCnt; i++) {
rules[i].genCSS(context, output);
}
output.add('}');
context.tabLevel--;
return;
// media stored inside other atrule should not bubble over it
// backpup media bubbling information
mediaPathBackup = context.mediaPath;
mediaBlocksBackup = context.mediaBlocks;
// deleted media bubbling information
context.mediaPath = [];
context.mediaBlocks = [];
if (value) {
value = value.eval(context);
}
if (rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
rules = [rules[0].eval(context)];
rules[0].root = true;
}
// restore media bubbling information
context.mediaPath = mediaPathBackup;
context.mediaBlocks = mediaBlocksBackup;
return new AtRule(this.name, value, rules,
this.getIndex(), this.fileInfo(), this.debugInfo, this.isRooted, this.visibilityInfo());
};
AtRule.prototype.variable = function(name) {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.variable.call(this.rules[0], name);
}
};
AtRule.prototype.find = function(...args) {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.find.apply(this.rules[0], args);
}
};
AtRule.prototype.rulesets = function() {
if (this.rules) {
// assuming that there is only one rule at this point - that is how parser constructs the rule
return Ruleset.prototype.rulesets.apply(this.rules[0]);
}
};
AtRule.prototype.outputRuleset = function(context, output, rules) {
const ruleCnt = rules.length;
let i;
context.tabLevel = (context.tabLevel | 0) + 1;
// Compressed
if (context.compress) {
output.add('{');
for (i = 0; i < ruleCnt; i++) {
rules[i].genCSS(context, output);
}
// Non-compressed
const tabSetStr = `\n${Array(context.tabLevel).join(' ')}`;
const tabRuleStr = `${tabSetStr} `;
if (!ruleCnt) {
output.add(` {${tabSetStr}}`);
} else {
output.add(` {${tabRuleStr}`);
rules[0].genCSS(context, output);
for (i = 1; i < ruleCnt; i++) {
output.add(tabRuleStr);
rules[i].genCSS(context, output);
}
output.add(`${tabSetStr}}`);
}
output.add('}');
context.tabLevel--;
return;
}
}
// Non-compressed
const tabSetStr = `\n${Array(context.tabLevel).join(' ')}`;
const tabRuleStr = `${tabSetStr} `;
if (!ruleCnt) {
output.add(` {${tabSetStr}}`);
} else {
output.add(` {${tabRuleStr}`);
rules[0].genCSS(context, output);
for (i = 1; i < ruleCnt; i++) {
output.add(tabRuleStr);
rules[i].genCSS(context, output);
}
output.add(`${tabSetStr}}`);
}
context.tabLevel--;
};
AtRule.prototype.type = 'AtRule';
export default AtRule;

View File

@@ -1,34 +1,32 @@
import Node from './node';
class Attribute extends Node {
constructor(key, op, value) {
super();
const Attribute = function(key, op, value) {
this.key = key;
this.op = op;
this.value = value;
};
this.key = key;
this.op = op;
this.value = value;
Attribute.prototype = new Node();
Attribute.prototype.eval = function(context) {
return new Attribute(this.key.eval ? this.key.eval(context) : this.key,
this.op, (this.value && this.value.eval) ? this.value.eval(context) : this.value);
};
Attribute.prototype.genCSS = function(context, output) {
output.add(this.toCSS(context));
};
Attribute.prototype.toCSS = function(context) {
let value = this.key.toCSS ? this.key.toCSS(context) : this.key;
if (this.op) {
value += this.op;
value += (this.value.toCSS ? this.value.toCSS(context) : this.value);
}
eval(context) {
return new Attribute(this.key.eval ? this.key.eval(context) : this.key,
this.op, (this.value && this.value.eval) ? this.value.eval(context) : this.value);
}
genCSS(context, output) {
output.add(this.toCSS(context));
}
toCSS(context) {
let value = this.key.toCSS ? this.key.toCSS(context) : this.key;
if (this.op) {
value += this.op;
value += (this.value.toCSS ? this.value.toCSS(context) : this.value);
}
return `[${value}]`;
}
}
return `[${value}]`;
};
Attribute.prototype.type = 'Attribute';
export default Attribute;

View File

@@ -5,109 +5,107 @@ import FunctionCaller from '../functions/function-caller';
//
// A function call node.
//
class Call extends Node {
constructor(name, args, index, currentFileInfo) {
super();
this.name = name;
this.args = args;
this.calc = name === 'calc';
this._index = index;
this._fileInfo = currentFileInfo;
}
accept(visitor) {
if (this.args) {
this.args = visitor.visitArray(this.args);
}
}
//
// When evaluating a function call,
// we either find the function in the functionRegistry,
// in which case we call it, passing the evaluated arguments,
// if this returns null or we cannot find the function, we
// simply print it out as it appeared originally [2].
//
// The reason why we evaluate the arguments, is in the case where
// we try to pass a variable to a function, like: `saturate(@color)`.
// The function should receive the value, not the variable.
//
eval(context) {
/**
* Turn off math for calc(), and switch back on for evaluating nested functions
*/
const currentMathContext = context.mathOn;
context.mathOn = !this.calc;
if (this.calc || context.inCalc) {
context.enterCalc();
}
const exitCalc = () => {
if (this.calc || context.inCalc) {
context.exitCalc();
}
context.mathOn = currentMathContext;
};
let result;
const funcCaller = new FunctionCaller(this.name, context, this.getIndex(), this.fileInfo());
if (funcCaller.isValid()) {
try {
result = funcCaller.call(this.args);
exitCalc();
} catch (e) {
if (e.hasOwnProperty('line') && e.hasOwnProperty('column')) {
throw e
}
throw {
type: e.type || 'Runtime',
message: `error evaluating function \`${this.name}\`${e.message ? `: ${e.message}` : ''}`,
index: this.getIndex(),
filename: this.fileInfo().filename,
line: e.lineNumber,
column: e.columnNumber
};
}
if (result !== null && result !== undefined) {
// Results that that are not nodes are cast as Anonymous nodes
// Falsy values or booleans are returned as empty nodes
if (!(result instanceof Node)) {
if (!result || result === true) {
result = new Anonymous(null);
}
else {
result = new Anonymous(result.toString());
}
}
result._index = this._index;
result._fileInfo = this._fileInfo;
return result;
}
}
const args = this.args.map(a => a.eval(context));
exitCalc();
return new Call(this.name, args, this.getIndex(), this.fileInfo());
}
genCSS(context, output) {
output.add(`${this.name}(`, this.fileInfo(), this.getIndex());
for (let i = 0; i < this.args.length; i++) {
this.args[i].genCSS(context, output);
if (i + 1 < this.args.length) {
output.add(', ');
}
}
output.add(')');
}
const Call = function(name, args, index, currentFileInfo) {
this.name = name;
this.args = args;
this.calc = name === 'calc';
this._index = index;
this._fileInfo = currentFileInfo;
}
Call.prototype = new Node();
Call.prototype.accept = function(visitor) {
if (this.args) {
this.args = visitor.visitArray(this.args);
}
};
//
// When evaluating a function call,
// we either find the function in the functionRegistry,
// in which case we call it, passing the evaluated arguments,
// if this returns null or we cannot find the function, we
// simply print it out as it appeared originally [2].
//
// The reason why we evaluate the arguments, is in the case where
// we try to pass a variable to a function, like: `saturate(@color)`.
// The function should receive the value, not the variable.
//
Call.prototype.eval = function(context) {
/**
* Turn off math for calc(), and switch back on for evaluating nested functions
*/
const currentMathContext = context.mathOn;
context.mathOn = !this.calc;
if (this.calc || context.inCalc) {
context.enterCalc();
}
const exitCalc = () => {
if (this.calc || context.inCalc) {
context.exitCalc();
}
context.mathOn = currentMathContext;
};
let result;
const funcCaller = new FunctionCaller(this.name, context, this.getIndex(), this.fileInfo());
if (funcCaller.isValid()) {
try {
result = funcCaller.call(this.args);
exitCalc();
} catch (e) {
if (e.hasOwnProperty('line') && e.hasOwnProperty('column')) {
throw e
}
throw {
type: e.type || 'Runtime',
message: `error evaluating function \`${this.name}\`${e.message ? `: ${e.message}` : ''}`,
index: this.getIndex(),
filename: this.fileInfo().filename,
line: e.lineNumber,
column: e.columnNumber
};
}
if (result !== null && result !== undefined) {
// Results that that are not nodes are cast as Anonymous nodes
// Falsy values or booleans are returned as empty nodes
if (!(result instanceof Node)) {
if (!result || result === true) {
result = new Anonymous(null);
}
else {
result = new Anonymous(result.toString());
}
}
result._index = this._index;
result._fileInfo = this._fileInfo;
return result;
}
}
const args = this.args.map(a => a.eval(context));
exitCalc();
return new Call(this.name, args, this.getIndex(), this.fileInfo());
};
Call.prototype.genCSS = function(context, output) {
output.add(`${this.name}(`, this.fileInfo(), this.getIndex());
for (let i = 0; i < this.args.length; i++) {
this.args[i].genCSS(context, output);
if (i + 1 < this.args.length) {
output.add(', ');
}
}
output.add(')');
};
Call.prototype.type = 'Call';
export default Call;

View File

@@ -4,216 +4,214 @@ import colors from '../data/colors';
//
// RGB Colors - #ff0014, #eee
//
class Color extends Node {
constructor(rgb, a, originalForm) {
super();
const self = this;
//
// The end goal here, is to parse the arguments
// into an integer triplet, such as `128, 255, 0`
//
// This facilitates operations and conversions.
//
if (Array.isArray(rgb)) {
this.rgb = rgb;
} else if (rgb.length >= 6) {
this.rgb = [];
rgb.match(/.{2}/g).map((c, i) => {
if (i < 3) {
self.rgb.push(parseInt(c, 16));
} else {
self.alpha = (parseInt(c, 16)) / 255;
}
});
} else {
this.rgb = [];
rgb.split('').map((c, i) => {
if (i < 3) {
self.rgb.push(parseInt(c + c, 16));
} else {
self.alpha = (parseInt(c + c, 16)) / 255;
}
});
}
this.alpha = this.alpha || (typeof a === 'number' ? a : 1);
if (typeof originalForm !== 'undefined') {
this.value = originalForm;
}
}
luma() {
let r = this.rgb[0] / 255;
let g = this.rgb[1] / 255;
let b = this.rgb[2] / 255;
r = (r <= 0.03928) ? r / 12.92 : Math.pow(((r + 0.055) / 1.055), 2.4);
g = (g <= 0.03928) ? g / 12.92 : Math.pow(((g + 0.055) / 1.055), 2.4);
b = (b <= 0.03928) ? b / 12.92 : Math.pow(((b + 0.055) / 1.055), 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
genCSS(context, output) {
output.add(this.toCSS(context));
}
toCSS(context, doNotCompress) {
const compress = context && context.compress && !doNotCompress;
let color;
let alpha;
let colorFunction;
let args = [];
// `value` is set if this color was originally
// converted from a named color string so we need
// to respect this and try to output named color too.
alpha = this.fround(context, this.alpha);
if (this.value) {
if (this.value.indexOf('rgb') === 0) {
if (alpha < 1) {
colorFunction = 'rgba';
}
} else if (this.value.indexOf('hsl') === 0) {
if (alpha < 1) {
colorFunction = 'hsla';
} else {
colorFunction = 'hsl';
}
const Color = function(rgb, a, originalForm) {
const self = this;
//
// The end goal here, is to parse the arguments
// into an integer triplet, such as `128, 255, 0`
//
// This facilitates operations and conversions.
//
if (Array.isArray(rgb)) {
this.rgb = rgb;
} else if (rgb.length >= 6) {
this.rgb = [];
rgb.match(/.{2}/g).map((c, i) => {
if (i < 3) {
self.rgb.push(parseInt(c, 16));
} else {
return this.value;
self.alpha = (parseInt(c, 16)) / 255;
}
} else {
});
} else {
this.rgb = [];
rgb.split('').map((c, i) => {
if (i < 3) {
self.rgb.push(parseInt(c + c, 16));
} else {
self.alpha = (parseInt(c + c, 16)) / 255;
}
});
}
this.alpha = this.alpha || (typeof a === 'number' ? a : 1);
if (typeof originalForm !== 'undefined') {
this.value = originalForm;
}
};
Color.prototype = new Node();
Color.prototype.luma = function() {
let r = this.rgb[0] / 255;
let g = this.rgb[1] / 255;
let b = this.rgb[2] / 255;
r = (r <= 0.03928) ? r / 12.92 : Math.pow(((r + 0.055) / 1.055), 2.4);
g = (g <= 0.03928) ? g / 12.92 : Math.pow(((g + 0.055) / 1.055), 2.4);
b = (b <= 0.03928) ? b / 12.92 : Math.pow(((b + 0.055) / 1.055), 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
Color.prototype.genCSS = function(context, output) {
output.add(this.toCSS(context));
};
Color.prototype.toCSS = function(context, doNotCompress) {
const compress = context && context.compress && !doNotCompress;
let color;
let alpha;
let colorFunction;
let args = [];
// `value` is set if this color was originally
// converted from a named color string so we need
// to respect this and try to output named color too.
alpha = this.fround(context, this.alpha);
if (this.value) {
if (this.value.indexOf('rgb') === 0) {
if (alpha < 1) {
colorFunction = 'rgba';
}
}
switch (colorFunction) {
case 'rgba':
args = this.rgb.map(c => clamp(Math.round(c), 255)).concat(clamp(alpha, 1));
break;
case 'hsla':
args.push(clamp(alpha, 1));
case 'hsl':
color = this.toHSL();
args = [
this.fround(context, color.h),
`${this.fround(context, color.s * 100)}%`,
`${this.fround(context, color.l * 100)}%`
].concat(args);
}
if (colorFunction) {
// Values are capped between `0` and `255`, rounded and zero-padded.
return `${colorFunction}(${args.join(`,${compress ? '' : ' '}`)})`;
}
color = this.toRGB();
if (compress) {
const splitcolor = color.split('');
// Convert color to short format
if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) {
color = `#${splitcolor[1]}${splitcolor[3]}${splitcolor[5]}`;
} else if (this.value.indexOf('hsl') === 0) {
if (alpha < 1) {
colorFunction = 'hsla';
} else {
colorFunction = 'hsl';
}
}
return color;
}
//
// Operations have to be done per-channel, if not,
// channels will spill onto each other. Once we have
// our result, in the form of an integer triplet,
// we create a new Color node to hold the result.
//
operate(context, op, other) {
const rgb = new Array(3);
const alpha = this.alpha * (1 - other.alpha) + other.alpha;
for (let c = 0; c < 3; c++) {
rgb[c] = this._operate(context, op, this.rgb[c], other.rgb[c]);
}
return new Color(rgb, alpha);
}
toRGB() {
return toHex(this.rgb);
}
toHSL() {
const r = this.rgb[0] / 255;
const g = this.rgb[1] / 255;
const b = this.rgb[2] / 255;
const a = this.alpha;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h;
let s;
const l = (max + min) / 2;
const d = max - min;
if (max === min) {
h = s = 0;
} else {
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
return this.value;
}
return { h: h * 360, s, l, a };
}
// Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
toHSV() {
const r = this.rgb[0] / 255;
const g = this.rgb[1] / 255;
const b = this.rgb[2] / 255;
const a = this.alpha;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h;
let s;
const v = max;
const d = max - min;
if (max === 0) {
s = 0;
} else {
s = d / max;
} else {
if (alpha < 1) {
colorFunction = 'rgba';
}
}
if (max === min) {
h = 0;
} else {
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
switch (colorFunction) {
case 'rgba':
args = this.rgb.map(c => clamp(Math.round(c), 255)).concat(clamp(alpha, 1));
break;
case 'hsla':
args.push(clamp(alpha, 1));
case 'hsl':
color = this.toHSL();
args = [
this.fround(context, color.h),
`${this.fround(context, color.s * 100)}%`,
`${this.fround(context, color.l * 100)}%`
].concat(args);
}
if (colorFunction) {
// Values are capped between `0` and `255`, rounded and zero-padded.
return `${colorFunction}(${args.join(`,${compress ? '' : ' '}`)})`;
}
color = this.toRGB();
if (compress) {
const splitcolor = color.split('');
// Convert color to short format
if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) {
color = `#${splitcolor[1]}${splitcolor[3]}${splitcolor[5]}`;
}
return { h: h * 360, s, v, a };
}
toARGB() {
return toHex([this.alpha * 255].concat(this.rgb));
return color;
};
//
// Operations have to be done per-channel, if not,
// channels will spill onto each other. Once we have
// our result, in the form of an integer triplet,
// we create a new Color node to hold the result.
//
Color.prototype.operate = function(context, op, other) {
const rgb = new Array(3);
const alpha = this.alpha * (1 - other.alpha) + other.alpha;
for (let c = 0; c < 3; c++) {
rgb[c] = this._operate(context, op, this.rgb[c], other.rgb[c]);
}
return new Color(rgb, alpha);
};
Color.prototype.toRGB = function() {
return toHex(this.rgb);
};
Color.prototype.toHSL = function() {
const r = this.rgb[0] / 255;
const g = this.rgb[1] / 255;
const b = this.rgb[2] / 255;
const a = this.alpha;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h;
let s;
const l = (max + min) / 2;
const d = max - min;
if (max === min) {
h = s = 0;
} else {
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h * 360, s, l, a };
};
// Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
Color.prototype.toHSV = function() {
const r = this.rgb[0] / 255;
const g = this.rgb[1] / 255;
const b = this.rgb[2] / 255;
const a = this.alpha;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h;
let s;
const v = max;
const d = max - min;
if (max === 0) {
s = 0;
} else {
s = d / max;
}
compare(x) {
return (x.rgb &&
x.rgb[0] === this.rgb[0] &&
x.rgb[1] === this.rgb[1] &&
x.rgb[2] === this.rgb[2] &&
x.alpha === this.alpha) ? 0 : undefined;
if (max === min) {
h = 0;
} else {
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
}
return { h: h * 360, s, v, a };
};
Color.prototype.toARGB = function() {
return toHex([this.alpha * 255].concat(this.rgb));
};
Color.prototype.compare = function(x) {
return (x.rgb &&
x.rgb[0] === this.rgb[0] &&
x.rgb[1] === this.rgb[1] &&
x.rgb[2] === this.rgb[2] &&
x.alpha === this.alpha) ? 0 : undefined;
};
Color.prototype.type = 'Color';
@@ -243,4 +241,5 @@ Color.fromKeyword = keyword => {
return c;
}
};
export default Color;

View File

@@ -5,24 +5,22 @@ const _noSpaceCombinators = {
'|': true
};
class Combinator extends Node {
constructor(value) {
super();
if (value === ' ') {
this.value = ' ';
this.emptyOrWhitespace = true;
} else {
this.value = value ? value.trim() : '';
this.emptyOrWhitespace = this.value === '';
}
const Combinator = function(value) {
if (value === ' ') {
this.value = ' ';
this.emptyOrWhitespace = true;
} else {
this.value = value ? value.trim() : '';
this.emptyOrWhitespace = this.value === '';
}
};
genCSS(context, output) {
const spaceOrEmpty = (context.compress || _noSpaceCombinators[this.value]) ? '' : ' ';
output.add(spaceOrEmpty + this.value + spaceOrEmpty);
}
}
Combinator.prototype = new Node();
Combinator.prototype.genCSS = function(context, output) {
const spaceOrEmpty = (context.compress || _noSpaceCombinators[this.value]) ? '' : ' ';
output.add(spaceOrEmpty + this.value + spaceOrEmpty);
};
Combinator.prototype.type = 'Combinator';

View File

@@ -1,29 +1,27 @@
import Node from './node';
import getDebugInfo from './debug-info';
class Comment extends Node {
constructor(value, isLineComment, index, currentFileInfo) {
super();
const Comment = function(value, isLineComment, index, currentFileInfo) {
this.value = value;
this.isLineComment = isLineComment;
this._index = index;
this._fileInfo = currentFileInfo;
this.allowRoot = true;
};
this.value = value;
this.isLineComment = isLineComment;
this._index = index;
this._fileInfo = currentFileInfo;
this.allowRoot = true;
}
Comment.prototype = new Node();
genCSS(context, output) {
if (this.debugInfo) {
output.add(getDebugInfo(context, this), this.fileInfo(), this.getIndex());
}
output.add(this.value);
Comment.prototype.genCSS = function(context, output) {
if (this.debugInfo) {
output.add(getDebugInfo(context, this), this.fileInfo(), this.getIndex());
}
output.add(this.value);
};
isSilent(context) {
const isCompressed = context.compress && this.value[2] !== '!';
return this.isLineComment || isCompressed;
}
}
Comment.prototype.isSilent = function(context) {
const isCompressed = context.compress && this.value[2] !== '!';
return this.isLineComment || isCompressed;
};
Comment.prototype.type = 'Comment';
export default Comment;

View File

@@ -1,43 +1,41 @@
import Node from './node';
class Condition extends Node {
constructor(op, l, r, i, negate) {
super();
const Condition = function(op, l, r, i, negate) {
this.op = op.trim();
this.lvalue = l;
this.rvalue = r;
this._index = i;
this.negate = negate;
};
this.op = op.trim();
this.lvalue = l;
this.rvalue = r;
this._index = i;
this.negate = negate;
}
Condition.prototype = new Node();
accept(visitor) {
this.lvalue = visitor.visit(this.lvalue);
this.rvalue = visitor.visit(this.rvalue);
}
eval(context) {
const result = ((op, a, b) => {
switch (op) {
case 'and': return a && b;
case 'or': return a || b;
default:
switch (Node.compare(a, b)) {
case -1:
return op === '<' || op === '=<' || op === '<=';
case 0:
return op === '=' || op === '>=' || op === '=<' || op === '<=';
case 1:
return op === '>' || op === '>=';
default:
return false;
}
}
})(this.op, this.lvalue.eval(context), this.rvalue.eval(context));
return this.negate ? !result : result;
}
Condition.prototype.accept = function(visitor) {
this.lvalue = visitor.visit(this.lvalue);
this.rvalue = visitor.visit(this.rvalue);
}
Condition.prototype.eval = function(context) {
const result = ((op, a, b) => {
switch (op) {
case 'and': return a && b;
case 'or': return a || b;
default:
switch (Node.compare(a, b)) {
case -1:
return op === '<' || op === '=<' || op === '<=';
case 0:
return op === '=' || op === '>=' || op === '=<' || op === '<=';
case 1:
return op === '>' || op === '>=';
default:
return false;
}
}
})(this.op, this.lvalue.eval(context), this.rvalue.eval(context));
return this.negate ? !result : result;
};
Condition.prototype.type = 'Condition';
export default Condition;

View File

@@ -6,100 +6,98 @@ import * as Constants from '../constants';
const MATH = Constants.Math;
class Declaration extends Node {
constructor(name, value, important, merge, index, currentFileInfo, inline, variable) {
super();
const Declaration = function(name, value, important, merge, index, currentFileInfo, inline, variable) {
this.name = name;
this.value = (value instanceof Node) ? value : new Value([value ? new Anonymous(value) : null]);
this.important = important ? ` ${important.trim()}` : '';
this.merge = merge;
this._index = index;
this._fileInfo = currentFileInfo;
this.inline = inline || false;
this.variable = (variable !== undefined) ? variable
: (name.charAt && (name.charAt(0) === '@'));
this.allowRoot = true;
this.setParent(this.value, this);
};
this.name = name;
this.value = (value instanceof Node) ? value : new Value([value ? new Anonymous(value) : null]);
this.important = important ? ` ${important.trim()}` : '';
this.merge = merge;
this._index = index;
this._fileInfo = currentFileInfo;
this.inline = inline || false;
this.variable = (variable !== undefined) ? variable
: (name.charAt && (name.charAt(0) === '@'));
this.allowRoot = true;
this.setParent(this.value, this);
Declaration.prototype = new Node();
Declaration.prototype.genCSS = function(context, output) {
output.add(this.name + (context.compress ? ':' : ': '), this.fileInfo(), this.getIndex());
try {
this.value.genCSS(context, output);
}
genCSS(context, output) {
output.add(this.name + (context.compress ? ':' : ': '), this.fileInfo(), this.getIndex());
try {
this.value.genCSS(context, output);
}
catch (e) {
e.index = this._index;
e.filename = this._fileInfo.filename;
throw e;
}
output.add(this.important + ((this.inline || (context.lastRule && context.compress)) ? '' : ';'), this._fileInfo, this._index);
}
eval(context) {
let mathBypass = false;
let prevMath;
let name = this.name;
let evaldValue;
let variable = this.variable;
if (typeof name !== 'string') {
// expand 'primitive' name directly to get
// things faster (~10% for benchmark.less):
name = (name.length === 1) && (name[0] instanceof Keyword) ?
name[0].value : evalName(context, name);
variable = false; // never treat expanded interpolation as new variable name
}
// @todo remove when parens-division is default
if (name === 'font' && context.math === MATH.ALWAYS) {
mathBypass = true;
prevMath = context.math;
context.math = MATH.PARENS_DIVISION;
}
try {
context.importantScope.push({});
evaldValue = this.value.eval(context);
if (!this.variable && evaldValue.type === 'DetachedRuleset') {
throw { message: 'Rulesets cannot be evaluated on a property.',
index: this.getIndex(), filename: this.fileInfo().filename };
}
let important = this.important;
const importantResult = context.importantScope.pop();
if (!important && importantResult.important) {
important = importantResult.important;
}
return new Declaration(name,
evaldValue,
important,
this.merge,
this.getIndex(), this.fileInfo(), this.inline,
variable);
}
catch (e) {
if (typeof e.index !== 'number') {
e.index = this.getIndex();
e.filename = this.fileInfo().filename;
}
throw e;
}
finally {
if (mathBypass) {
context.math = prevMath;
}
}
}
makeImportant() {
return new Declaration(this.name,
this.value,
'!important',
this.merge,
this.getIndex(), this.fileInfo(), this.inline);
catch (e) {
e.index = this._index;
e.filename = this._fileInfo.filename;
throw e;
}
output.add(this.important + ((this.inline || (context.lastRule && context.compress)) ? '' : ';'), this._fileInfo, this._index);
}
Declaration.prototype.eval = function(context) {
let mathBypass = false;
let prevMath;
let name = this.name;
let evaldValue;
let variable = this.variable;
if (typeof name !== 'string') {
// expand 'primitive' name directly to get
// things faster (~10% for benchmark.less):
name = (name.length === 1) && (name[0] instanceof Keyword) ?
name[0].value : evalName(context, name);
variable = false; // never treat expanded interpolation as new variable name
}
// @todo remove when parens-division is default
if (name === 'font' && context.math === MATH.ALWAYS) {
mathBypass = true;
prevMath = context.math;
context.math = MATH.PARENS_DIVISION;
}
try {
context.importantScope.push({});
evaldValue = this.value.eval(context);
if (!this.variable && evaldValue.type === 'DetachedRuleset') {
throw { message: 'Rulesets cannot be evaluated on a property.',
index: this.getIndex(), filename: this.fileInfo().filename };
}
let important = this.important;
const importantResult = context.importantScope.pop();
if (!important && importantResult.important) {
important = importantResult.important;
}
return new Declaration(name,
evaldValue,
important,
this.merge,
this.getIndex(), this.fileInfo(), this.inline,
variable);
}
catch (e) {
if (typeof e.index !== 'number') {
e.index = this.getIndex();
e.filename = this.fileInfo().filename;
}
throw e;
}
finally {
if (mathBypass) {
context.math = prevMath;
}
}
};
Declaration.prototype.makeImportant = function() {
return new Declaration(this.name,
this.value,
'!important',
this.merge,
this.getIndex(), this.fileInfo(), this.inline);
};
function evalName(context, name) {
let value = '';
let i;

View File

@@ -2,28 +2,26 @@ import Node from './node';
import contexts from '../contexts';
import * as utils from '../utils';
class DetachedRuleset extends Node {
constructor(ruleset, frames) {
super();
const DetachedRuleset = function(ruleset, frames) {
this.ruleset = ruleset;
this.frames = frames;
this.setParent(this.ruleset, this);
};
this.ruleset = ruleset;
this.frames = frames;
this.setParent(this.ruleset, this);
}
DetachedRuleset.prototype = new Node();
accept(visitor) {
this.ruleset = visitor.visit(this.ruleset);
}
DetachedRuleset.prototype.accept = function(visitor) {
this.ruleset = visitor.visit(this.ruleset);
};
eval(context) {
const frames = this.frames || utils.copyArray(context.frames);
return new DetachedRuleset(this.ruleset, frames);
}
DetachedRuleset.prototype.eval = function(context) {
const frames = this.frames || utils.copyArray(context.frames);
return new DetachedRuleset(this.ruleset, frames);
};
callEval(context) {
return this.ruleset.eval(this.frames ? new contexts.Eval(context, this.frames.concat(context.frames)) : context);
}
}
DetachedRuleset.prototype.callEval = function(context) {
return this.ruleset.eval(this.frames ? new contexts.Eval(context, this.frames.concat(context.frames)) : context);
};
DetachedRuleset.prototype.type = 'DetachedRuleset';
DetachedRuleset.prototype.evalFirst = true;

View File

@@ -6,174 +6,172 @@ import Color from './color';
//
// A number with a unit
//
class Dimension extends Node {
constructor(value, unit) {
super();
const Dimension = function(value, unit) {
this.value = parseFloat(value);
if (isNaN(this.value)) {
throw new Error('Dimension is not a number.');
}
this.unit = (unit && unit instanceof Unit) ? unit :
new Unit(unit ? [unit] : undefined);
this.setParent(this.unit, this);
};
this.value = parseFloat(value);
if (isNaN(this.value)) {
throw new Error('Dimension is not a number.');
}
this.unit = (unit && unit instanceof Unit) ? unit :
new Unit(unit ? [unit] : undefined);
this.setParent(this.unit, this);
Dimension.prototype = new Node();
Dimension.prototype.accept = function(visitor) {
this.unit = visitor.visit(this.unit);
};
Dimension.prototype.eval = function(context) {
return this;
};
Dimension.prototype.toColor = function() {
return new Color([this.value, this.value, this.value]);
};
Dimension.prototype.genCSS = function(context, output) {
if ((context && context.strictUnits) && !this.unit.isSingular()) {
throw new Error(`Multiple units in dimension. Correct the units or use the unit function. Bad unit: ${this.unit.toString()}`);
}
accept(visitor) {
this.unit = visitor.visit(this.unit);
const value = this.fround(context, this.value);
let strValue = String(value);
if (value !== 0 && value < 0.000001 && value > -0.000001) {
// would be output 1e-6 etc.
strValue = value.toFixed(20).replace(/0+$/, '');
}
eval(context) {
return this;
}
toColor() {
return new Color([this.value, this.value, this.value]);
}
genCSS(context, output) {
if ((context && context.strictUnits) && !this.unit.isSingular()) {
throw new Error(`Multiple units in dimension. Correct the units or use the unit function. Bad unit: ${this.unit.toString()}`);
if (context && context.compress) {
// Zero values doesn't need a unit
if (value === 0 && this.unit.isLength()) {
output.add(strValue);
return;
}
const value = this.fround(context, this.value);
let strValue = String(value);
if (value !== 0 && value < 0.000001 && value > -0.000001) {
// would be output 1e-6 etc.
strValue = value.toFixed(20).replace(/0+$/, '');
// Float values doesn't need a leading zero
if (value > 0 && value < 1) {
strValue = (strValue).substr(1);
}
}
if (context && context.compress) {
// Zero values doesn't need a unit
if (value === 0 && this.unit.isLength()) {
output.add(strValue);
return;
output.add(strValue);
this.unit.genCSS(context, output);
};
// In an operation between two Dimensions,
// we default to the first Dimension's unit,
// so `1px + 2` will yield `3px`.
Dimension.prototype.operate = function(context, op, other) {
/* jshint noempty:false */
let value = this._operate(context, op, this.value, other.value);
let unit = this.unit.clone();
if (op === '+' || op === '-') {
if (unit.numerator.length === 0 && unit.denominator.length === 0) {
unit = other.unit.clone();
if (this.unit.backupUnit) {
unit.backupUnit = this.unit.backupUnit;
}
} else if (other.unit.numerator.length === 0 && unit.denominator.length === 0) {
// do nothing
} else {
other = other.convertTo(this.unit.usedUnits());
if (context.strictUnits && other.unit.toString() !== unit.toString()) {
throw new Error(`Incompatible units. Change the units or use the unit function. ` +
`Bad units: '${unit.toString()}' and '${other.unit.toString()}'.`);
}
// Float values doesn't need a leading zero
if (value > 0 && value < 1) {
strValue = (strValue).substr(1);
}
value = this._operate(context, op, this.value, other.value);
}
} else if (op === '*') {
unit.numerator = unit.numerator.concat(other.unit.numerator).sort();
unit.denominator = unit.denominator.concat(other.unit.denominator).sort();
unit.cancel();
} else if (op === '/') {
unit.numerator = unit.numerator.concat(other.unit.denominator).sort();
unit.denominator = unit.denominator.concat(other.unit.numerator).sort();
unit.cancel();
}
return new Dimension(value, unit);
};
output.add(strValue);
this.unit.genCSS(context, output);
Dimension.prototype.compare = function(other) {
let a;
let b;
if (!(other instanceof Dimension)) {
return undefined;
}
// In an operation between two Dimensions,
// we default to the first Dimension's unit,
// so `1px + 2` will yield `3px`.
operate(context, op, other) {
/* jshint noempty:false */
let value = this._operate(context, op, this.value, other.value);
let unit = this.unit.clone();
if (op === '+' || op === '-') {
if (unit.numerator.length === 0 && unit.denominator.length === 0) {
unit = other.unit.clone();
if (this.unit.backupUnit) {
unit.backupUnit = this.unit.backupUnit;
}
} else if (other.unit.numerator.length === 0 && unit.denominator.length === 0) {
// do nothing
} else {
other = other.convertTo(this.unit.usedUnits());
if (context.strictUnits && other.unit.toString() !== unit.toString()) {
throw new Error(`Incompatible units. Change the units or use the unit function. ` +
`Bad units: '${unit.toString()}' and '${other.unit.toString()}'.`);
}
value = this._operate(context, op, this.value, other.value);
}
} else if (op === '*') {
unit.numerator = unit.numerator.concat(other.unit.numerator).sort();
unit.denominator = unit.denominator.concat(other.unit.denominator).sort();
unit.cancel();
} else if (op === '/') {
unit.numerator = unit.numerator.concat(other.unit.denominator).sort();
unit.denominator = unit.denominator.concat(other.unit.numerator).sort();
unit.cancel();
}
return new Dimension(value, unit);
}
compare(other) {
let a;
let b;
if (!(other instanceof Dimension)) {
if (this.unit.isEmpty() || other.unit.isEmpty()) {
a = this;
b = other;
} else {
a = this.unify();
b = other.unify();
if (a.unit.compare(b.unit) !== 0) {
return undefined;
}
if (this.unit.isEmpty() || other.unit.isEmpty()) {
a = this;
b = other;
} else {
a = this.unify();
b = other.unify();
if (a.unit.compare(b.unit) !== 0) {
return undefined;
}
}
return Node.numericCompare(a.value, b.value);
}
unify() {
return this.convertTo({ length: 'px', duration: 's', angle: 'rad' });
}
return Node.numericCompare(a.value, b.value);
};
convertTo(conversions) {
let value = this.value;
const unit = this.unit.clone();
let i;
let groupName;
let group;
let targetUnit;
let derivedConversions = {};
let applyUnit;
Dimension.prototype.unify = function() {
return this.convertTo({ length: 'px', duration: 's', angle: 'rad' });
};
if (typeof conversions === 'string') {
for (i in unitConversions) {
if (unitConversions[i].hasOwnProperty(conversions)) {
derivedConversions = {};
derivedConversions[i] = conversions;
}
}
conversions = derivedConversions;
}
applyUnit = (atomicUnit, denominator) => {
/* jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit)) {
if (denominator) {
value = value / (group[atomicUnit] / group[targetUnit]);
} else {
value = value * (group[atomicUnit] / group[targetUnit]);
}
Dimension.prototype.convertTo = function(conversions) {
let value = this.value;
const unit = this.unit.clone();
let i;
let groupName;
let group;
let targetUnit;
let derivedConversions = {};
let applyUnit;
return targetUnit;
}
return atomicUnit;
};
for (groupName in conversions) {
if (conversions.hasOwnProperty(groupName)) {
targetUnit = conversions[groupName];
group = unitConversions[groupName];
unit.map(applyUnit);
if (typeof conversions === 'string') {
for (i in unitConversions) {
if (unitConversions[i].hasOwnProperty(conversions)) {
derivedConversions = {};
derivedConversions[i] = conversions;
}
}
unit.cancel();
return new Dimension(value, unit);
conversions = derivedConversions;
}
}
applyUnit = (atomicUnit, denominator) => {
/* jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit)) {
if (denominator) {
value = value / (group[atomicUnit] / group[targetUnit]);
} else {
value = value * (group[atomicUnit] / group[targetUnit]);
}
return targetUnit;
}
return atomicUnit;
};
for (groupName in conversions) {
if (conversions.hasOwnProperty(groupName)) {
targetUnit = conversions[groupName];
group = unitConversions[groupName];
unit.map(applyUnit);
}
}
unit.cancel();
return new Dimension(value, unit);
};
Dimension.prototype.type = 'Dimension';
export default Dimension;

View File

@@ -2,72 +2,70 @@ import Node from './node';
import Paren from './paren';
import Combinator from './combinator';
class Element extends Node {
constructor(combinator, value, isVariable, index, currentFileInfo, visibilityInfo) {
super();
const Element = function(combinator, value, isVariable, index, currentFileInfo, visibilityInfo) {
this.combinator = combinator instanceof Combinator ?
combinator : new Combinator(combinator);
this.combinator = combinator instanceof Combinator ?
combinator : new Combinator(combinator);
if (typeof value === 'string') {
this.value = value.trim();
} else if (value) {
this.value = value;
} else {
this.value = '';
}
this.isVariable = isVariable;
this._index = index;
this._fileInfo = currentFileInfo;
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.combinator, this);
if (typeof value === 'string') {
this.value = value.trim();
} else if (value) {
this.value = value;
} else {
this.value = '';
}
this.isVariable = isVariable;
this._index = index;
this._fileInfo = currentFileInfo;
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.combinator, this);
};
accept(visitor) {
const value = this.value;
this.combinator = visitor.visit(this.combinator);
if (typeof value === 'object') {
this.value = visitor.visit(value);
}
}
Element.prototype = new Node();
eval(context) {
return new Element(this.combinator,
this.value.eval ? this.value.eval(context) : this.value,
this.isVariable,
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
Element.prototype.accept = function(visitor) {
const value = this.value;
this.combinator = visitor.visit(this.combinator);
if (typeof value === 'object') {
this.value = visitor.visit(value);
}
};
clone() {
return new Element(this.combinator,
this.value,
this.isVariable,
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
}
Element.prototype.eval = function(context) {
return new Element(this.combinator,
this.value.eval ? this.value.eval(context) : this.value,
this.isVariable,
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
};
genCSS(context, output) {
output.add(this.toCSS(context), this.fileInfo(), this.getIndex());
}
Element.prototype.clone = function() {
return new Element(this.combinator,
this.value,
this.isVariable,
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
};
toCSS(context = {}) {
let value = this.value;
const firstSelector = context.firstSelector;
if (value instanceof Paren) {
// selector in parens should not be affected by outer selector
// flags (breaks only interpolated selectors - see #1973)
context.firstSelector = true;
}
value = value.toCSS ? value.toCSS(context) : value;
context.firstSelector = firstSelector;
if (value === '' && this.combinator.value.charAt(0) === '&') {
return '';
} else {
return this.combinator.toCSS(context) + value;
}
Element.prototype.genCSS = function(context, output) {
output.add(this.toCSS(context), this.fileInfo(), this.getIndex());
};
Element.prototype.toCSS = function(context = {}) {
let value = this.value;
const firstSelector = context.firstSelector;
if (value instanceof Paren) {
// selector in parens should not be affected by outer selector
// flags (breaks only interpolated selectors - see #1973)
context.firstSelector = true;
}
}
value = value.toCSS ? value.toCSS(context) : value;
context.firstSelector = firstSelector;
if (value === '' && this.combinator.value.charAt(0) === '&') {
return '';
} else {
return this.combinator.toCSS(context) + value;
}
};
Element.prototype.type = 'Element';
export default Element;

View File

@@ -5,70 +5,68 @@ import Dimension from './dimension';
import * as Constants from '../constants';
const MATH = Constants.Math;
class Expression extends Node {
constructor(value, noSpacing) {
super();
this.value = value;
this.noSpacing = noSpacing;
if (!value) {
throw new Error('Expression requires an array parameter');
}
const Expression = function(value, noSpacing) {
this.value = value;
this.noSpacing = noSpacing;
if (!value) {
throw new Error('Expression requires an array parameter');
}
};
accept(visitor) {
this.value = visitor.visitArray(this.value);
Expression.prototype = new Node();
Expression.prototype.accept = function(visitor) {
this.value = visitor.visitArray(this.value);
};
Expression.prototype.eval = function(context) {
let returnValue;
const mathOn = context.isMathOn();
const inParenthesis = this.parens &&
(context.math !== MATH.STRICT_LEGACY || !this.parensInOp);
let doubleParen = false;
if (inParenthesis) {
context.inParenthesis();
}
eval(context) {
let returnValue;
const mathOn = context.isMathOn();
const inParenthesis = this.parens &&
(context.math !== MATH.STRICT_LEGACY || !this.parensInOp);
let doubleParen = false;
if (inParenthesis) {
context.inParenthesis();
}
if (this.value.length > 1) {
returnValue = new Expression(this.value.map(e => {
if (!e.eval) {
return e;
}
return e.eval(context);
}), this.noSpacing);
} else if (this.value.length === 1) {
if (this.value[0].parens && !this.value[0].parensInOp && !context.inCalc) {
doubleParen = true;
if (this.value.length > 1) {
returnValue = new Expression(this.value.map(e => {
if (!e.eval) {
return e;
}
returnValue = this.value[0].eval(context);
} else {
returnValue = this;
return e.eval(context);
}), this.noSpacing);
} else if (this.value.length === 1) {
if (this.value[0].parens && !this.value[0].parensInOp && !context.inCalc) {
doubleParen = true;
}
if (inParenthesis) {
context.outOfParenthesis();
}
if (this.parens && this.parensInOp && !mathOn && !doubleParen
&& (!(returnValue instanceof Dimension))) {
returnValue = new Paren(returnValue);
}
return returnValue;
returnValue = this.value[0].eval(context);
} else {
returnValue = this;
}
if (inParenthesis) {
context.outOfParenthesis();
}
if (this.parens && this.parensInOp && !mathOn && !doubleParen
&& (!(returnValue instanceof Dimension))) {
returnValue = new Paren(returnValue);
}
return returnValue;
};
genCSS(context, output) {
for (let i = 0; i < this.value.length; i++) {
this.value[i].genCSS(context, output);
if (!this.noSpacing && i + 1 < this.value.length) {
output.add(' ');
}
Expression.prototype.genCSS = function(context, output) {
for (let i = 0; i < this.value.length; i++) {
this.value[i].genCSS(context, output);
if (!this.noSpacing && i + 1 < this.value.length) {
output.add(' ');
}
}
};
throwAwayComments() {
this.value = this.value.filter(v => !(v instanceof Comment));
}
}
Expression.prototype.throwAwayComments = function() {
this.value = this.value.filter(v => !(v instanceof Comment));
};
Expression.prototype.type = 'Expression';
export default Expression;

View File

@@ -1,66 +1,66 @@
import Node from './node';
import Selector from './selector';
class Extend extends Node {
constructor(selector, option, index, currentFileInfo, visibilityInfo) {
super();
const Extend = function(selector, option, index, currentFileInfo, visibilityInfo) {
this.selector = selector;
this.option = option;
this.object_id = Extend.next_id++;
this.parent_ids = [this.object_id];
this._index = index;
this._fileInfo = currentFileInfo;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
this.selector = selector;
this.option = option;
this.object_id = Extend.next_id++;
this.parent_ids = [this.object_id];
this._index = index;
this._fileInfo = currentFileInfo;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
switch (option) {
case 'all':
this.allowBefore = true;
this.allowAfter = true;
break;
default:
this.allowBefore = false;
this.allowAfter = false;
break;
}
this.setParent(this.selector, this);
};
switch (option) {
case 'all':
this.allowBefore = true;
this.allowAfter = true;
break;
default:
this.allowBefore = false;
this.allowAfter = false;
break;
Extend.prototype = new Node();
Extend.prototype.accept = function(visitor) {
this.selector = visitor.visit(this.selector);
};
Extend.prototype.eval = function(context) {
return new Extend(this.selector.eval(context), this.option, this.getIndex(), this.fileInfo(), this.visibilityInfo());
};
Extend.prototype.clone = function(context) {
return new Extend(this.selector, this.option, this.getIndex(), this.fileInfo(), this.visibilityInfo());
};
// it concatenates (joins) all selectors in selector array
Extend.prototype.findSelfSelectors = function(selectors) {
let selfElements = [];
let i;
let selectorElements;
for (i = 0; i < selectors.length; i++) {
selectorElements = selectors[i].elements;
// duplicate the logic in genCSS function inside the selector node.
// future TODO - move both logics into the selector joiner visitor
if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === '') {
selectorElements[0].combinator.value = ' ';
}
this.setParent(this.selector, this);
selfElements = selfElements.concat(selectors[i].elements);
}
accept(visitor) {
this.selector = visitor.visit(this.selector);
}
eval(context) {
return new Extend(this.selector.eval(context), this.option, this.getIndex(), this.fileInfo(), this.visibilityInfo());
}
clone(context) {
return new Extend(this.selector, this.option, this.getIndex(), this.fileInfo(), this.visibilityInfo());
}
// it concatenates (joins) all selectors in selector array
findSelfSelectors(selectors) {
let selfElements = [];
let i;
let selectorElements;
for (i = 0; i < selectors.length; i++) {
selectorElements = selectors[i].elements;
// duplicate the logic in genCSS function inside the selector node.
// future TODO - move both logics into the selector joiner visitor
if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === '') {
selectorElements[0].combinator.value = ' ';
}
selfElements = selfElements.concat(selectors[i].elements);
}
this.selfSelectors = [new Selector(selfElements)];
this.selfSelectors[0].copyVisibilityInfo(this.visibilityInfo());
}
}
this.selfSelectors = [new Selector(selfElements)];
this.selfSelectors[0].copyVisibilityInfo(this.visibilityInfo());
};
/**
* Used with the extend visitor
*/
Extend.next_id = 0;
Extend.prototype.type = 'Extend';
export default Extend;

View File

@@ -19,168 +19,166 @@ import LessError from '../less-error';
// `import,push`, we also pass it a callback, which it'll call once
// the file has been fetched, and parsed.
//
class Import extends Node {
constructor(path, features, options, index, currentFileInfo, visibilityInfo) {
super();
const Import = function(path, features, options, index, currentFileInfo, visibilityInfo) {
this.options = options;
this._index = index;
this._fileInfo = currentFileInfo;
this.path = path;
this.features = features;
this.allowRoot = true;
this.options = options;
this._index = index;
this._fileInfo = currentFileInfo;
this.path = path;
this.features = features;
this.allowRoot = true;
if (this.options.less !== undefined || this.options.inline) {
this.css = !this.options.less || this.options.inline;
} else {
const pathValue = this.getPath();
if (pathValue && /[#\.\&\?]css([\?;].*)?$/.test(pathValue)) {
this.css = true;
}
}
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.features, this);
this.setParent(this.path, this);
}
accept(visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
}
this.path = visitor.visit(this.path);
if (!this.options.isPlugin && !this.options.inline && this.root) {
this.root = visitor.visit(this.root);
}
}
genCSS(context, output) {
if (this.css && this.path._fileInfo.reference === undefined) {
output.add('@import ', this._fileInfo, this._index);
this.path.genCSS(context, output);
if (this.features) {
output.add(' ');
this.features.genCSS(context, output);
}
output.add(';');
}
}
getPath() {
return (this.path instanceof URL) ?
this.path.value.value : this.path.value;
}
isVariableImport() {
let path = this.path;
if (path instanceof URL) {
path = path.value;
}
if (path instanceof Quoted) {
return path.containsVariables();
}
return true;
}
evalForImport(context) {
let path = this.path;
if (path instanceof URL) {
path = path.value;
}
return new Import(path.eval(context), this.features, this.options, this._index, this._fileInfo, this.visibilityInfo());
}
evalPath(context) {
const path = this.path.eval(context);
const fileInfo = this._fileInfo;
if (!(path instanceof URL)) {
// Add the rootpath if the URL requires a rewrite
const pathValue = path.value;
if (fileInfo &&
pathValue &&
context.pathRequiresRewrite(pathValue)) {
path.value = context.rewritePath(pathValue, fileInfo.rootpath);
} else {
path.value = context.normalizePath(path.value);
}
}
return path;
}
eval(context) {
const result = this.doEval(context);
if (this.options.reference || this.blocksVisibility()) {
if (result.length || result.length === 0) {
result.forEach(node => {
node.addVisibilityBlock();
}
);
} else {
result.addVisibilityBlock();
}
}
return result;
}
doEval(context) {
let ruleset;
let registry;
const features = this.features && this.features.eval(context);
if (this.options.isPlugin) {
if (this.root && this.root.eval) {
try {
this.root.eval(context);
}
catch (e) {
e.message = 'Plugin error during evaluation';
throw new LessError(e, this.root.imports, this.root.filename);
}
}
registry = context.frames[0] && context.frames[0].functionRegistry;
if ( registry && this.root && this.root.functions ) {
registry.addMultiple( this.root.functions );
}
return [];
}
if (this.skip) {
if (typeof this.skip === 'function') {
this.skip = this.skip();
}
if (this.skip) {
return [];
}
}
if (this.options.inline) {
const contents = new Anonymous(this.root, 0,
{
filename: this.importedFilename,
reference: this.path._fileInfo && this.path._fileInfo.reference
}, true, true);
return this.features ? new Media([contents], this.features.value) : [contents];
} else if (this.css) {
const newImport = new Import(this.evalPath(context), features, this.options, this._index);
if (!newImport.css && this.error) {
throw this.error;
}
return newImport;
} else if (this.root) {
ruleset = new Ruleset(null, utils.copyArray(this.root.rules));
ruleset.evalImports(context);
return this.features ? new Media(ruleset.rules, this.features.value) : ruleset.rules;
} else {
return [];
if (this.options.less !== undefined || this.options.inline) {
this.css = !this.options.less || this.options.inline;
} else {
const pathValue = this.getPath();
if (pathValue && /[#\.\&\?]css([\?;].*)?$/.test(pathValue)) {
this.css = true;
}
}
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.features, this);
this.setParent(this.path, this);
}
Import.prototype = new Node();
Import.prototype.accept = function(visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
}
this.path = visitor.visit(this.path);
if (!this.options.isPlugin && !this.options.inline && this.root) {
this.root = visitor.visit(this.root);
}
};
Import.prototype.genCSS = function(context, output) {
if (this.css && this.path._fileInfo.reference === undefined) {
output.add('@import ', this._fileInfo, this._index);
this.path.genCSS(context, output);
if (this.features) {
output.add(' ');
this.features.genCSS(context, output);
}
output.add(';');
}
};
Import.prototype.getPath = function() {
return (this.path instanceof URL) ?
this.path.value.value : this.path.value;
};
Import.prototype.isVariableImport = function() {
let path = this.path;
if (path instanceof URL) {
path = path.value;
}
if (path instanceof Quoted) {
return path.containsVariables();
}
return true;
};
Import.prototype.evalForImport = function(context) {
let path = this.path;
if (path instanceof URL) {
path = path.value;
}
return new Import(path.eval(context), this.features, this.options, this._index, this._fileInfo, this.visibilityInfo());
};
Import.prototype.evalPath = function(context) {
const path = this.path.eval(context);
const fileInfo = this._fileInfo;
if (!(path instanceof URL)) {
// Add the rootpath if the URL requires a rewrite
const pathValue = path.value;
if (fileInfo &&
pathValue &&
context.pathRequiresRewrite(pathValue)) {
path.value = context.rewritePath(pathValue, fileInfo.rootpath);
} else {
path.value = context.normalizePath(path.value);
}
}
return path;
};
Import.prototype.eval = function(context) {
const result = this.doEval(context);
if (this.options.reference || this.blocksVisibility()) {
if (result.length || result.length === 0) {
result.forEach(node => {
node.addVisibilityBlock();
}
);
} else {
result.addVisibilityBlock();
}
}
return result;
};
Import.prototype.doEval = function(context) {
let ruleset;
let registry;
const features = this.features && this.features.eval(context);
if (this.options.isPlugin) {
if (this.root && this.root.eval) {
try {
this.root.eval(context);
}
catch (e) {
e.message = 'Plugin error during evaluation';
throw new LessError(e, this.root.imports, this.root.filename);
}
}
registry = context.frames[0] && context.frames[0].functionRegistry;
if ( registry && this.root && this.root.functions ) {
registry.addMultiple( this.root.functions );
}
return [];
}
if (this.skip) {
if (typeof this.skip === 'function') {
this.skip = this.skip();
}
if (this.skip) {
return [];
}
}
if (this.options.inline) {
const contents = new Anonymous(this.root, 0,
{
filename: this.importedFilename,
reference: this.path._fileInfo && this.path._fileInfo.reference
}, true, true);
return this.features ? new Media([contents], this.features.value) : [contents];
} else if (this.css) {
const newImport = new Import(this.evalPath(context), features, this.options, this._index);
if (!newImport.css && this.error) {
throw this.error;
}
return newImport;
} else if (this.root) {
ruleset = new Ruleset(null, utils.copyArray(this.root.rules));
ruleset.evalImports(context);
return this.features ? new Media(ruleset.rules, this.features.value) : ruleset.rules;
} else {
return [];
}
};
Import.prototype.type = 'Import';
export default Import;

View File

@@ -1,5 +1,3 @@
const tree = Object.create(null);
import Node from './node';
import Color from './color';
import AtRule from './atrule';

View File

@@ -3,31 +3,29 @@ import Dimension from './dimension';
import Quoted from './quoted';
import Anonymous from './anonymous';
class JavaScript extends JsEvalNode {
constructor(string, escaped, index, currentFileInfo) {
super();
const JavaScript = function(string, escaped, index, currentFileInfo) {
this.escaped = escaped;
this.expression = string;
this._index = index;
this._fileInfo = currentFileInfo;
};
this.escaped = escaped;
this.expression = string;
this._index = index;
this._fileInfo = currentFileInfo;
JavaScript.prototype = new JsEvalNode();
JavaScript.prototype.eval = function(context) {
const result = this.evaluateJavaScript(this.expression, context);
const type = typeof result;
if (type === 'number' && !isNaN(result)) {
return new Dimension(result);
} else if (type === 'string') {
return new Quoted(`"${result}"`, result, this.escaped, this._index);
} else if (Array.isArray(result)) {
return new Anonymous(result.join(', '));
} else {
return new Anonymous(result);
}
eval(context) {
const result = this.evaluateJavaScript(this.expression, context);
const type = typeof result;
if (type === 'number' && !isNaN(result)) {
return new Dimension(result);
} else if (type === 'string') {
return new Quoted(`"${result}"`, result, this.escaped, this._index);
} else if (Array.isArray(result)) {
return new Anonymous(result.join(', '));
} else {
return new Anonymous(result);
}
}
}
};
JavaScript.prototype.type = 'JavaScript';
export default JavaScript;

View File

@@ -1,58 +1,60 @@
import Node from './node';
import Variable from './variable';
class JsEvalNode extends Node {
evaluateJavaScript(expression, context) {
let result;
const that = this;
const evalContext = {};
const JsEvalNode = function() {};
if (!context.javascriptEnabled) {
throw { message: 'Inline JavaScript is not enabled. Is it set in your options?',
filename: this.fileInfo().filename,
index: this.getIndex() };
}
JsEvalNode.prototype = new Node();
expression = expression.replace(/@\{([\w-]+)\}/g, (_, name) => that.jsify(new Variable(`@${name}`, that.getIndex(), that.fileInfo()).eval(context)));
JsEvalNode.prototype.evaluateJavaScript = function(expression, context) {
let result;
const that = this;
const evalContext = {};
try {
expression = new Function(`return (${expression})`);
} catch (e) {
throw { message: `JavaScript evaluation error: ${e.message} from \`${expression}\`` ,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
const variables = context.frames[0].variables();
for (const k in variables) {
if (variables.hasOwnProperty(k)) {
/* jshint loopfunc:true */
evalContext[k.slice(1)] = {
value: variables[k].value,
toJS: function () {
return this.value.eval(context).toCSS();
}
};
}
}
try {
result = expression.call(evalContext);
} catch (e) {
throw { message: `JavaScript evaluation error: '${e.name}: ${e.message.replace(/["]/g, '\'')}'` ,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
return result;
if (!context.javascriptEnabled) {
throw { message: 'Inline JavaScript is not enabled. Is it set in your options?',
filename: this.fileInfo().filename,
index: this.getIndex() };
}
jsify(obj) {
if (Array.isArray(obj.value) && (obj.value.length > 1)) {
return `[${obj.value.map(v => v.toCSS()).join(', ')}]`;
} else {
return obj.toCSS();
expression = expression.replace(/@\{([\w-]+)\}/g, (_, name) => that.jsify(new Variable(`@${name}`, that.getIndex(), that.fileInfo()).eval(context)));
try {
expression = new Function(`return (${expression})`);
} catch (e) {
throw { message: `JavaScript evaluation error: ${e.message} from \`${expression}\`` ,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
const variables = context.frames[0].variables();
for (const k in variables) {
if (variables.hasOwnProperty(k)) {
/* jshint loopfunc:true */
evalContext[k.slice(1)] = {
value: variables[k].value,
toJS: function () {
return this.value.eval(context).toCSS();
}
};
}
}
}
try {
result = expression.call(evalContext);
} catch (e) {
throw { message: `JavaScript evaluation error: '${e.name}: ${e.message.replace(/["]/g, '\'')}'` ,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
return result;
};
JsEvalNode.prototype.jsify = function(obj) {
if (Array.isArray(obj.value) && (obj.value.length > 1)) {
return `[${obj.value.map(v => v.toCSS()).join(', ')}]`;
} else {
return obj.toCSS();
}
};
export default JsEvalNode;

View File

@@ -1,17 +1,15 @@
import Node from './node';
class Keyword extends Node {
constructor(value) {
super();
const Keyword = function(value) {
this.value = value;
};
this.value = value;
}
Keyword.prototype = new Node();
genCSS(context, output) {
if (this.value === '%') { throw { type: 'Syntax', message: 'Invalid % without number' }; }
output.add(this.value);
}
}
Keyword.prototype.genCSS = function(context, output) {
if (this.value === '%') { throw { type: 'Syntax', message: 'Invalid % without number' }; }
output.add(this.value);
};
Keyword.prototype.type = 'Keyword';

View File

@@ -6,149 +6,147 @@ import Expression from './expression';
import AtRule from './atrule';
import * as utils from '../utils';
class Media extends AtRule {
constructor(value, features, index, currentFileInfo, visibilityInfo) {
super();
const Media = function(value, features, index, currentFileInfo, visibilityInfo) {
this._index = index;
this._fileInfo = currentFileInfo;
this._index = index;
this._fileInfo = currentFileInfo;
const selectors = (new Selector([], null, null, this._index, this._fileInfo)).createEmptySelectors();
const selectors = (new Selector([], null, null, this._index, this._fileInfo)).createEmptySelectors();
this.features = new Value(features);
this.rules = [new Ruleset(selectors, value)];
this.rules[0].allowImports = true;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
this.setParent(selectors, this);
this.setParent(this.features, this);
this.setParent(this.rules, this);
}
this.features = new Value(features);
this.rules = [new Ruleset(selectors, value)];
this.rules[0].allowImports = true;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
this.setParent(selectors, this);
this.setParent(this.features, this);
this.setParent(this.rules, this);
Media.prototype = new AtRule();
Media.prototype.isRulesetLike = function() {
return true;
};
Media.prototype.accept = function(visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
}
if (this.rules) {
this.rules = visitor.visitArray(this.rules);
}
};
Media.prototype.genCSS = function(context, output) {
output.add('@media ', this._fileInfo, this._index);
this.features.genCSS(context, output);
this.outputRuleset(context, output, this.rules);
};
Media.prototype.eval = function(context) {
if (!context.mediaBlocks) {
context.mediaBlocks = [];
context.mediaPath = [];
}
isRulesetLike() {
return true;
const media = new Media(null, [], this._index, this._fileInfo, this.visibilityInfo());
if (this.debugInfo) {
this.rules[0].debugInfo = this.debugInfo;
media.debugInfo = this.debugInfo;
}
media.features = this.features.eval(context);
context.mediaPath.push(media);
context.mediaBlocks.push(media);
this.rules[0].functionRegistry = context.frames[0].functionRegistry.inherit();
context.frames.unshift(this.rules[0]);
media.rules = [this.rules[0].eval(context)];
context.frames.shift();
context.mediaPath.pop();
return context.mediaPath.length === 0 ? media.evalTop(context) :
media.evalNested(context);
};
Media.prototype.evalTop = function(context) {
let result = this;
// Render all dependent Media blocks.
if (context.mediaBlocks.length > 1) {
const selectors = (new Selector([], null, null, this.getIndex(), this.fileInfo())).createEmptySelectors();
result = new Ruleset(selectors, context.mediaBlocks);
result.multiMedia = true;
result.copyVisibilityInfo(this.visibilityInfo());
this.setParent(result, this);
}
accept(visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
}
if (this.rules) {
this.rules = visitor.visitArray(this.rules);
}
delete context.mediaBlocks;
delete context.mediaPath;
return result;
};
Media.prototype.evalNested = function(context) {
let i;
let value;
const path = context.mediaPath.concat([this]);
// Extract the media-query conditions separated with `,` (OR).
for (i = 0; i < path.length; i++) {
value = path[i].features instanceof Value ?
path[i].features.value : path[i].features;
path[i] = Array.isArray(value) ? value : [value];
}
genCSS(context, output) {
output.add('@media ', this._fileInfo, this._index);
this.features.genCSS(context, output);
this.outputRuleset(context, output, this.rules);
}
// Trace all permutations to generate the resulting media-query.
//
// (a, b and c) with nested (d, e) ->
// a and d
// a and e
// b and c and d
// b and c and e
this.features = new Value(this.permute(path).map(path => {
path = path.map(fragment => fragment.toCSS ? fragment : new Anonymous(fragment));
eval(context) {
if (!context.mediaBlocks) {
context.mediaBlocks = [];
context.mediaPath = [];
for (i = path.length - 1; i > 0; i--) {
path.splice(i, 0, new Anonymous('and'));
}
const media = new Media(null, [], this._index, this._fileInfo, this.visibilityInfo());
if (this.debugInfo) {
this.rules[0].debugInfo = this.debugInfo;
media.debugInfo = this.debugInfo;
return new Expression(path);
}));
this.setParent(this.features, this);
// Fake a tree-node that doesn't output anything.
return new Ruleset([], []);
};
Media.prototype.permute = function(arr) {
if (arr.length === 0) {
return [];
} else if (arr.length === 1) {
return arr[0];
} else {
const result = [];
const rest = this.permute(arr.slice(1));
for (let i = 0; i < rest.length; i++) {
for (let j = 0; j < arr[0].length; j++) {
result.push([arr[0][j]].concat(rest[i]));
}
}
media.features = this.features.eval(context);
context.mediaPath.push(media);
context.mediaBlocks.push(media);
this.rules[0].functionRegistry = context.frames[0].functionRegistry.inherit();
context.frames.unshift(this.rules[0]);
media.rules = [this.rules[0].eval(context)];
context.frames.shift();
context.mediaPath.pop();
return context.mediaPath.length === 0 ? media.evalTop(context) :
media.evalNested(context);
}
evalTop(context) {
let result = this;
// Render all dependent Media blocks.
if (context.mediaBlocks.length > 1) {
const selectors = (new Selector([], null, null, this.getIndex(), this.fileInfo())).createEmptySelectors();
result = new Ruleset(selectors, context.mediaBlocks);
result.multiMedia = true;
result.copyVisibilityInfo(this.visibilityInfo());
this.setParent(result, this);
}
delete context.mediaBlocks;
delete context.mediaPath;
return result;
}
};
evalNested(context) {
let i;
let value;
const path = context.mediaPath.concat([this]);
// Extract the media-query conditions separated with `,` (OR).
for (i = 0; i < path.length; i++) {
value = path[i].features instanceof Value ?
path[i].features.value : path[i].features;
path[i] = Array.isArray(value) ? value : [value];
}
// Trace all permutations to generate the resulting media-query.
//
// (a, b and c) with nested (d, e) ->
// a and d
// a and e
// b and c and d
// b and c and e
this.features = new Value(this.permute(path).map(path => {
path = path.map(fragment => fragment.toCSS ? fragment : new Anonymous(fragment));
for (i = path.length - 1; i > 0; i--) {
path.splice(i, 0, new Anonymous('and'));
}
return new Expression(path);
}));
this.setParent(this.features, this);
// Fake a tree-node that doesn't output anything.
return new Ruleset([], []);
Media.prototype.bubbleSelectors = function(selectors) {
if (!selectors) {
return;
}
permute(arr) {
if (arr.length === 0) {
return [];
} else if (arr.length === 1) {
return arr[0];
} else {
const result = [];
const rest = this.permute(arr.slice(1));
for (let i = 0; i < rest.length; i++) {
for (let j = 0; j < arr[0].length; j++) {
result.push([arr[0][j]].concat(rest[i]));
}
}
return result;
}
}
bubbleSelectors(selectors) {
if (!selectors) {
return;
}
this.rules = [new Ruleset(utils.copyArray(selectors), [this.rules[0]])];
this.setParent(this.rules, this);
}
}
this.rules = [new Ruleset(utils.copyArray(selectors), [this.rules[0]])];
this.setParent(this.rules, this);
};
Media.prototype.type = 'Media';
export default Media;

View File

@@ -3,213 +3,211 @@ import Selector from './selector';
import MixinDefinition from './mixin-definition';
import defaultFunc from '../functions/default';
class MixinCall extends Node {
constructor(elements, args, index, currentFileInfo, important) {
super();
const MixinCall = function(elements, args, index, currentFileInfo, important) {
this.selector = new Selector(elements);
this.arguments = args || [];
this._index = index;
this._fileInfo = currentFileInfo;
this.important = important;
this.allowRoot = true;
this.setParent(this.selector, this);
};
this.selector = new Selector(elements);
this.arguments = args || [];
this._index = index;
this._fileInfo = currentFileInfo;
this.important = important;
this.allowRoot = true;
this.setParent(this.selector, this);
MixinCall.prototype = new Node();
MixinCall.prototype.accept = function(visitor) {
if (this.selector) {
this.selector = visitor.visit(this.selector);
}
accept(visitor) {
if (this.selector) {
this.selector = visitor.visit(this.selector);
}
if (this.arguments.length) {
this.arguments = visitor.visitArray(this.arguments);
}
if (this.arguments.length) {
this.arguments = visitor.visitArray(this.arguments);
}
};
eval(context) {
let mixins;
let mixin;
let mixinPath;
const args = [];
let arg;
let argValue;
const rules = [];
let match = false;
let i;
let m;
MixinCall.prototype.eval = function(context) {
let mixins;
let mixin;
let mixinPath;
const args = [];
let arg;
let argValue;
const rules = [];
let match = false;
let i;
let m;
let f;
let isRecursive;
let isOneFound;
const candidates = [];
let candidate;
const conditionResult = [];
let defaultResult;
const defFalseEitherCase = -1;
const defNone = 0;
const defTrue = 1;
const defFalse = 2;
let count;
let originalRuleset;
let noArgumentsFilter;
this.selector = this.selector.eval(context);
function calcDefGroup(mixin, mixinPath) {
let f;
let isRecursive;
let isOneFound;
const candidates = [];
let candidate;
const conditionResult = [];
let defaultResult;
const defFalseEitherCase = -1;
const defNone = 0;
const defTrue = 1;
const defFalse = 2;
let count;
let originalRuleset;
let noArgumentsFilter;
let p;
let namespace;
this.selector = this.selector.eval(context);
function calcDefGroup(mixin, mixinPath) {
let f;
let p;
let 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);
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 (conditionResult[0] || conditionResult[1]) {
if (conditionResult[0] != conditionResult[1]) {
return conditionResult[1] ?
defTrue : defFalse;
}
return defNone;
}
return defFalseEitherCase;
}
for (i = 0; i < this.arguments.length; i++) {
arg = this.arguments[i];
argValue = arg.value.eval(context);
if (arg.expand && Array.isArray(argValue.value)) {
argValue = argValue.value;
for (m = 0; m < argValue.length; m++) {
args.push({value: argValue[m]});
}
} else {
args.push({name: arg.name, value: argValue});
if (mixin.matchCondition) {
conditionResult[f] = conditionResult[f] && mixin.matchCondition(args, context);
}
}
noArgumentsFilter = rule => 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, 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.getIndex(), filename: this.fileInfo().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, null, originalRuleset.visibilityInfo());
mixin.originalRuleset = originalRuleset;
}
const newRules = mixin.evalCall(context, args, this.important).rules;
this._setVisibilityToReplacement(newRules);
Array.prototype.push.apply(rules, newRules);
} catch (e) {
throw { message: e.message, index: this.getIndex(), filename: this.fileInfo().filename, stack: e.stack };
}
}
}
if (match) {
return rules;
}
if (conditionResult[0] || conditionResult[1]) {
if (conditionResult[0] != conditionResult[1]) {
return conditionResult[1] ?
defTrue : defFalse;
}
return defNone;
}
if (isOneFound) {
throw { type: 'Runtime',
message: `No matching definition was found for \`${this.format(args)}\``,
index: this.getIndex(), filename: this.fileInfo().filename };
return defFalseEitherCase;
}
for (i = 0; i < this.arguments.length; i++) {
arg = this.arguments[i];
argValue = arg.value.eval(context);
if (arg.expand && Array.isArray(argValue.value)) {
argValue = argValue.value;
for (m = 0; m < argValue.length; m++) {
args.push({value: argValue[m]});
}
} else {
throw { type: 'Name',
message: `${this.selector.toCSS().trim()} is undefined`,
index: this.getIndex(), filename: this.fileInfo().filename };
args.push({name: arg.name, value: argValue});
}
}
_setVisibilityToReplacement(replacement) {
let i;
let rule;
if (this.blocksVisibility()) {
for (i = 0; i < replacement.length; i++) {
rule = replacement[i];
rule.addVisibilityBlock();
}
}
}
noArgumentsFilter = rule => rule.matchArgs(null, context);
format(args) {
return `${this.selector.toCSS().trim()}(${args ? args.map(a => {
let argValue = '';
if (a.name) {
argValue += `${a.name}:`;
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, group: calcDefGroup(mixin, mixinPath)};
if (candidate.group !== defFalseEitherCase) {
candidates.push(candidate);
}
match = true;
}
}
if (a.value.toCSS) {
argValue += a.value.toCSS();
defaultFunc.reset();
count = [0, 0, 0];
for (m = 0; m < candidates.length; m++) {
count[candidates[m].group]++;
}
if (count[defNone] > 0) {
defaultResult = defFalse;
} else {
argValue += '???';
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.getIndex(), filename: this.fileInfo().filename };
}
}
return argValue;
}).join(', ') : ''})`;
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, null, originalRuleset.visibilityInfo());
mixin.originalRuleset = originalRuleset;
}
const newRules = mixin.evalCall(context, args, this.important).rules;
this._setVisibilityToReplacement(newRules);
Array.prototype.push.apply(rules, newRules);
} catch (e) {
throw { message: e.message, index: this.getIndex(), filename: this.fileInfo().filename, stack: e.stack };
}
}
}
if (match) {
return rules;
}
}
}
}
if (isOneFound) {
throw { type: 'Runtime',
message: `No matching definition was found for \`${this.format(args)}\``,
index: this.getIndex(), filename: this.fileInfo().filename };
} else {
throw { type: 'Name',
message: `${this.selector.toCSS().trim()} is undefined`,
index: this.getIndex(), filename: this.fileInfo().filename };
}
};
MixinCall.prototype._setVisibilityToReplacement = function(replacement) {
let i;
let rule;
if (this.blocksVisibility()) {
for (i = 0; i < replacement.length; i++) {
rule = replacement[i];
rule.addVisibilityBlock();
}
}
};
MixinCall.prototype.format = function(args) {
return `${this.selector.toCSS().trim()}(${args ? args.map(a => {
let argValue = '';
if (a.name) {
argValue += `${a.name}:`;
}
if (a.value.toCSS) {
argValue += a.value.toCSS();
} else {
argValue += '???';
}
return argValue;
}).join(', ') : ''})`;
};
MixinCall.prototype.type = 'MixinCall';
export default MixinCall;

View File

@@ -7,222 +7,220 @@ import Expression from './expression';
import contexts from '../contexts';
import * as utils from '../utils';
class Definition extends Ruleset {
constructor(name, params, rules, condition, variadic, frames, visibilityInfo) {
super();
const Definition = function(name, params, rules, condition, variadic, frames, visibilityInfo) {
this.name = name || 'anonymous mixin';
this.selectors = [new Selector([new Element(null, name, false, this._index, this._fileInfo)])];
this.params = params;
this.condition = condition;
this.variadic = variadic;
this.arity = params.length;
this.rules = rules;
this._lookups = {};
const optionalParameters = [];
this.required = params.reduce((count, p) => {
if (!p.name || (p.name && !p.value)) {
return count + 1;
}
else {
optionalParameters.push(p.name);
return count;
}
}, 0);
this.optionalParameters = optionalParameters;
this.frames = frames;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
};
this.name = name || 'anonymous mixin';
this.selectors = [new Selector([new Element(null, name, false, this._index, this._fileInfo)])];
this.params = params;
this.condition = condition;
this.variadic = variadic;
this.arity = params.length;
this.rules = rules;
this._lookups = {};
const optionalParameters = [];
this.required = params.reduce((count, p) => {
if (!p.name || (p.name && !p.value)) {
return count + 1;
}
else {
optionalParameters.push(p.name);
return count;
}
}, 0);
this.optionalParameters = optionalParameters;
this.frames = frames;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
Definition.prototype = new Ruleset();
Definition.prototype.accept = function(visitor) {
if (this.params && this.params.length) {
this.params = visitor.visitArray(this.params);
}
accept(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);
}
this.rules = visitor.visitArray(this.rules);
if (this.condition) {
this.condition = visitor.visit(this.condition);
}
};
evalParams(context, mixinEnv, args, evaldArguments) {
/* jshint boss:true */
const frame = new Ruleset(null, null);
Definition.prototype.evalParams = function(context, mixinEnv, args, evaldArguments) {
/* jshint boss:true */
const frame = new Ruleset(null, null);
let varargs;
let arg;
const params = utils.copyArray(this.params);
let i;
let j;
let val;
let name;
let isNamedFound;
let argIndex;
let argsLength = 0;
let varargs;
let arg;
const params = utils.copyArray(this.params);
let i;
let j;
let val;
let name;
let isNamedFound;
let argIndex;
let argsLength = 0;
if (mixinEnv.frames && mixinEnv.frames[0] && mixinEnv.frames[0].functionRegistry) {
frame.functionRegistry = mixinEnv.frames[0].functionRegistry.inherit();
}
mixinEnv = new contexts.Eval(mixinEnv, [frame].concat(mixinEnv.frames));
if (mixinEnv.frames && mixinEnv.frames[0] && mixinEnv.frames[0].functionRegistry) {
frame.functionRegistry = mixinEnv.frames[0].functionRegistry.inherit();
}
mixinEnv = new contexts.Eval(mixinEnv, [frame].concat(mixinEnv.frames));
if (args) {
args = utils.copyArray(args);
argsLength = args.length;
if (args) {
args = utils.copyArray(args);
argsLength = args.length;
for (i = 0; i < argsLength; 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(context);
frame.prependRule(new Declaration(name, arg.value.eval(context)));
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` };
for (i = 0; i < argsLength; 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(context);
frame.prependRule(new Declaration(name, arg.value.eval(context)));
isNamedFound = true;
break;
}
}
}
}
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) {
varargs = [];
for (j = argIndex; j < argsLength; j++) {
varargs.push(args[j].value.eval(context));
}
frame.prependRule(new Declaration(name, new Expression(varargs).eval(context)));
if (isNamedFound) {
args.splice(i, 1);
i--;
continue;
} else {
val = arg && arg.value;
if (val) {
// This was a mixin call, pass in a detached ruleset of it's eval'd rules
if (Array.isArray(val)) {
val = new DetachedRuleset(new Ruleset('', val));
}
else {
val = val.eval(context);
}
} 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} (${argsLength} for ${this.arity})` };
}
frame.prependRule(new Declaration(name, val));
evaldArguments[i] = val;
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; }
if (params[i].variadic && args) {
arg = args && args[argIndex];
if (name = params[i].name) {
if (params[i].variadic) {
varargs = [];
for (j = argIndex; j < argsLength; j++) {
evaldArguments[j] = args[j].value.eval(context);
varargs.push(args[j].value.eval(context));
}
}
argIndex++;
}
return frame;
}
makeImportant() {
const rules = !this.rules ? this.rules : this.rules.map(r => {
if (r.makeImportant) {
return r.makeImportant(true);
frame.prependRule(new Declaration(name, new Expression(varargs).eval(context)));
} else {
return r;
val = arg && arg.value;
if (val) {
// This was a mixin call, pass in a detached ruleset of it's eval'd rules
if (Array.isArray(val)) {
val = new DetachedRuleset(new Ruleset('', val));
}
else {
val = val.eval(context);
}
} 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} (${argsLength} for ${this.arity})` };
}
frame.prependRule(new Declaration(name, val));
evaldArguments[i] = val;
}
});
const result = new Definition(this.name, this.params, rules, this.condition, this.variadic, this.frames);
return result;
}
eval(context) {
return new Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || utils.copyArray(context.frames));
}
evalCall(context, args, important) {
const _arguments = [];
const mixinFrames = this.frames ? this.frames.concat(context.frames) : context.frames;
const frame = this.evalParams(context, new contexts.Eval(context, mixinFrames), args, _arguments);
let rules;
let ruleset;
frame.prependRule(new Declaration('@arguments', new Expression(_arguments).eval(context)));
rules = utils.copyArray(this.rules);
ruleset = new Ruleset(null, rules);
ruleset.originalRuleset = this;
ruleset = ruleset.eval(new contexts.Eval(context, [this, frame].concat(mixinFrames)));
if (important) {
ruleset = ruleset.makeImportant();
}
return ruleset;
if (params[i].variadic && args) {
for (j = argIndex; j < argsLength; j++) {
evaldArguments[j] = args[j].value.eval(context);
}
}
argIndex++;
}
matchCondition(args, context) {
if (this.condition && !this.condition.eval(
new contexts.Eval(context,
[this.evalParams(context, /* the parameter variables */
new contexts.Eval(context, this.frames ? this.frames.concat(context.frames) : context.frames), args, [])]
.concat(this.frames || []) // the parent namespace/mixin frames
.concat(context.frames)))) { // the current environment frames
return frame;
};
Definition.prototype.makeImportant = function() {
const rules = !this.rules ? this.rules : this.rules.map(r => {
if (r.makeImportant) {
return r.makeImportant(true);
} else {
return r;
}
});
const result = new Definition(this.name, this.params, rules, this.condition, this.variadic, this.frames);
return result;
};
Definition.prototype.eval = function(context) {
return new Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || utils.copyArray(context.frames));
};
Definition.prototype.evalCall = function(context, args, important) {
const _arguments = [];
const mixinFrames = this.frames ? this.frames.concat(context.frames) : context.frames;
const frame = this.evalParams(context, new contexts.Eval(context, mixinFrames), args, _arguments);
let rules;
let ruleset;
frame.prependRule(new Declaration('@arguments', new Expression(_arguments).eval(context)));
rules = utils.copyArray(this.rules);
ruleset = new Ruleset(null, rules);
ruleset.originalRuleset = this;
ruleset = ruleset.eval(new contexts.Eval(context, [this, frame].concat(mixinFrames)));
if (important) {
ruleset = ruleset.makeImportant();
}
return ruleset;
};
Definition.prototype.matchCondition = function(args, context) {
if (this.condition && !this.condition.eval(
new contexts.Eval(context,
[this.evalParams(context, /* the parameter variables */
new contexts.Eval(context, this.frames ? this.frames.concat(context.frames) : context.frames), args, [])]
.concat(this.frames || []) // the parent namespace/mixin frames
.concat(context.frames)))) { // the current environment frames
return false;
}
return true;
};
Definition.prototype.matchArgs = function(args, context) {
const allArgsCnt = (args && args.length) || 0;
let len;
const optionalParameters = this.optionalParameters;
const requiredArgsCnt = !args ? 0 : args.reduce((count, p) => {
if (optionalParameters.indexOf(p.name) < 0) {
return count + 1;
} else {
return count;
}
}, 0);
if (!this.variadic) {
if (requiredArgsCnt < this.required) {
return false;
}
if (allArgsCnt > this.params.length) {
return false;
}
} else {
if (requiredArgsCnt < (this.required - 1)) {
return false;
}
return true;
}
matchArgs(args, context) {
const allArgsCnt = (args && args.length) || 0;
let len;
const optionalParameters = this.optionalParameters;
const requiredArgsCnt = !args ? 0 : args.reduce((count, p) => {
if (optionalParameters.indexOf(p.name) < 0) {
return count + 1;
} else {
return count;
}
}, 0);
// check patterns
len = Math.min(requiredArgsCnt, this.arity);
if (!this.variadic) {
if (requiredArgsCnt < this.required) {
return false;
}
if (allArgsCnt > this.params.length) {
return false;
}
} else {
if (requiredArgsCnt < (this.required - 1)) {
for (let i = 0; i < len; i++) {
if (!this.params[i].name && !this.params[i].variadic) {
if (args[i].value.eval(context).toCSS() != this.params[i].value.eval(context).toCSS()) {
return false;
}
}
// check patterns
len = Math.min(requiredArgsCnt, this.arity);
for (let i = 0; i < len; i++) {
if (!this.params[i].name && !this.params[i].variadic) {
if (args[i].value.eval(context).toCSS() != this.params[i].value.eval(context).toCSS()) {
return false;
}
}
}
return true;
}
}
return true;
};
Definition.prototype.type = 'MixinDefinition';
Definition.prototype.evalFirst = true;

View File

@@ -3,84 +3,82 @@ import Variable from './variable';
import Ruleset from './ruleset';
import Selector from './selector';
class NamespaceValue extends Node {
constructor(ruleCall, lookups, index, fileInfo) {
super();
const NamespaceValue = function(ruleCall, lookups, index, fileInfo) {
this.value = ruleCall;
this.lookups = lookups;
this._index = index;
this._fileInfo = fileInfo;
}
this.value = ruleCall;
this.lookups = lookups;
this._index = index;
this._fileInfo = fileInfo;
}
NamespaceValue.prototype = new Node();
eval(context) {
let i;
let j;
let name;
let rules = this.value.eval(context);
NamespaceValue.prototype.eval = function(context) {
let i;
let j;
let name;
let rules = this.value.eval(context);
for (i = 0; i < this.lookups.length; i++) {
name = this.lookups[i];
for (i = 0; i < this.lookups.length; i++) {
name = this.lookups[i];
/**
* Eval'd DRs return rulesets.
* Eval'd mixins return rules, so let's make a ruleset if we need it.
* We need to do this because of late parsing of values
*/
if (Array.isArray(rules)) {
rules = new Ruleset([new Selector()], rules);
/**
* Eval'd DRs return rulesets.
* Eval'd mixins return rules, so let's make a ruleset if we need it.
* We need to do this because of late parsing of values
*/
if (Array.isArray(rules)) {
rules = new Ruleset([new Selector()], rules);
}
if (name === '') {
rules = rules.lastDeclaration();
}
else if (name.charAt(0) === '@') {
if (name.charAt(1) === '@') {
name = `@${new Variable(name.substr(1)).eval(context).value}`;
}
if (name === '') {
rules = rules.lastDeclaration();
if (rules.variables) {
rules = rules.variable(name);
}
else if (name.charAt(0) === '@') {
if (name.charAt(1) === '@') {
name = `@${new Variable(name.substr(1)).eval(context).value}`;
}
if (rules.variables) {
rules = rules.variable(name);
}
if (!rules) {
throw { type: 'Name',
message: `variable ${name} not found`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
}
else {
if (name.substring(0, 2) === '$@') {
name = `$${new Variable(name.substr(1)).eval(context).value}`;
}
else {
name = name.charAt(0) === '$' ? name : `$${name}`;
}
if (rules.properties) {
rules = rules.property(name);
}
if (!rules) {
throw { type: 'Name',
message: `property "${name.substr(1)}" not found`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
// Properties are an array of values, since a ruleset can have multiple props.
// We pick the last one (the "cascaded" value)
rules = rules[rules.length - 1];
}
if (rules.value) {
rules = rules.eval(context).value;
}
if (rules.ruleset) {
rules = rules.ruleset.eval(context);
if (!rules) {
throw { type: 'Name',
message: `variable ${name} not found`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
}
return rules;
else {
if (name.substring(0, 2) === '$@') {
name = `$${new Variable(name.substr(1)).eval(context).value}`;
}
else {
name = name.charAt(0) === '$' ? name : `$${name}`;
}
if (rules.properties) {
rules = rules.property(name);
}
if (!rules) {
throw { type: 'Name',
message: `property "${name.substr(1)}" not found`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
// Properties are an array of values, since a ruleset can have multiple props.
// We pick the last one (the "cascaded" value)
rules = rules[rules.length - 1];
}
if (rules.value) {
rules = rules.eval(context).value;
}
if (rules.ruleset) {
rules = rules.ruleset.eval(context);
}
}
}
return rules;
};
NamespaceValue.prototype.type = 'NamespaceValue';
export default NamespaceValue;

View File

@@ -2,25 +2,23 @@ import Node from './node';
import Operation from './operation';
import Dimension from './dimension';
class Negative extends Node {
constructor(node) {
super();
this.value = node;
}
genCSS(context, output) {
output.add('-');
this.value.genCSS(context, output);
}
eval(context) {
if (context.isMathOn()) {
return (new Operation('*', [new Dimension(-1), this.value])).eval(context);
}
return new Negative(this.value.eval(context));
}
const Negative = function(node) {
this.value = node;
}
Negative.prototype = new Node();
Negative.prototype.genCSS = function(context, output) {
output.add('-');
this.value.genCSS(context, output);
};
Negative.prototype.eval = function(context) {
if (context.isMathOn()) {
return (new Operation('*', [new Dimension(-1), this.value])).eval(context);
}
return new Negative(this.value.eval(context));
};
Negative.prototype.type = 'Negative';
export default Negative;

View File

@@ -1,3 +1,9 @@
/**
* The reason why Node is a class and other nodes simply do not extend
* from Node (since we're transpiling) is due to this issue:
*
* https://github.com/less/less.js/issues/3434
*/
class Node {
constructor() {
this.parent = null;

View File

@@ -4,59 +4,56 @@ import Dimension from './dimension';
import * as Constants from '../constants';
const MATH = Constants.Math;
const Operation = function(op, operands, isSpaced) {
this.op = op.trim();
this.operands = operands;
this.isSpaced = isSpaced;
};
class Operation extends Node {
constructor(op, operands, isSpaced) {
super();
Operation.prototype = new Node();
this.op = op.trim();
this.operands = operands;
this.isSpaced = isSpaced;
}
accept(visitor) {
this.operands = visitor.visitArray(this.operands);
}
eval(context) {
let a = this.operands[0].eval(context);
let b = this.operands[1].eval(context);
let op;
if (context.isMathOn(this.op)) {
op = this.op === './' ? '/' : this.op;
if (a instanceof Dimension && b instanceof Color) {
a = a.toColor();
}
if (b instanceof Dimension && a instanceof Color) {
b = b.toColor();
}
if (!a.operate) {
if (a instanceof Operation && a.op === '/' && context.math === MATH.PARENS_DIVISION) {
return new Operation(this.op, [a, b], this.isSpaced);
}
throw { type: 'Operation',
message: 'Operation on an invalid type' };
}
return a.operate(context, op, b);
} else {
return new Operation(this.op, [a, b], this.isSpaced);
}
}
genCSS(context, output) {
this.operands[0].genCSS(context, output);
if (this.isSpaced) {
output.add(' ');
}
output.add(this.op);
if (this.isSpaced) {
output.add(' ');
}
this.operands[1].genCSS(context, output);
}
Operation.prototype.accept = function(visitor) {
this.operands = visitor.visitArray(this.operands);
}
Operation.prototype.eval = function(context) {
let a = this.operands[0].eval(context);
let b = this.operands[1].eval(context);
let op;
if (context.isMathOn(this.op)) {
op = this.op === './' ? '/' : this.op;
if (a instanceof Dimension && b instanceof Color) {
a = a.toColor();
}
if (b instanceof Dimension && a instanceof Color) {
b = b.toColor();
}
if (!a.operate) {
if (a instanceof Operation && a.op === '/' && context.math === MATH.PARENS_DIVISION) {
return new Operation(this.op, [a, b], this.isSpaced);
}
throw { type: 'Operation',
message: 'Operation on an invalid type' };
}
return a.operate(context, op, b);
} else {
return new Operation(this.op, [a, b], this.isSpaced);
}
};
Operation.prototype.genCSS = function(context, output) {
this.operands[0].genCSS(context, output);
if (this.isSpaced) {
output.add(' ');
}
output.add(this.op);
if (this.isSpaced) {
output.add(' ');
}
this.operands[1].genCSS(context, output);
};
Operation.prototype.type = 'Operation';
export default Operation;

View File

@@ -1,22 +1,20 @@
import Node from './node';
class Paren extends Node {
constructor(node) {
super();
const Paren = function(node) {
this.value = node;
};
this.value = node;
}
Paren.prototype = new Node();
genCSS(context, output) {
output.add('(');
this.value.genCSS(context, output);
output.add(')');
}
eval(context) {
return new Paren(this.value.eval(context));
}
Paren.prototype.genCSS = function(context, output) {
output.add('(');
this.value.genCSS(context, output);
output.add(')');
}
Paren.prototype.eval = function(context) {
return new Paren(this.value.eval(context));
};
Paren.prototype.type = 'Paren';
export default Paren;

View File

@@ -1,77 +1,75 @@
import Node from './node';
import Declaration from './declaration';
class Property extends Node {
constructor(name, index, currentFileInfo) {
super();
const Property = function(name, index, currentFileInfo) {
this.name = name;
this._index = index;
this._fileInfo = currentFileInfo;
};
this.name = name;
this._index = index;
this._fileInfo = currentFileInfo;
Property.prototype = new Node();
Property.prototype.eval = function(context) {
let property;
const name = this.name;
// TODO: shorten this reference
const mergeRules = context.pluginManager.less.visitors.ToCSSVisitor.prototype._mergeRules;
if (this.evaluating) {
throw { type: 'Name',
message: `Recursive property reference for ${name}`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
eval(context) {
let property;
const name = this.name;
// TODO: shorten this reference
const mergeRules = context.pluginManager.less.visitors.ToCSSVisitor.prototype._mergeRules;
this.evaluating = true;
if (this.evaluating) {
throw { type: 'Name',
message: `Recursive property reference for ${name}`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
property = this.find(context.frames, frame => {
let v;
const vArr = frame.property(name);
if (vArr) {
for (let i = 0; i < vArr.length; i++) {
v = vArr[i];
this.evaluating = true;
property = this.find(context.frames, frame => {
let v;
const vArr = frame.property(name);
if (vArr) {
for (let i = 0; i < vArr.length; i++) {
v = vArr[i];
vArr[i] = new Declaration(v.name,
v.value,
v.important,
v.merge,
v.index,
v.currentFileInfo,
v.inline,
v.variable
);
}
mergeRules(vArr);
v = vArr[vArr.length - 1];
if (v.important) {
const importantScope = context.importantScope[context.importantScope.length - 1];
importantScope.important = v.important;
}
v = v.value.eval(context);
return v;
vArr[i] = new Declaration(v.name,
v.value,
v.important,
v.merge,
v.index,
v.currentFileInfo,
v.inline,
v.variable
);
}
});
if (property) {
this.evaluating = false;
return property;
} else {
throw { type: 'Name',
message: `Property '${name}' is undefined`,
filename: this.currentFileInfo.filename,
index: this.index };
}
}
mergeRules(vArr);
find(obj, fun) {
for (let i = 0, r; i < obj.length; i++) {
r = fun.call(obj, obj[i]);
if (r) { return r; }
v = vArr[vArr.length - 1];
if (v.important) {
const importantScope = context.importantScope[context.importantScope.length - 1];
importantScope.important = v.important;
}
v = v.value.eval(context);
return v;
}
return null;
});
if (property) {
this.evaluating = false;
return property;
} else {
throw { type: 'Name',
message: `Property '${name}' is undefined`,
filename: this.currentFileInfo.filename,
index: this.index };
}
}
};
Property.prototype.find = function(obj, fun) {
for (let i = 0, r; i < obj.length; i++) {
r = fun.call(obj, obj[i]);
if (r) { return r; }
}
return null;
};
Property.prototype.type = 'Property';
export default Property;

View File

@@ -2,69 +2,66 @@ import Node from './node';
import Variable from './variable';
import Property from './property';
const Quoted = function(str, content, escaped, index, currentFileInfo) {
this.escaped = (escaped == null) ? true : escaped;
this.value = content || '';
this.quote = str.charAt(0);
this._index = index;
this._fileInfo = currentFileInfo;
this.variableRegex = /@\{([\w-]+)\}/g;
this.propRegex = /\$\{([\w-]+)\}/g;
this.allowRoot = escaped;
};
class Quoted extends Node {
constructor(str, content, escaped, index, currentFileInfo) {
super();
Quoted.prototype = new Node();
this.escaped = (escaped == null) ? true : escaped;
this.value = content || '';
this.quote = str.charAt(0);
this._index = index;
this._fileInfo = currentFileInfo;
this.variableRegex = /@\{([\w-]+)\}/g;
this.propRegex = /\$\{([\w-]+)\}/g;
this.allowRoot = escaped;
Quoted.prototype.genCSS = function(context, output) {
if (!this.escaped) {
output.add(this.quote, this.fileInfo(), this.getIndex());
}
genCSS(context, output) {
if (!this.escaped) {
output.add(this.quote, this.fileInfo(), this.getIndex());
}
output.add(this.value);
if (!this.escaped) {
output.add(this.quote);
}
output.add(this.value);
if (!this.escaped) {
output.add(this.quote);
}
};
containsVariables() {
return this.value.match(this.variableRegex);
Quoted.prototype.containsVariables = function() {
return this.value.match(this.variableRegex);
};
Quoted.prototype.eval = function(context) {
const that = this;
let value = this.value;
const variableReplacement = (_, name) => {
const v = new Variable(`@${name}`, that.getIndex(), that.fileInfo()).eval(context, true);
return (v instanceof Quoted) ? v.value : v.toCSS();
};
const propertyReplacement = (_, name) => {
const v = new Property(`$${name}`, that.getIndex(), that.fileInfo()).eval(context, true);
return (v instanceof Quoted) ? v.value : v.toCSS();
};
function iterativeReplace(value, regexp, replacementFnc) {
let evaluatedValue = value;
do {
value = evaluatedValue.toString();
evaluatedValue = value.replace(regexp, replacementFnc);
} while (value !== evaluatedValue);
return evaluatedValue;
}
value = iterativeReplace(value, this.variableRegex, variableReplacement);
value = iterativeReplace(value, this.propRegex, propertyReplacement);
eval(context) {
const that = this;
let value = this.value;
const variableReplacement = (_, name) => {
const v = new Variable(`@${name}`, that.getIndex(), that.fileInfo()).eval(context, true);
return (v instanceof Quoted) ? v.value : v.toCSS();
};
const propertyReplacement = (_, name) => {
const v = new Property(`$${name}`, that.getIndex(), that.fileInfo()).eval(context, true);
return (v instanceof Quoted) ? v.value : v.toCSS();
};
function iterativeReplace(value, regexp, replacementFnc) {
let evaluatedValue = value;
do {
value = evaluatedValue.toString();
evaluatedValue = value.replace(regexp, replacementFnc);
} while (value !== evaluatedValue);
return evaluatedValue;
}
value = iterativeReplace(value, this.variableRegex, variableReplacement);
value = iterativeReplace(value, this.propRegex, propertyReplacement);
return new Quoted(this.quote + value + this.quote, value, this.escaped, this.getIndex(), this.fileInfo());
};
return new Quoted(this.quote + value + this.quote, value, this.escaped, this.getIndex(), this.fileInfo());
Quoted.prototype.compare = function(other) {
// when comparing quoted strings allow the quote to differ
if (other.type === 'Quoted' && !this.escaped && !other.escaped) {
return Node.numericCompare(this.value, other.value);
} else {
return other.toCSS && this.toCSS() === other.toCSS() ? 0 : undefined;
}
compare(other) {
// when comparing quoted strings allow the quote to differ
if (other.type === 'Quoted' && !this.escaped && !other.escaped) {
return Node.numericCompare(this.value, other.value);
} else {
return other.toCSS && this.toCSS() === other.toCSS() ? 0 : undefined;
}
}
}
};
Quoted.prototype.type = 'Quoted';
export default Quoted;

File diff suppressed because it is too large Load Diff

View File

@@ -2,145 +2,143 @@ import Node from './node';
import Element from './element';
import LessError from '../less-error';
class Selector extends Node {
constructor(elements, extendList, condition, index, currentFileInfo, visibilityInfo) {
super();
this.extendList = extendList;
this.condition = condition;
this.evaldCondition = !condition;
this._index = index;
this._fileInfo = currentFileInfo;
this.elements = this.getElements(elements);
this.mixinElements_ = undefined;
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.elements, this);
}
accept(visitor) {
if (this.elements) {
this.elements = visitor.visitArray(this.elements);
}
if (this.extendList) {
this.extendList = visitor.visitArray(this.extendList);
}
if (this.condition) {
this.condition = visitor.visit(this.condition);
}
}
createDerived(elements, extendList, evaldCondition) {
elements = this.getElements(elements);
const newSelector = new Selector(elements, extendList || this.extendList,
null, this.getIndex(), this.fileInfo(), this.visibilityInfo());
newSelector.evaldCondition = (evaldCondition != null) ? evaldCondition : this.evaldCondition;
newSelector.mediaEmpty = this.mediaEmpty;
return newSelector;
}
getElements(els) {
if (!els) {
return [new Element('', '&', false, this._index, this._fileInfo)];
}
if (typeof els === 'string') {
this.parse.parseNode(
els,
['selector'],
this._index,
this._fileInfo,
function(err, result) {
if (err) {
throw new LessError({
index: err.index,
message: err.message
}, this.parse.imports, this._fileInfo.filename);
}
els = result[0].elements;
});
}
return els;
}
createEmptySelectors() {
const el = new Element('', '&', false, this._index, this._fileInfo);
const sels = [new Selector([el], null, null, this._index, this._fileInfo)];
sels[0].mediaEmpty = true;
return sels;
}
match(other) {
const elements = this.elements;
const len = elements.length;
let olen;
let i;
other = other.mixinElements();
olen = other.length;
if (olen === 0 || len < olen) {
return 0;
} else {
for (i = 0; i < olen; i++) {
if (elements[i].value !== other[i]) {
return 0;
}
}
}
return olen; // return number of matched elements
}
mixinElements() {
if (this.mixinElements_) {
return this.mixinElements_;
}
let elements = this.elements.map( v => v.combinator.value + (v.value.value || v.value)).join('').match(/[,&#\*\.\w-]([\w-]|(\\.))*/g);
if (elements) {
if (elements[0] === '&') {
elements.shift();
}
} else {
elements = [];
}
return (this.mixinElements_ = elements);
}
isJustParentSelector() {
return !this.mediaEmpty &&
this.elements.length === 1 &&
this.elements[0].value === '&' &&
(this.elements[0].combinator.value === ' ' || this.elements[0].combinator.value === '');
}
eval(context) {
const evaldCondition = this.condition && this.condition.eval(context);
let elements = this.elements;
let extendList = this.extendList;
elements = elements && elements.map(e => e.eval(context));
extendList = extendList && extendList.map(extend => extend.eval(context));
return this.createDerived(elements, extendList, evaldCondition);
}
genCSS(context, output) {
let i;
let element;
if ((!context || !context.firstSelector) && this.elements[0].combinator.value === '') {
output.add(' ', this.fileInfo(), this.getIndex());
}
for (i = 0; i < this.elements.length; i++) {
element = this.elements[i];
element.genCSS(context, output);
}
}
getIsOutput() {
return this.evaldCondition;
}
const Selector = function(elements, extendList, condition, index, currentFileInfo, visibilityInfo) {
this.extendList = extendList;
this.condition = condition;
this.evaldCondition = !condition;
this._index = index;
this._fileInfo = currentFileInfo;
this.elements = this.getElements(elements);
this.mixinElements_ = undefined;
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.elements, this);
}
Selector.prototype = new Node();
Selector.prototype.accept = function(visitor) {
if (this.elements) {
this.elements = visitor.visitArray(this.elements);
}
if (this.extendList) {
this.extendList = visitor.visitArray(this.extendList);
}
if (this.condition) {
this.condition = visitor.visit(this.condition);
}
};
Selector.prototype.createDerived = function(elements, extendList, evaldCondition) {
elements = this.getElements(elements);
const newSelector = new Selector(elements, extendList || this.extendList,
null, this.getIndex(), this.fileInfo(), this.visibilityInfo());
newSelector.evaldCondition = (evaldCondition != null) ? evaldCondition : this.evaldCondition;
newSelector.mediaEmpty = this.mediaEmpty;
return newSelector;
};
Selector.prototype.getElements = function(els) {
if (!els) {
return [new Element('', '&', false, this._index, this._fileInfo)];
}
if (typeof els === 'string') {
this.parse.parseNode(
els,
['selector'],
this._index,
this._fileInfo,
function(err, result) {
if (err) {
throw new LessError({
index: err.index,
message: err.message
}, this.parse.imports, this._fileInfo.filename);
}
els = result[0].elements;
});
}
return els;
};
Selector.prototype.createEmptySelectors = function() {
const el = new Element('', '&', false, this._index, this._fileInfo);
const sels = [new Selector([el], null, null, this._index, this._fileInfo)];
sels[0].mediaEmpty = true;
return sels;
};
Selector.prototype.match = function(other) {
const elements = this.elements;
const len = elements.length;
let olen;
let i;
other = other.mixinElements();
olen = other.length;
if (olen === 0 || len < olen) {
return 0;
} else {
for (i = 0; i < olen; i++) {
if (elements[i].value !== other[i]) {
return 0;
}
}
}
return olen; // return number of matched elements
};
Selector.prototype.mixinElements = function() {
if (this.mixinElements_) {
return this.mixinElements_;
}
let elements = this.elements.map( v => v.combinator.value + (v.value.value || v.value)).join('').match(/[,&#\*\.\w-]([\w-]|(\\.))*/g);
if (elements) {
if (elements[0] === '&') {
elements.shift();
}
} else {
elements = [];
}
return (this.mixinElements_ = elements);
};
Selector.prototype.isJustParentSelector = function() {
return !this.mediaEmpty &&
this.elements.length === 1 &&
this.elements[0].value === '&' &&
(this.elements[0].combinator.value === ' ' || this.elements[0].combinator.value === '');
};
Selector.prototype.eval = function(context) {
const evaldCondition = this.condition && this.condition.eval(context);
let elements = this.elements;
let extendList = this.extendList;
elements = elements && elements.map(e => e.eval(context));
extendList = extendList && extendList.map(extend => extend.eval(context));
return this.createDerived(elements, extendList, evaldCondition);
};
Selector.prototype.genCSS = function(context, output) {
let i;
let element;
if ((!context || !context.firstSelector) && this.elements[0].combinator.value === '') {
output.add(' ', this.fileInfo(), this.getIndex());
}
for (i = 0; i < this.elements.length; i++) {
element = this.elements[i];
element.genCSS(context, output);
}
};
Selector.prototype.getIsOutput = function() {
return this.evaldCondition;
};
Selector.prototype.type = 'Selector';
export default Selector;

View File

@@ -1,13 +1,10 @@
import Node from './node';
class UnicodeDescriptor extends Node {
constructor(value) {
super();
this.value = value;
}
const UnicodeDescriptor = function(value) {
this.value = value;
}
UnicodeDescriptor.prototype = new Node();
UnicodeDescriptor.prototype.type = 'UnicodeDescriptor';
export default UnicodeDescriptor;

View File

@@ -2,140 +2,138 @@ import Node from './node';
import unitConversions from '../data/unit-conversions';
import * as utils from '../utils';
class Unit extends Node {
constructor(numerator, denominator, backupUnit) {
super();
const Unit = function(numerator, denominator, backupUnit) {
this.numerator = numerator ? utils.copyArray(numerator).sort() : [];
this.denominator = denominator ? utils.copyArray(denominator).sort() : [];
if (backupUnit) {
this.backupUnit = backupUnit;
} else if (numerator && numerator.length) {
this.backupUnit = numerator[0];
}
};
this.numerator = numerator ? utils.copyArray(numerator).sort() : [];
this.denominator = denominator ? utils.copyArray(denominator).sort() : [];
if (backupUnit) {
this.backupUnit = backupUnit;
} else if (numerator && numerator.length) {
this.backupUnit = numerator[0];
Unit.prototype = new Node();
Unit.prototype.clone = function() {
return new Unit(utils.copyArray(this.numerator), utils.copyArray(this.denominator), this.backupUnit);
};
Unit.prototype.genCSS = function(context, output) {
// Dimension checks the unit is singular and throws an error if in strict math mode.
const strictUnits = context && context.strictUnits;
if (this.numerator.length === 1) {
output.add(this.numerator[0]); // the ideal situation
} else if (!strictUnits && this.backupUnit) {
output.add(this.backupUnit);
} else if (!strictUnits && this.denominator.length) {
output.add(this.denominator[0]);
}
};
Unit.prototype.toString = function() {
let i;
let returnStr = this.numerator.join('*');
for (i = 0; i < this.denominator.length; i++) {
returnStr += `/${this.denominator[i]}`;
}
return returnStr;
};
Unit.prototype.compare = function(other) {
return this.is(other.toString()) ? 0 : undefined;
};
Unit.prototype.is = function(unitString) {
return this.toString().toUpperCase() === unitString.toUpperCase();
};
Unit.prototype.isLength = function() {
return RegExp('^(px|em|ex|ch|rem|in|cm|mm|pc|pt|ex|vw|vh|vmin|vmax)$', 'gi').test(this.toCSS());
};
Unit.prototype.isEmpty = function() {
return this.numerator.length === 0 && this.denominator.length === 0;
};
Unit.prototype.isSingular = function() {
return this.numerator.length <= 1 && this.denominator.length === 0;
};
Unit.prototype.map = function(callback) {
let i;
for (i = 0; i < this.numerator.length; i++) {
this.numerator[i] = callback(this.numerator[i], false);
}
for (i = 0; i < this.denominator.length; i++) {
this.denominator[i] = callback(this.denominator[i], true);
}
};
Unit.prototype.usedUnits = function() {
let group;
const result = {};
let mapUnit;
let groupName;
mapUnit = atomicUnit => {
/* jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit) && !result[groupName]) {
result[groupName] = atomicUnit;
}
return atomicUnit;
};
for (groupName in unitConversions) {
if (unitConversions.hasOwnProperty(groupName)) {
group = unitConversions[groupName];
this.map(mapUnit);
}
}
clone() {
return new Unit(utils.copyArray(this.numerator), utils.copyArray(this.denominator), this.backupUnit);
return result;
};
Unit.prototype.cancel = function() {
const counter = {};
let atomicUnit;
let i;
for (i = 0; i < this.numerator.length; i++) {
atomicUnit = this.numerator[i];
counter[atomicUnit] = (counter[atomicUnit] || 0) + 1;
}
genCSS(context, output) {
// Dimension checks the unit is singular and throws an error if in strict math mode.
const strictUnits = context && context.strictUnits;
if (this.numerator.length === 1) {
output.add(this.numerator[0]); // the ideal situation
} else if (!strictUnits && this.backupUnit) {
output.add(this.backupUnit);
} else if (!strictUnits && this.denominator.length) {
output.add(this.denominator[0]);
}
for (i = 0; i < this.denominator.length; i++) {
atomicUnit = this.denominator[i];
counter[atomicUnit] = (counter[atomicUnit] || 0) - 1;
}
toString() {
let i;
let returnStr = this.numerator.join('*');
for (i = 0; i < this.denominator.length; i++) {
returnStr += `/${this.denominator[i]}`;
}
return returnStr;
}
this.numerator = [];
this.denominator = [];
compare(other) {
return this.is(other.toString()) ? 0 : undefined;
}
for (atomicUnit in counter) {
if (counter.hasOwnProperty(atomicUnit)) {
const count = counter[atomicUnit];
is(unitString) {
return this.toString().toUpperCase() === unitString.toUpperCase();
}
isLength() {
return RegExp('^(px|em|ex|ch|rem|in|cm|mm|pc|pt|ex|vw|vh|vmin|vmax)$', 'gi').test(this.toCSS());
}
isEmpty() {
return this.numerator.length === 0 && this.denominator.length === 0;
}
isSingular() {
return this.numerator.length <= 1 && this.denominator.length === 0;
}
map(callback) {
let i;
for (i = 0; i < this.numerator.length; i++) {
this.numerator[i] = callback(this.numerator[i], false);
}
for (i = 0; i < this.denominator.length; i++) {
this.denominator[i] = callback(this.denominator[i], true);
}
}
usedUnits() {
let group;
const result = {};
let mapUnit;
let groupName;
mapUnit = atomicUnit => {
/* jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit) && !result[groupName]) {
result[groupName] = atomicUnit;
}
return atomicUnit;
};
for (groupName in unitConversions) {
if (unitConversions.hasOwnProperty(groupName)) {
group = unitConversions[groupName];
this.map(mapUnit);
}
}
return result;
}
cancel() {
const counter = {};
let atomicUnit;
let i;
for (i = 0; i < this.numerator.length; i++) {
atomicUnit = this.numerator[i];
counter[atomicUnit] = (counter[atomicUnit] || 0) + 1;
}
for (i = 0; i < this.denominator.length; i++) {
atomicUnit = this.denominator[i];
counter[atomicUnit] = (counter[atomicUnit] || 0) - 1;
}
this.numerator = [];
this.denominator = [];
for (atomicUnit in counter) {
if (counter.hasOwnProperty(atomicUnit)) {
const count = counter[atomicUnit];
if (count > 0) {
for (i = 0; i < count; i++) {
this.numerator.push(atomicUnit);
}
} else if (count < 0) {
for (i = 0; i < -count; i++) {
this.denominator.push(atomicUnit);
}
if (count > 0) {
for (i = 0; i < count; i++) {
this.numerator.push(atomicUnit);
}
} else if (count < 0) {
for (i = 0; i < -count; i++) {
this.denominator.push(atomicUnit);
}
}
}
this.numerator.sort();
this.denominator.sort();
}
}
this.numerator.sort();
this.denominator.sort();
};
Unit.prototype.type = 'Unit';
export default Unit;

View File

@@ -1,60 +1,58 @@
import Node from './node';
class URL extends Node {
constructor(val, index, currentFileInfo, isEvald) {
super();
const URL = function(val, index, currentFileInfo, isEvald) {
this.value = val;
this._index = index;
this._fileInfo = currentFileInfo;
this.isEvald = isEvald;
};
this.value = val;
this._index = index;
this._fileInfo = currentFileInfo;
this.isEvald = isEvald;
}
URL.prototype = new Node();
accept(visitor) {
this.value = visitor.visit(this.value);
}
URL.prototype.accept = function(visitor) {
this.value = visitor.visit(this.value);
};
genCSS(context, output) {
output.add('url(');
this.value.genCSS(context, output);
output.add(')');
}
URL.prototype.genCSS = function(context, output) {
output.add('url(');
this.value.genCSS(context, output);
output.add(')');
};
eval(context) {
const val = this.value.eval(context);
let rootpath;
URL.prototype.eval = function(context) {
const val = this.value.eval(context);
let rootpath;
if (!this.isEvald) {
// Add the rootpath if the URL requires a rewrite
rootpath = this.fileInfo() && this.fileInfo().rootpath;
if (typeof rootpath === 'string' &&
typeof val.value === 'string' &&
context.pathRequiresRewrite(val.value)) {
if (!val.quote) {
rootpath = escapePath(rootpath);
}
val.value = context.rewritePath(val.value, rootpath);
} else {
val.value = context.normalizePath(val.value);
if (!this.isEvald) {
// Add the rootpath if the URL requires a rewrite
rootpath = this.fileInfo() && this.fileInfo().rootpath;
if (typeof rootpath === 'string' &&
typeof val.value === 'string' &&
context.pathRequiresRewrite(val.value)) {
if (!val.quote) {
rootpath = escapePath(rootpath);
}
val.value = context.rewritePath(val.value, rootpath);
} else {
val.value = context.normalizePath(val.value);
}
// Add url args if enabled
if (context.urlArgs) {
if (!val.value.match(/^\s*data:/)) {
const delimiter = val.value.indexOf('?') === -1 ? '?' : '&';
const urlArgs = delimiter + context.urlArgs;
if (val.value.indexOf('#') !== -1) {
val.value = val.value.replace('#', `${urlArgs}#`);
} else {
val.value += urlArgs;
}
// Add url args if enabled
if (context.urlArgs) {
if (!val.value.match(/^\s*data:/)) {
const delimiter = val.value.indexOf('?') === -1 ? '?' : '&';
const urlArgs = delimiter + context.urlArgs;
if (val.value.indexOf('#') !== -1) {
val.value = val.value.replace('#', `${urlArgs}#`);
} else {
val.value += urlArgs;
}
}
}
return new URL(val, this.getIndex(), this.fileInfo(), true);
}
}
return new URL(val, this.getIndex(), this.fileInfo(), true);
};
URL.prototype.type = 'Url';

View File

@@ -1,44 +1,42 @@
import Node from './node';
class Value extends Node {
constructor(value) {
super();
const Value = function(value) {
if (!value) {
throw new Error('Value requires an array argument');
}
if (!Array.isArray(value)) {
this.value = [ value ];
}
else {
this.value = value;
}
};
if (!value) {
throw new Error('Value requires an array argument');
}
if (!Array.isArray(value)) {
this.value = [ value ];
}
else {
this.value = value;
Value.prototype = new Node();
Value.prototype.accept = function(visitor) {
if (this.value) {
this.value = visitor.visitArray(this.value);
}
};
Value.prototype.eval = function(context) {
if (this.value.length === 1) {
return this.value[0].eval(context);
} else {
return new Value(this.value.map(v => v.eval(context)));
}
};
Value.prototype.genCSS = function(context, output) {
let i;
for (i = 0; i < this.value.length; i++) {
this.value[i].genCSS(context, output);
if (i + 1 < this.value.length) {
output.add((context && context.compress) ? ',' : ', ');
}
}
accept(visitor) {
if (this.value) {
this.value = visitor.visitArray(this.value);
}
}
eval(context) {
if (this.value.length === 1) {
return this.value[0].eval(context);
} else {
return new Value(this.value.map(v => v.eval(context)));
}
}
genCSS(context, output) {
let i;
for (i = 0; i < this.value.length; i++) {
this.value[i].genCSS(context, output);
if (i + 1 < this.value.length) {
output.add((context && context.compress) ? ',' : ', ');
}
}
}
}
};
Value.prototype.type = 'Value';
export default Value;

View File

@@ -4,43 +4,41 @@ import Ruleset from './ruleset';
import DetachedRuleset from './detached-ruleset';
import LessError from '../less-error';
class VariableCall extends Node {
constructor(variable, index, currentFileInfo) {
super();
const VariableCall = function(variable, index, currentFileInfo) {
this.variable = variable;
this._index = index;
this._fileInfo = currentFileInfo;
this.allowRoot = true;
};
this.variable = variable;
this._index = index;
this._fileInfo = currentFileInfo;
this.allowRoot = true;
VariableCall.prototype = new Node();
VariableCall.prototype.eval = function(context) {
let rules;
let detachedRuleset = new Variable(this.variable, this.getIndex(), this.fileInfo()).eval(context);
const error = new LessError({message: `Could not evaluate variable call ${this.variable}`});
if (!detachedRuleset.ruleset) {
if (detachedRuleset.rules) {
rules = detachedRuleset;
}
else if (Array.isArray(detachedRuleset)) {
rules = new Ruleset('', detachedRuleset);
}
else if (Array.isArray(detachedRuleset.value)) {
rules = new Ruleset('', detachedRuleset.value);
}
else {
throw error;
}
detachedRuleset = new DetachedRuleset(rules);
}
eval(context) {
let rules;
let detachedRuleset = new Variable(this.variable, this.getIndex(), this.fileInfo()).eval(context);
const error = new LessError({message: `Could not evaluate variable call ${this.variable}`});
if (!detachedRuleset.ruleset) {
if (detachedRuleset.rules) {
rules = detachedRuleset;
}
else if (Array.isArray(detachedRuleset)) {
rules = new Ruleset('', detachedRuleset);
}
else if (Array.isArray(detachedRuleset.value)) {
rules = new Ruleset('', detachedRuleset.value);
}
else {
throw error;
}
detachedRuleset = new DetachedRuleset(rules);
}
if (detachedRuleset.ruleset) {
return detachedRuleset.callEval(context);
}
throw error;
if (detachedRuleset.ruleset) {
return detachedRuleset.callEval(context);
}
}
throw error;
};
VariableCall.prototype.type = 'VariableCall';
export default VariableCall;

View File

@@ -1,67 +1,65 @@
import Node from './node';
import Call from './call';
class Variable extends Node {
constructor(name, index, currentFileInfo) {
super();
const Variable = function(name, index, currentFileInfo) {
this.name = name;
this._index = index;
this._fileInfo = currentFileInfo;
};
this.name = name;
this._index = index;
this._fileInfo = currentFileInfo;
Variable.prototype = new Node();
Variable.prototype.eval = function(context) {
let variable;
let name = this.name;
if (name.indexOf('@@') === 0) {
name = `@${new Variable(name.slice(1), this.getIndex(), this.fileInfo()).eval(context).value}`;
}
eval(context) {
let variable;
let name = this.name;
if (this.evaluating) {
throw { type: 'Name',
message: `Recursive variable definition for ${name}`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
if (name.indexOf('@@') === 0) {
name = `@${new Variable(name.slice(1), this.getIndex(), this.fileInfo()).eval(context).value}`;
}
this.evaluating = true;
if (this.evaluating) {
throw { type: 'Name',
message: `Recursive variable definition for ${name}`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
this.evaluating = true;
variable = this.find(context.frames, frame => {
const v = frame.variable(name);
if (v) {
if (v.important) {
const importantScope = context.importantScope[context.importantScope.length - 1];
importantScope.important = v.important;
}
// If in calc, wrap vars in a function call to cascade evaluate args first
if (context.inCalc) {
return (new Call('_SELF', [v.value])).eval(context);
}
else {
return v.value.eval(context);
}
variable = this.find(context.frames, frame => {
const v = frame.variable(name);
if (v) {
if (v.important) {
const importantScope = context.importantScope[context.importantScope.length - 1];
importantScope.important = v.important;
}
// If in calc, wrap vars in a function call to cascade evaluate args first
if (context.inCalc) {
return (new Call('_SELF', [v.value])).eval(context);
}
else {
return v.value.eval(context);
}
});
if (variable) {
this.evaluating = false;
return variable;
} else {
throw { type: 'Name',
message: `variable ${name} is undefined`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
});
if (variable) {
this.evaluating = false;
return variable;
} else {
throw { type: 'Name',
message: `variable ${name} is undefined`,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
};
find(obj, fun) {
for (let i = 0, r; i < obj.length; i++) {
r = fun.call(obj, obj[i]);
if (r) { return r; }
}
return null;
Variable.prototype.find = function(obj, fun) {
for (let i = 0, r; i < obj.length; i++) {
r = fun.call(obj, obj[i]);
if (r) { return r; }
}
}
return null;
};
Variable.prototype.type = 'Variable';
export default Variable;

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "3.12.2",
"version": "3.13.0",
"description": "Less files and CSS results",
"author": {
"name": "Alexis Sellier",

View File

@@ -1,7 +1,7 @@
{
"name": "@less/test-import-module",
"private": true,
"version": "3.12.0",
"version": "3.13.0",
"description": "Less files to be included in node_modules directory for testing import from node_modules",
"author": {
"name": "Alexis Sellier",