mirror of
https://github.com/less/less.js.git
synced 2026-05-01 03:00:22 -04:00
Simplified API on less object
e.g. "less.Ruleset()" instead of "new less.tree.Ruleset()"
Auto-casting of string values into nodes for AtRule, Declaration, Selector, Value
e.g. "less.Selector('&.a')" instead of "new tree.Selector(new tree.Element(new tree.Combinator('&'), '.a'))"
This commit is contained in:
@@ -54,8 +54,8 @@ AbstractPluginLoader.prototype.evalPlugin = function(contents, context, pluginOp
|
||||
registry = functionRegistry.create();
|
||||
|
||||
try {
|
||||
loader = new Function("module", "require", "functions", "tree", "fileInfo", contents);
|
||||
pluginObj = loader(localModule, this.require, registry, this.less.tree, fileInfo);
|
||||
loader = new Function("module", "require", "functions", "tree", "less", "fileInfo", contents);
|
||||
pluginObj = loader(localModule, this.require, registry, this.less.tree, this.less, fileInfo);
|
||||
|
||||
if (!pluginObj) {
|
||||
pluginObj = localModule.exports;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = function(environment, fileManagers) {
|
||||
var SourceMapOutput, SourceMapBuilder, ParseTree, ImportManager, Environment;
|
||||
|
||||
var less = {
|
||||
var initial = {
|
||||
version: [3, 0, 0],
|
||||
data: require('./data'),
|
||||
tree: require('./tree'),
|
||||
@@ -26,5 +26,28 @@ module.exports = function(environment, fileManagers) {
|
||||
logger: require('./logger')
|
||||
};
|
||||
|
||||
return less;
|
||||
// Create a public API
|
||||
|
||||
var ctor = function(t) {
|
||||
return function() {
|
||||
var obj = Object.create(t.prototype);
|
||||
t.apply(obj, Array.prototype.slice.call(arguments, 0));
|
||||
return obj;
|
||||
};
|
||||
};
|
||||
var t, api = Object.create(initial);
|
||||
for (var n in initial.tree) {
|
||||
t = initial.tree[n];
|
||||
if (typeof t === "function") {
|
||||
api[n] = ctor(t);
|
||||
}
|
||||
else {
|
||||
api[n] = Object.create(null);
|
||||
for (var o in t) {
|
||||
api[n][o] = ctor(t[o]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
@@ -80,6 +80,53 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Used after initial parsing to create nodes on the fly
|
||||
*
|
||||
* @param {String} str - string to parse
|
||||
* @param {Array} parseList - array of parsers to run input through e.g. ["value", "important"]
|
||||
* @param {Number} currentIndex - start number to begin indexing
|
||||
* @param {Object} fileInfo - fileInfo to attach to created nodes
|
||||
*/
|
||||
function parseNode(str, parseList, currentIndex, fileInfo, callback) {
|
||||
var result, returnNodes = [];
|
||||
var parser = parserInput;
|
||||
|
||||
try {
|
||||
parser.start(str, false, function fail(msg, index) {
|
||||
callback({
|
||||
message: msg,
|
||||
index: index + currentIndex
|
||||
});
|
||||
});
|
||||
for(var x = 0, p, i; (p = parseList[x]); x++) {
|
||||
i = parser.i;
|
||||
result = parsers[p]();
|
||||
if (result) {
|
||||
result._index = i + currentIndex;
|
||||
result._fileInfo = fileInfo;
|
||||
returnNodes.push(result);
|
||||
}
|
||||
else {
|
||||
returnNodes.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
var endInfo = parser.end();
|
||||
if (endInfo.isFinished) {
|
||||
callback(null, returnNodes);
|
||||
}
|
||||
else {
|
||||
callback(true, null);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new LessError({
|
||||
index: e.index + currentIndex,
|
||||
message: e.message
|
||||
}, imports, fileInfo.filename);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The Parser
|
||||
//
|
||||
@@ -87,6 +134,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
parserInput: parserInput,
|
||||
imports: imports,
|
||||
fileInfo: fileInfo,
|
||||
parseNode: parseNode,
|
||||
//
|
||||
// Parse an input string into an abstract syntax tree,
|
||||
// @param str A string containing 'less' markup
|
||||
@@ -133,8 +181,8 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
});
|
||||
|
||||
tree.Node.prototype.parse = this;
|
||||
|
||||
root = new(tree.Ruleset)(null, this.parsers.primary());
|
||||
root = new tree.Ruleset(null, this.parsers.primary());
|
||||
tree.Node.prototype.rootNode = root;
|
||||
root.root = true;
|
||||
root.firstRoot = true;
|
||||
|
||||
@@ -1008,7 +1056,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
if (! e) {
|
||||
parserInput.save();
|
||||
if (parserInput.$char('(')) {
|
||||
if ((v = this.selector()) && parserInput.$char(')')) {
|
||||
if ((v = this.selector(false)) && parserInput.$char(')')) {
|
||||
e = new(tree.Paren)(v);
|
||||
parserInput.forget();
|
||||
} else {
|
||||
@@ -1059,14 +1107,8 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
}
|
||||
},
|
||||
//
|
||||
// A CSS selector (see selector below)
|
||||
// with less extensions e.g. the ability to extend and guard
|
||||
//
|
||||
lessSelector: function () {
|
||||
return this.selector(true);
|
||||
},
|
||||
//
|
||||
// A CSS Selector
|
||||
// with less extensions e.g. the ability to extend and guard
|
||||
//
|
||||
// .class > div + h1
|
||||
// li a:hover
|
||||
@@ -1075,7 +1117,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
//
|
||||
selector: function (isLess) {
|
||||
var index = parserInput.i, elements, extendList, c, e, allExtends, when, condition;
|
||||
|
||||
isLess = isLess !== false;
|
||||
while ((isLess && (extendList = this.extend())) || (isLess && (when = parserInput.$str("when"))) || (e = this.element())) {
|
||||
if (when) {
|
||||
condition = expect(this.conditions, 'expected condition');
|
||||
@@ -1165,7 +1207,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
}
|
||||
|
||||
while (true) {
|
||||
s = this.lessSelector();
|
||||
s = this.selector();
|
||||
if (!s) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
var utils = require('./utils');
|
||||
/**
|
||||
* Plugin Manager
|
||||
*/
|
||||
@@ -53,13 +54,6 @@ PluginManager.prototype.get = function(filename) {
|
||||
return this.pluginCache[filename];
|
||||
};
|
||||
|
||||
// Object.getPrototypeOf shim for visitor upgrade
|
||||
if (!Object.getPrototypeOf) {
|
||||
Object.getPrototypeOf = function getPrototypeOf(object) {
|
||||
return object.constructor ? object.constructor.prototype : void 0;
|
||||
};
|
||||
}
|
||||
|
||||
function upgradeVisitors(visitor, oldType, newType) {
|
||||
|
||||
if (visitor['visit' + oldType] && !visitor['visit' + newType]) {
|
||||
@@ -78,7 +72,7 @@ PluginManager.prototype.addVisitor = function(visitor) {
|
||||
var proto;
|
||||
// 2.x to 3.x visitor compatibility
|
||||
try {
|
||||
proto = Object.getPrototypeOf(visitor);
|
||||
proto = utils.getPrototype(visitor);
|
||||
upgradeVisitors(proto, 'Directive', 'AtRule');
|
||||
upgradeVisitors(proto, 'Rule', 'Declaration');
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
var Node = require("./node"),
|
||||
Selector = require("./selector"),
|
||||
Ruleset = require("./ruleset");
|
||||
Ruleset = require("./ruleset"),
|
||||
Value = require('./value'),
|
||||
Anonymous = require('./anonymous');
|
||||
|
||||
var AtRule = function (name, value, rules, index, currentFileInfo, debugInfo, isRooted, visibilityInfo) {
|
||||
var i;
|
||||
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.value = (value instanceof Node) ? value : (value ? new Anonymous(value) : value);
|
||||
if (rules) {
|
||||
if (Array.isArray(rules)) {
|
||||
this.rules = rules;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
var Node = require("./node"),
|
||||
Value = require("./value"),
|
||||
Keyword = require("./keyword");
|
||||
Keyword = require("./keyword"),
|
||||
Anonymous = require("./anonymous");
|
||||
|
||||
var Declaration = function (name, value, important, merge, index, currentFileInfo, inline, variable) {
|
||||
this.name = name;
|
||||
this.value = (value instanceof Node) ? value : new Value([value]); //value instanceof tree.Value || value instanceof tree.Ruleset ??
|
||||
this.value = (value instanceof Node) ? value : new Value([value ? new Anonymous(value) : null]);
|
||||
this.important = important ? ' ' + important.trim() : '';
|
||||
this.merge = merge;
|
||||
this._index = index;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var tree = {};
|
||||
var tree = Object.create(null);
|
||||
|
||||
tree.Node = require('./node');
|
||||
tree.Alpha = require('./alpha');
|
||||
|
||||
@@ -298,26 +298,21 @@ Ruleset.prototype.parseValue = function(toParse) {
|
||||
var self = this;
|
||||
function transformDeclaration(decl) {
|
||||
if (decl.value instanceof Anonymous && !decl.parsed) {
|
||||
try {
|
||||
this.parse.parserInput.start(decl.value.value, false, function fail(msg, index) {
|
||||
decl.parsed = true;
|
||||
return decl;
|
||||
this.parse.parseNode(
|
||||
decl.value.value,
|
||||
["value", "important"],
|
||||
decl.value.getIndex(),
|
||||
decl.fileInfo(),
|
||||
function(err, result) {
|
||||
if (err) {
|
||||
decl.parsed = true;
|
||||
}
|
||||
if (result) {
|
||||
decl.value = result[0];
|
||||
decl.important = result[1] || '';
|
||||
decl.parsed = true;
|
||||
}
|
||||
});
|
||||
var result = this.parse.parsers.value();
|
||||
var important = this.parse.parsers.important();
|
||||
|
||||
var endInfo = this.parse.parserInput.end();
|
||||
if (endInfo.isFinished) {
|
||||
decl.value = result;
|
||||
decl.imporant = important;
|
||||
decl.parsed = true;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new LessError({
|
||||
index: e.index + decl.value.getIndex(),
|
||||
message: e.message
|
||||
}, this.parse.imports, decl.fileInfo().filename);
|
||||
}
|
||||
|
||||
return decl;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
var Node = require("./node"),
|
||||
Element = require("./element");
|
||||
Element = require("./element"),
|
||||
LessError = require("../less-error");
|
||||
|
||||
var Selector = function (elements, extendList, condition, index, currentFileInfo, visibilityInfo) {
|
||||
this.elements = elements;
|
||||
this.extendList = extendList;
|
||||
this.condition = condition;
|
||||
this._index = index;
|
||||
this._fileInfo = currentFileInfo;
|
||||
this.elements = this.getElements(elements);
|
||||
if (!condition) {
|
||||
this.evaldCondition = true;
|
||||
}
|
||||
@@ -27,6 +28,7 @@ Selector.prototype.accept = function (visitor) {
|
||||
}
|
||||
};
|
||||
Selector.prototype.createDerived = function(elements, extendList, evaldCondition) {
|
||||
elements = this.getElements(elements);
|
||||
var info = this.visibilityInfo();
|
||||
evaldCondition = (evaldCondition != null) ? evaldCondition : this.evaldCondition;
|
||||
var newSelector = new Selector(elements, extendList || this.extendList, null, this.getIndex(), this.fileInfo(), info);
|
||||
@@ -34,6 +36,25 @@ Selector.prototype.createDerived = function(elements, extendList, evaldCondition
|
||||
newSelector.mediaEmpty = this.mediaEmpty;
|
||||
return newSelector;
|
||||
};
|
||||
Selector.prototype.getElements = function(els) {
|
||||
if (typeof els === "string") {
|
||||
this.parse.parseNode(
|
||||
els,
|
||||
["selector"],
|
||||
this._index,
|
||||
this._fileInfo,
|
||||
function(err, result) {
|
||||
if (err) {
|
||||
throw new LessError({
|
||||
index: e.index + currentIndex,
|
||||
message: e.message
|
||||
}, this.parse.imports, this._fileInfo.filename);
|
||||
}
|
||||
els = result[0].elements;
|
||||
});
|
||||
}
|
||||
return els;
|
||||
};
|
||||
Selector.prototype.createEmptySelectors = function() {
|
||||
var el = new Element('', '&', this._index, this._fileInfo),
|
||||
sels = [new Selector([el], null, null, this._index, this._fileInfo)];
|
||||
@@ -45,7 +66,7 @@ Selector.prototype.match = function (other) {
|
||||
len = elements.length,
|
||||
olen, i;
|
||||
|
||||
other.CacheElements();
|
||||
other.cacheElements();
|
||||
|
||||
olen = other._elements.length;
|
||||
if (olen === 0 || len < olen) {
|
||||
@@ -60,7 +81,7 @@ Selector.prototype.match = function (other) {
|
||||
|
||||
return olen; // return number of matched elements
|
||||
};
|
||||
Selector.prototype.CacheElements = function() {
|
||||
Selector.prototype.cacheElements = function() {
|
||||
if (this._elements) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
var Node = require("./node");
|
||||
var Node = require("./node"),
|
||||
Anonymous = require("./anonymous");
|
||||
|
||||
|
||||
var Value = function (value) {
|
||||
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.type = "Value";
|
||||
|
||||
@@ -25,5 +25,18 @@ module.exports = {
|
||||
copy[i] = arr[i];
|
||||
}
|
||||
return copy;
|
||||
},
|
||||
getPrototype: function(obj) {
|
||||
if (Object.getPrototypeOf) {
|
||||
return Object.getPrototypeOf(obj);
|
||||
}
|
||||
else {
|
||||
if ("".__proto__ === String.prototype) {
|
||||
return obj.__proto__;
|
||||
}
|
||||
else if (obj.constructor) {
|
||||
return obj.constructor.prototype;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,21 +10,20 @@ function _noop(node) {
|
||||
function indexNodeTypes(parent, ticker) {
|
||||
// add .typeIndex to tree node types for lookup table
|
||||
var key, child;
|
||||
for (key in parent) {
|
||||
if (parent.hasOwnProperty(key)) {
|
||||
child = parent[key];
|
||||
switch (typeof child) {
|
||||
case "function":
|
||||
// ignore bound functions directly on tree which do not have a prototype
|
||||
// or aren't nodes
|
||||
if (child.prototype && child.prototype.type) {
|
||||
child.prototype.typeIndex = ticker++;
|
||||
}
|
||||
break;
|
||||
case "object":
|
||||
ticker = indexNodeTypes(child, ticker);
|
||||
break;
|
||||
}
|
||||
for (key in parent) {
|
||||
child = parent[key];
|
||||
switch (typeof child) {
|
||||
case "function":
|
||||
// ignore bound functions directly on tree which do not have a prototype
|
||||
// or aren't nodes
|
||||
if (child.prototype && child.prototype.type) {
|
||||
child.prototype.typeIndex = ticker++;
|
||||
}
|
||||
break;
|
||||
case "object":
|
||||
ticker = indexNodeTypes(child, ticker);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return ticker;
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
val2: foo;
|
||||
}
|
||||
|
||||
test-directive("@charset"; '"utf-8"');
|
||||
test-directive("@arbitrary"; "value after ()");
|
||||
test-atrule("@charset"; '"utf-8"');
|
||||
test-atrule("@arbitrary"; "value after ()");
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
functions.addMultiple({
|
||||
|
||||
"test-comment": function() {
|
||||
return new tree.Combinator(' ');
|
||||
return less.Combinator(' ');
|
||||
},
|
||||
"test-directive": function(arg1, arg2) {
|
||||
return new tree.Directive(arg1.value, new tree.Anonymous(arg2.value));
|
||||
"test-atrule": function(arg1, arg2) {
|
||||
return less.AtRule(arg1.value, arg2.value);
|
||||
},
|
||||
"test-extend": function() {
|
||||
//TODO
|
||||
@@ -22,7 +22,7 @@ functions.addMultiple({
|
||||
//TODO
|
||||
},
|
||||
"test-ruleset-call": function() {
|
||||
return new tree.Combinator(' ');
|
||||
return less.Combinator(' ');
|
||||
},
|
||||
// Functions must return something, even if it's false/true
|
||||
"test-undefined": function() {
|
||||
@@ -33,52 +33,53 @@ functions.addMultiple({
|
||||
},
|
||||
// These cause root errors
|
||||
"test-alpha": function() {
|
||||
return new tree.Alpha(30);
|
||||
return less.Alpha(30);
|
||||
},
|
||||
"test-assignment": function() {
|
||||
return new tree.Assignment("bird", "robin");
|
||||
return less.Assignment("bird", "robin");
|
||||
},
|
||||
"test-attribute": function() {
|
||||
return new tree.Attribute("foo", "=", "bar");
|
||||
return less.Attribute("foo", "=", "bar");
|
||||
},
|
||||
"test-call": function() {
|
||||
return new tree.Call("foo");
|
||||
return less.Call("foo");
|
||||
},
|
||||
"test-color": function() {
|
||||
return new tree.Color([50, 50, 50]);
|
||||
return less.Color([50, 50, 50]);
|
||||
},
|
||||
"test-condition": function() {
|
||||
return new tree.Condition('<', new tree.Value([0]), new tree.Value([1]));
|
||||
return less.Condition('<', less.Value([0]), less.Value([1]));
|
||||
},
|
||||
"test-detached-ruleset" : function() {
|
||||
var decl = new tree.Declaration('prop', new tree.Anonymous('value'));
|
||||
return new tree.DetachedRuleset(new tree.Ruleset("", [ decl ]));
|
||||
var decl = less.Declaration('prop', 'value');
|
||||
return less.DetachedRuleset(less.Ruleset("", [ decl ]));
|
||||
},
|
||||
"test-dimension": function() {
|
||||
return new tree.Dimension(1, 'px');
|
||||
return less.Dimension(1, 'px');
|
||||
},
|
||||
"test-element": function() {
|
||||
return new tree.Element('+', 'a');
|
||||
return less.Element('+', 'a');
|
||||
},
|
||||
"test-expression": function() {
|
||||
return new tree.Expression([1, 2, 3]);
|
||||
return less.Expression([1, 2, 3]);
|
||||
},
|
||||
"test-keyword": function() {
|
||||
return new tree.Keyword('foo');
|
||||
return less.Keyword('foo');
|
||||
},
|
||||
"test-operation": function() {
|
||||
return new tree.Operation('+', [1, 2]);
|
||||
return less.Operation('+', [1, 2]);
|
||||
},
|
||||
"test-quoted": function() {
|
||||
return new tree.Quoted('"', 'foo');
|
||||
return less.Quoted('"', 'foo');
|
||||
},
|
||||
"test-selector": function() {
|
||||
return new tree.Selector([new tree.Element('a')]);
|
||||
var sel = less.Selector('.a.b');
|
||||
return sel;
|
||||
},
|
||||
"test-url": function() {
|
||||
return new tree.URL('http://google.com');
|
||||
return less.URL('http://google.com');
|
||||
},
|
||||
"test-value": function() {
|
||||
return new tree.Value([1]);
|
||||
return less.Value([1]);
|
||||
}
|
||||
});
|
||||
@@ -2,7 +2,8 @@
|
||||
.block_1 {
|
||||
color: red;
|
||||
background-color: $color;
|
||||
width: 50px;
|
||||
@width: 50px;
|
||||
width: @width;
|
||||
height: ($width / 2);
|
||||
@color: red;
|
||||
border: 1px solid lighten($color, 10%);
|
||||
|
||||
Reference in New Issue
Block a user