Merge branch '3.x'

This commit is contained in:
Matthew Dean
2018-02-10 21:19:29 -08:00
174 changed files with 9219 additions and 1879 deletions

88
.eslintrc.json Normal file
View File

@@ -0,0 +1,88 @@
{
"env": {
"node": true
},
"globals": {},
"rules": {
"no-eval": 2,
"no-use-before-define": [
2,
{
"functions": false
}
],
"no-undef": 0,
"no-unused-vars": 1,
"no-caller": 2,
"no-eq-null": 1,
"guard-for-in": 2,
"no-implicit-coercion": [
2,
{
"boolean": false,
"string": true,
"number": true
}
],
"no-with": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multiple-empty-lines": 2,
"dot-location": [
2,
"property"
],
"operator-linebreak": [
0,
"after"
],
"keyword-spacing": [
2,
{}
],
"space-unary-ops": [
2,
{
"words": false,
"nonwords": false
}
],
"no-spaced-func": 2,
"space-before-function-paren": [
1,
{
"anonymous": "ignore",
"named": "never"
}
],
"comma-dangle": [
2,
"never"
],
"no-trailing-spaces": 0,
"max-len": [
2,
160
],
"comma-style": [
2,
"last"
],
"curly": [
2,
"all"
],
"space-infix-ops": 2,
"spaced-comment": 1,
"space-before-blocks": [
2,
"always"
],
"indent": [
2,
4,
{
"SwitchCase": 1
}
]
}
}

17
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- up-for-grabs
- bug
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

73
.jscsrc
View File

@@ -1,73 +0,0 @@
{
"disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"],
"disallowKeywords": ["with"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineBreaks": true,
"disallowOperatorBeforeLineBreak": ["."],
"disallowSpaceAfterKeywords": [
"void"//,
//"typeof"
],
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~"],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"disallowSpacesInCallExpression": true,
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true},
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
"maximumLineLength": 160,
"requireCommaBeforeLineBreak": true,
"requireCurlyBraces": [ "if",
"else",
"for",
"while",
"do",
"try",
"catch"],
"requireOperatorBeforeLineBreak": [ "?",
"=",
"+",
"-",
"/",
"*",
"==",
"===",
"!=",
"!==",
">",
">=",
"<",
"<="],
"requireSpaceAfterBinaryOperators": true,
"requireSpaceAfterKeywords": [
"else",
"case",
"try",
"typeof",
"return",
"if",
"for",
"while",
"do"
],
"requireSpaceBeforeBlockStatements": true,
"requireSpaceBeforeBinaryOperators": [
"=",
"+",
"-",
"/",
"*",
"==",
"===",
"!=",
"!=="
],
"requireSpaceBeforeBlockStatements": true,
"requireSpaceBetweenArguments": true,
"requireSpacesInConditionalExpression": true,
"requireSpacesInForStatement": true,
"requireSpacesInNamedFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"validateIndentation": 4
}

View File

@@ -1,11 +0,0 @@
{
"evil": true,
"latedef": true,
"node": true,
"undef": true,
"unused": "vars",
"noarg": true,
"eqnull": true,
"forin": true,
"predef": ["Promise"]
}

View File

@@ -1,12 +1,31 @@
language: node_js
cache:
directories:
- travis-phantomjs
node_js:
- "9"
- "8"
- "6"
- "4"
- "0.12"
- "0.10"
before_install:
# from https://github.com/travis-ci/travis-ci/issues/3225#issuecomment-177592725
# and also from https://github.com/travis-ci/travis-ci/issues/3225#issuecomment-200965782
- phantomjs --version
- export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH
- phantomjs --version
# Clear cache and download new copy of PhantomJS if the current version doesn't match 2.1.1.
- "if [ $(phantomjs --version) != '2.1.1' ]; then rm -rf $PWD/travis-phantomjs; mkdir -p $PWD/travis-phantomjs; fi"
- "if [ $(phantomjs --version) != '2.1.1' ]; then wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2; fi"
- "if [ $(phantomjs --version) != '2.1.1' ]; then tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs; fi"
- phantomjs --version
install:
- npm install -g grunt-cli
- npm install
# node 0.10 & 0.12 have race condition issues when running custom install scripts
# this can cause phantomjs-prebuilt install script to fail with the error:
# <Cannot find module 'boom'>
# Seems related to: https://github.com/npm/npm/issues/8152
# using <travis_retry> solves this.
- travis_retry npm install
env:
global:
- PHANTOMJS_CDNURL=http://cnpmjs.org/downloads

View File

@@ -1,8 +1,29 @@
# 2.7.2
# 3.0.0
2018-02-04
- Fix `calc()` function to not do math operations on compile
- Rename Directive -> AtRule & Rule -> Declaration
- Cross-platform `@plugin` loading! (Node & Browser)
- Numerous changes / improvements to plugin architecture
- Simplified API calls in plugins (`less.atrule()` vs `new less.tree.AtRule()`)
- Property accessors (`$width` to refer to `width: 300px` value)
- Inline JavaScript disabled by default for security reasons (use `@plugin`)
- Improvements in Less error reporting
- Added feature: returning `null` / `false` from Less functions will remove that line
- Simple `boolean()` and `if()` functions added
- Bug fixes
- Removal of unnecessary nodes from API (like IE's `alpha()`)
# 2.7.3
2017-10-23
- Bump `request` dependency
# 2.7.2
2017-01-04
- Revert changes to contrast() function in 2.7.0
- Revert breaking changes to contrast() function
- Fix error reporting of lessc executable
- Changed octals to hex for ES6 strict mode
# 2.7.1 HOTFIX

View File

@@ -2,10 +2,154 @@
module.exports = function (grunt) {
grunt.option('stack', true)
// Report the elapsed execution time of tasks.
require('time-grunt')(grunt);
var COMPRESS_FOR_TESTS = true;
var COMPRESS_FOR_TESTS = false;
var git = require('git-rev');
// Sauce Labs browser
var browsers = [
// Desktop browsers
{
browserName: "chrome",
version: 'latest',
platform: 'Windows 7'
},
{
browserName: "firefox",
version: 'latest',
platform: 'Linux'
},
{
browserName: 'safari',
version: '9',
platform: 'OS X 10.11'
},
{
browserName: "internet explorer",
version: '8',
platform: 'Windows XP'
},
{
browserName: "internet explorer",
version: '11',
platform: 'Windows 8.1'
},
{
browserName: "edge",
version: '13',
platform: 'Windows 10'
},
// Mobile browsers
{
browserName: 'ipad',
deviceName: 'iPad Air Simulator',
deviceOrientation: 'portrait',
version: '8.4',
platform: 'OS X 10.9'
},
{
browserName: 'iphone',
deviceName: 'iPhone 5 Simulator',
deviceOrientation: 'portrait',
version: '9.3',
platform: 'OS X 10.11'
},
{
browserName: 'android',
deviceName: 'Google Nexus 7 HD Emulator',
deviceOrientation: 'portrait',
version: '4.4',
platform: 'Linux'
}
];
var sauceJobs = {};
var browserTests = [ "filemanager-plugin",
"visitor-plugin",
"global-vars",
"modify-vars",
"production",
"rootpath-relative",
"rootpath",
"relative-urls",
"browser",
"no-js-errors",
"legacy"
];
function makeJob(testName) {
sauceJobs[testName] = {
options: {
urls: testName === 'all' ?
browserTests.map(function(name) {
return "http://localhost:8081/tmp/browser/test-runner-" + name + ".html";
}) :
["http://localhost:8081/tmp/browser/test-runner-" + testName + ".html"],
testname: testName === 'all' ? 'Unit Tests for Less.js' : testName,
browsers: browsers,
public: 'public',
recordVideo: false,
videoUploadOnPass: false,
recordScreenshots: process.env.TRAVIS_BRANCH !== "master",
build: process.env.TRAVIS_BRANCH === "master" ? process.env.TRAVIS_JOB_ID : undefined,
tags: [process.env.TRAVIS_BUILD_NUMBER, process.env.TRAVIS_PULL_REQUEST, process.env.TRAVIS_BRANCH],
statusCheckAttempts: -1,
sauceConfig: {
'idle-timeout': 100
},
throttled: 5,
onTestComplete: function(result, callback) {
// Called after a unit test is done, per page, per browser
// 'result' param is the object returned by the test framework's reporter
// 'callback' is a Node.js style callback function. You must invoke it after you
// finish your work.
// Pass a non-null value as the callback's first parameter if you want to throw an
// exception. If your function is synchronous you can also throw exceptions
// directly.
// Passing true or false as the callback's second parameter passes or fails the
// test. Passing undefined does not alter the test result. Please note that this
// only affects the grunt task's result. You have to explicitly update the Sauce
// Labs job's status via its REST API, if you want so.
// This should be the encrypted value in Travis
var user = process.env.SAUCE_USERNAME;
var pass = process.env.SAUCE_ACCESS_KEY;
git.short(function(hash) {
require('phin')({
method: 'PUT',
url: ['https://saucelabs.com/rest/v1', user, 'jobs', result.job_id].join('/'),
auth: { user: user, pass: pass },
data: {
passed: result.passed,
build: 'build-' + hash
}
}, function (error, response) {
if (error) {
console.log(error);
callback(error);
} else if (response.statusCode !== 200) {
console.log(response);
callback(new Error('Unexpected response status'));
} else {
callback(null, result.passed);
}
});
});
}
}
};
}
// Make the SauceLabs jobs
(['all'].concat(browserTests)).map(makeJob);
// Project configuration.
grunt.initConfig({
@@ -29,9 +173,15 @@ module.exports = function (grunt) {
},
shell: {
options: {stdout: true, failOnError: true},
options: {
stdout: true,
failOnError: true,
execOptions: {
maxBuffer: Infinity
}
},
test: {
command: 'node test'
command: 'node test/index.js'
},
benchmark: {
command: 'node benchmark/index.js'
@@ -107,24 +257,16 @@ module.exports = function (grunt) {
}
},
jshint: {
options: {jshintrc: '.jshintrc'},
files: {
src: [
'Gruntfile.js',
'lib/less/**/*.js',
'lib/less-node/**/*.js',
'lib/less-browser/**/*.js',
'lib/less-rhino/**/*.js',
'bin/lessc'
]
}
},
jscs: {
src: ["test/**/*.js", "lib/less*/**/*.js", "bin/lessc"],
eslint: {
target: ["Gruntfile.js",
"test/**/*.js",
"lib/less*/**/*.js",
"bin/lessc",
"!test/browser/jasmine-jsreporter.js",
"!test/less/errors/plugin/plugin-error.js"
],
options: {
config: ".jscsrc"
configFile: ".eslintrc.json"
}
},
@@ -145,7 +287,15 @@ module.exports = function (grunt) {
},
main: {
// src is used to build list of less files to compile
src: ['test/less/*.less', '!test/less/javascript.less', '!test/less/urls.less', '!test/less/empty.less'],
src: [
'test/less/*.less',
// Don't test NPM import, obviously
'!test/less/plugin-module.less',
'!test/less/import-module.less',
'!test/less/javascript.less',
'!test/less/urls.less',
'!test/less/empty.less'
],
options: {
helpers: 'test/browser/runner-main-options.js',
specs: 'test/browser/runner-main-spec.js',
@@ -186,7 +336,7 @@ module.exports = function (grunt) {
}
},
browser: {
src: ['test/browser/less/*.less'],
src: ['test/browser/less/*.less', 'test/browser/less/plugin/*.less'],
options: {
helpers: 'test/browser/runner-browser-options.js',
specs: 'test/browser/runner-browser-spec.js',
@@ -241,14 +391,6 @@ module.exports = function (grunt) {
outfile: 'tmp/browser/test-runner-global-vars.html'
}
},
postProcessor: {
src: ['test/browser/less/postProcessor/*.less'],
options: {
helpers: 'test/browser/runner-postProcessor-options.js',
specs: 'test/browser/runner-postProcessor.js',
outfile: 'tmp/browser/test-runner-post-processor.html'
}
},
postProcessorPlugin: {
src: ['test/less/postProcessorPlugin/*.less'],
options: {
@@ -283,62 +425,8 @@ module.exports = function (grunt) {
}
},
'saucelabs-jasmine': {
all: {
options: {
urls: ["filemanager-plugin","visitor-plugin","pre-processor-plugin","post-processor-plugin","post-processor", "global-vars", "modify-vars", "production", "rootpath-relative",
"rootpath", "relative-urls", "browser", "no-js-errors", "legacy", "strict-units"
].map(function(testName) {
return "http://localhost:8081/tmp/browser/test-runner-" + testName + ".html";
}),
testname: 'Sauce Unit Test for less.js',
browsers: [{
browserName: "chrome",
version: '',
platform: 'Windows 8'
},
{
browserName: "firefox",
version: '33',
platform: 'Linux'
},
{
browserName: "iPad",
version: '8.0',
platform: 'OS X 10.9',
'device-orientation': 'portrait'
},
{
browserName: "internet explorer",
version: '8',
platform: 'Windows XP'
},
{
browserName: "internet explorer",
version: '9',
platform: 'Windows 7'
},
{
browserName: "internet explorer",
version: '10',
platform: 'Windows 7'
},
{
browserName: "internet explorer",
version: '11',
platform: 'Windows 8.1'
}],
sauceConfig: {
'record-video': process.env.TRAVIS_BRANCH !== "master",
'record-screenshots': process.env.TRAVIS_BRANCH !== "master",
'idle-timeout': 100, 'max-duration': 120,
build: process.env.TRAVIS_BRANCH === "master" ? process.env.TRAVIS_JOB_ID : undefined,
tags: [process.env.TRAVIS_BUILD_NUMBER, process.env.TRAVIS_PULL_REQUEST, process.env.TRAVIS_BRANCH]
},
throttled: 3
}
}
},
'saucelabs-jasmine': sauceJobs,
// Clean the version of less built for the tests
clean: {
@@ -349,6 +437,8 @@ module.exports = function (grunt) {
});
// Load these plugins to provide the necessary tasks
grunt.loadNpmTasks('grunt-saucelabs');
require('jit-grunt')(grunt);
// Actually load this plugin's task(s).
@@ -366,7 +456,7 @@ module.exports = function (grunt) {
'uglify:dist'
]);
// Release Rhino Version
// Release Rhino Version (UNSUPPORTED)
grunt.registerTask('rhino', [
'browserify:rhino',
'concat:rhino',
@@ -415,23 +505,26 @@ module.exports = function (grunt) {
'sauce-after-setup'
]);
// setup a web server to run the browser tests in a browser rather than phantom
// var sauceTests = [];
// browserTests.map(function(testName) {
// sauceTests.push('saucelabs-jasmine:' + testName);
// });
grunt.registerTask('sauce-after-setup', [
'saucelabs-jasmine',
'saucelabs-jasmine:all',
'clean:sauce_log'
]);
var testTasks = [
'clean',
'jshint',
'jscs',
'eslint',
'shell:test',
'browsertest'
];
if (isNaN(Number(process.env.TRAVIS_PULL_REQUEST, 10)) &&
Number(process.env.TRAVIS_NODE_VERSION) === 0.11 &&
(process.env.TRAVIS_BRANCH === "master" || process.env.TRAVIS_BRANCH === "sauce")) {
Number(process.env.TRAVIS_NODE_VERSION) === 4 &&
(process.env.TRAVIS_BRANCH === "master" || process.env.TRAVIS_BRANCH === "3.x")) {
testTasks.push("force:on");
testTasks.push("sauce-after-setup");
testTasks.push("force:off");
@@ -441,7 +534,7 @@ module.exports = function (grunt) {
grunt.registerTask('test', testTasks);
// Run all tests
grunt.registerTask('quicktest', testTasks.slice(0, testTasks.length -1));
grunt.registerTask('quicktest', testTasks.slice(0, -1));
// generate a good test environment for testing sourcemaps
grunt.registerTask('sourcemap-test', [

View File

@@ -1,6 +1,9 @@
[![npm version](https://badge.fury.io/js/less.svg)](http://badge.fury.io/js/less) [![Build Status](https://travis-ci.org/less/less.js.svg?branch=master)](https://travis-ci.org/less/less.js)
[![Dependencies](https://david-dm.org/less/less.js.svg)](https://david-dm.org/less/less.js) [![devDependency Status](https://david-dm.org/less/less.js/dev-status.svg)](https://david-dm.org/less/less.js#info=devDependencies) [![optionalDependency Status](https://david-dm.org/less/less.js/optional-status.svg)](https://david-dm.org/less/less.js#info=optionalDependencies)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/less.svg)](https://saucelabs.com/u/less) [![Build status](https://ci.appveyor.com/api/projects/status/bx2qspy3qbuxpl9q/branch/master?svg=true)](https://ci.appveyor.com/project/lukeapage/less-js/branch/master)
### This is the Less 3.0 Edge branch. For the stable (2.x) branch of Less, go [here](https://github.com/less/less.js/tree/master).
[![npm version](https://badge.fury.io/js/less.svg)](http://badge.fury.io/js/less) [![Build Status](https://travis-ci.org/less/less.js.svg?branch=master)](https://travis-ci.org/less/less.js) [![Build status](https://ci.appveyor.com/api/projects/status/bx2qspy3qbuxpl9q/branch/3.x?svg=true)](https://ci.appveyor.com/project/lukeapage/less-js/branch/3.x) [![Dependencies](https://david-dm.org/less/less.js.svg)](https://david-dm.org/less/less.js) [![devDependency Status](https://david-dm.org/less/less.js/dev-status.svg)](https://david-dm.org/less/less.js#info=devDependencies) [![optionalDependency Status](https://david-dm.org/less/less.js/optional-status.svg)](https://david-dm.org/less/less.js#info=optionalDependencies) [![Twitter Follow](https://img.shields.io/twitter/follow/lesstocss.svg?style=flat-square)](https://twitter.com/lesstocss) [![Join the chat at https://gitter.im/less/less.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/less/less.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) <sup>Chat with Less.js users</sup>
[![Sauce Test Status](https://saucelabs.com/browser-matrix/less.svg)](https://saucelabs.com/u/less)
# [Less.js](http://lesscss.org)
@@ -8,8 +11,6 @@
This is the JavaScript, official, stable version of Less.
###### :point_right: [![Join the chat at https://gitter.im/less/less.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/less/less.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) <sup>Chat with Less.js users</sup>
## Getting Started
@@ -47,7 +48,7 @@ See the [changelog](CHANGELOG.md)
## [License](LICENSE)
Copyright (c) 2009-2016 [Alexis Sellier](http://cloudhead.io) & The Core Less Team
Copyright (c) 2009-2017 [Alexis Sellier](http://cloudhead.io) & The Core Less Team
Licensed under the [Apache License](LICENSE).

View File

@@ -1,21 +1,20 @@
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.10"
- nodejs_version: "0.12"
- nodejs_version: "4"
- nodejs_version: "6"
- nodejs_version: "8"
- nodejs_version: "9"
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node 0.STABLE.latest
- ps: Install-Product node $env:nodejs_version
# Use npm v2
- npm -g install npm@2
- set PATH=%APPDATA%\npm;%PATH%
- npm -v
# Typical npm stuff.
- npm install
# node 0.10 & 0.12 have race condition issues when running custom install scripts
# this can cause phantomjs-prebuilt install script to fail with the error:
# <Cannot find module 'boom'>
# Seems related to: https://github.com/npm/npm/issues/8152
# using <appveyor_retry> solves this.
- appveyor-retry call npm install
# Grunt-specific stuff.
- npm install -g grunt-cli

View File

@@ -1,5 +1,6 @@
var path = require('path'),
fs = require('fs');
fs = require('fs'),
now = require("performance-now");
var less = require('../lib/less-node');
var file = path.join(__dirname, 'benchmark.less');
@@ -7,49 +8,86 @@ var file = path.join(__dirname, 'benchmark.less');
if (process.argv[2]) { file = path.join(process.cwd(), process.argv[2]) }
fs.readFile(file, 'utf8', function (e, data) {
var start, end, total;
var start, total;
console.log("Benchmarking...\n", path.basename(file) + " (" +
parseInt(data.length / 1024) + " KB)", "");
var benchMarkData = [];
var renderBenchmark = []
, parserBenchmark = []
, evalBenchmark = [];
var totalruns = 100;
var ignoreruns = 30;
var totalruns = 30;
var ignoreruns = 5;
for(var i = 0; i < totalruns; i++) {
start = new Date();
var i = 0;
less.render(data, function (err) {
end = new Date();
nextRun();
benchMarkData.push(end - start);
function nextRun() {
var start, renderEnd, parserEnd;
start = now();
less.parse(data, {}, function(err, root, imports, options) {
if (err) {
less.writeError(err);
process.exit(3);
}
parserEnd = now();
var tree, result;
tree = new less.ParseTree(root, imports);
result = tree.toCSS(options);
renderEnd = now();
renderBenchmark.push(renderEnd - start);
parserBenchmark.push(parserEnd - start);
evalBenchmark.push(renderEnd - parserEnd);
i += 1;
//console.log('Less Run #: ' + i);
if(i < totalruns) {
nextRun();
}
else {
finish();
}
});
}
var totalTime = 0;
var mintime = Infinity;
var maxtime = 0;
for(var i = ignoreruns; i < totalruns; i++) {
totalTime += benchMarkData[i];
mintime = Math.min(mintime, benchMarkData[i]);
maxtime = Math.max(maxtime, benchMarkData[i]);
}
var avgtime = totalTime / (totalruns - ignoreruns);
var variation = maxtime - mintime;
var variationperc = (variation / avgtime) * 100;
function finish() {
function analyze(benchmark, benchMarkData) {
console.log('----------------------');
console.log(benchmark);
console.log('----------------------');
var totalTime = 0;
var mintime = Infinity;
var maxtime = 0;
for(var i = ignoreruns; i < totalruns; i++) {
totalTime += benchMarkData[i];
mintime = Math.min(mintime, benchMarkData[i]);
maxtime = Math.max(maxtime, benchMarkData[i]);
}
var avgtime = totalTime / (totalruns - ignoreruns);
var variation = maxtime - mintime;
var variationperc = (variation / avgtime) * 100;
console.log("Min. Time: "+mintime + " ms");
console.log("Max. Time: "+maxtime + " ms");
console.log("Total Average Time: " + avgtime + " ms (" +
parseInt(1000 / avgtime *
data.length / 1024) + " KB\/s)");
console.log("+/- " + variationperc + "%");
console.log("Min. Time: " + Math.round(mintime) + " ms");
console.log("Max. Time: " + Math.round(maxtime) + " ms");
console.log("Total Average Time: " + Math.round(avgtime) + " ms (" +
parseInt(1000 / avgtime *
data.length / 1024) + " KB\/s)");
console.log("+/- " + Math.round(variationperc) + "%");
console.log("");
}
analyze('Parsing', parserBenchmark);
analyze('Evaluation', evalBenchmark);
analyze('Render Time', renderBenchmark);
}
});

449
bin/lessc
View File

@@ -14,31 +14,15 @@ try {
var less = require('../lib/less-node'),
pluginLoader = new less.PluginLoader(less),
plugin,
plugins = [];
var args = process.argv.slice(1);
var silent = false,
plugins = [],
queuePlugins = [],
args = process.argv.slice(1),
silent = false,
verbose = false,
options = {
depends: false,
compress: false,
max_line_len: -1,
lint: false,
paths: [],
color: true,
strictImports: false,
insecure: false,
rootpath: '',
relativeUrls: false,
ieCompat: true,
strictMath: false,
strictUnits: false,
globalVars: null,
modifyVars: null,
urlArgs: '',
plugins: plugins
};
options = less.options;
options.plugins = plugins;
var sourceMapOptions = {};
var continueProcessing = true;
@@ -94,194 +78,7 @@ function printUsage() {
pluginLoader.printUsage(plugins);
continueProcessing = false;
}
// self executing function so we can return
(function() {
args = args.filter(function (arg) {
var match;
match = arg.match(/^-I(.+)$/);
if (match) {
options.paths.push(match[1]);
return false;
}
match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
if (match) {
arg = match[1];
} else {
return arg;
}
switch (arg) {
case 'v':
case 'version':
console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]");
continueProcessing = false;
break;
case 'verbose':
verbose = true;
break;
case 's':
case 'silent':
silent = true;
break;
case 'l':
case 'lint':
options.lint = true;
break;
case 'strict-imports':
options.strictImports = true;
break;
case 'h':
case 'help':
printUsage();
break;
case 'x':
case 'compress':
options.compress = true;
break;
case 'insecure':
options.insecure = true;
break;
case 'M':
case 'depends':
options.depends = true;
break;
case 'max-line-len':
if (checkArgFunc(arg, match[2])) {
options.maxLineLen = parseInt(match[2], 10);
if (options.maxLineLen <= 0) {
options.maxLineLen = -1;
}
}
break;
case 'no-color':
options.color = false;
break;
case 'no-ie-compat':
options.ieCompat = false;
break;
case 'no-js':
options.javascriptEnabled = false;
break;
case 'include-path':
if (checkArgFunc(arg, match[2])) {
// ; supported on windows.
// : supported on windows and linux, excluding a drive letter like C:\ so C:\file:D:\file parses to 2
options.paths = match[2]
.split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':')
.map(function(p) {
if (p) {
return path.resolve(process.cwd(), p);
}
});
}
break;
case 'line-numbers':
if (checkArgFunc(arg, match[2])) {
options.dumpLineNumbers = match[2];
}
break;
case 'source-map':
options.sourceMap = true;
if (match[2]) {
sourceMapOptions.sourceMapFullFilename = match[2];
}
break;
case 'source-map-rootpath':
if (checkArgFunc(arg, match[2])) {
sourceMapOptions.sourceMapRootpath = match[2];
}
break;
case 'source-map-basepath':
if (checkArgFunc(arg, match[2])) {
sourceMapOptions.sourceMapBasepath = match[2];
}
break;
case 'source-map-map-inline':
sourceMapFileInline = true;
options.sourceMap = true;
break;
case 'source-map-less-inline':
sourceMapOptions.outputSourceFiles = true;
break;
case 'source-map-url':
if (checkArgFunc(arg, match[2])) {
sourceMapOptions.sourceMapURL = match[2];
}
break;
case 'rp':
case 'rootpath':
if (checkArgFunc(arg, match[2])) {
options.rootpath = match[2].replace(/\\/g, '/');
}
break;
case "ru":
case "relative-urls":
options.relativeUrls = true;
break;
case "sm":
case "strict-math":
if (checkArgFunc(arg, match[2])) {
options.strictMath = checkBooleanArg(match[2]);
}
break;
case "su":
case "strict-units":
if (checkArgFunc(arg, match[2])) {
options.strictUnits = checkBooleanArg(match[2]);
}
break;
case "global-var":
if (checkArgFunc(arg, match[2])) {
if (!options.globalVars) {
options.globalVars = {};
}
parseVariableOption(match[2], options.globalVars);
}
break;
case "modify-var":
if (checkArgFunc(arg, match[2])) {
if (!options.modifyVars) {
options.modifyVars = {};
}
parseVariableOption(match[2], options.modifyVars);
}
break;
case 'url-args':
if (checkArgFunc(arg, match[2])) {
options.urlArgs = match[2];
}
break;
case 'plugin':
var splitupArg = match[2].match(/^([^=]+)(=(.*))?/),
name = splitupArg[1],
pluginOptions = splitupArg[3];
plugin = pluginLoader.tryLoadPlugin(name, pluginOptions);
if (plugin) {
plugins.push(plugin);
} else {
console.error("Unable to load plugin " + name +
" please make sure that it is installed under or at the same level as less");
process.exitCode = 1;
}
break;
default:
plugin = pluginLoader.tryLoadPlugin("less-plugin-" + arg, match[2]);
if (plugin) {
plugins.push(plugin);
} else {
console.error("Unable to interpret argument " + arg +
" - if it is a plugin (less-plugin-" + arg + "), make sure that it is installed under or at" +
" the same level as less");
process.exitCode = 1;
}
break;
}
});
function render() {
if (!continueProcessing) {
return;
@@ -340,7 +137,7 @@ function printUsage() {
sourceMapOptions.sourceMapRootpath = path.relative(pathToMap, pathToInput);
}
if (! input) {
if (!input) {
console.error("lessc: no input files");
console.error("");
printUsage();
@@ -355,7 +152,7 @@ function printUsage() {
if (!existsSync(dir)) {
if (mkdirp === undefined) {
try {mkdirp = require('mkdirp');}
catch(e) { mkdirp = null; }
catch (e) { mkdirp = null; }
}
cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
cmd(dir);
@@ -489,7 +286,11 @@ function printUsage() {
}
},
function(err) {
less.writeError(err, options);
if (!options.silent) {
console.error(err.toString({
stylize: options.color && less.lesscHelper.stylize
}));
}
process.exitCode = 1;
});
};
@@ -509,4 +310,222 @@ function printUsage() {
parseLessFile(false, buffer);
});
}
}
function processPluginQueue() {
var x = 0;
function pluginError(name) {
console.error("Unable to load plugin " + name +
" please make sure that it is installed under or at the same level as less");
process.exitCode = 1;
}
function pluginFinished(plugin) {
x++;
plugins.push(plugin);
if (x === queuePlugins.length) {
render();
}
}
queuePlugins.forEach(function(queue) {
pluginLoader.tryLoadPlugin(queue.name, function(err, data) {
if (err) {
pluginError(queue.name);
}
else {
pluginFinished({
fileContent: data.contents,
filename: data.filename,
options: queue.options
});
}
});
});
}
// self executing function so we can return
(function() {
args = args.filter(function (arg) {
var match;
match = arg.match(/^-I(.+)$/);
if (match) {
options.paths.push(match[1]);
return false;
}
match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
if (match) {
arg = match[1];
} else {
return arg;
}
switch (arg) {
case 'v':
case 'version':
console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]");
continueProcessing = false;
break;
case 'verbose':
verbose = true;
break;
case 's':
case 'silent':
silent = true;
break;
case 'l':
case 'lint':
options.lint = true;
break;
case 'strict-imports':
options.strictImports = true;
break;
case 'h':
case 'help':
printUsage();
break;
case 'x':
case 'compress':
options.compress = true;
break;
case 'insecure':
options.insecure = true;
break;
case 'M':
case 'depends':
options.depends = true;
break;
case 'max-line-len':
if (checkArgFunc(arg, match[2])) {
options.maxLineLen = parseInt(match[2], 10);
if (options.maxLineLen <= 0) {
options.maxLineLen = -1;
}
}
break;
case 'no-color':
options.color = false;
break;
case 'ie-compat':
options.ieCompat = true;
break;
case 'js':
options.javascriptEnabled = true;
break;
case 'no-js':
console.error('The "--no-js" argument is deprecated, as inline JavaScript ' +
'is disabled by default. Use "--js" to enable inline JavaScript (not recommended).');
break;
case 'include-path':
if (checkArgFunc(arg, match[2])) {
// ; supported on windows.
// : supported on windows and linux, excluding a drive letter like C:\ so C:\file:D:\file parses to 2
options.paths = match[2]
.split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':')
.map(function(p) {
if (p) {
return path.resolve(process.cwd(), p);
}
});
}
break;
case 'line-numbers':
if (checkArgFunc(arg, match[2])) {
options.dumpLineNumbers = match[2];
}
break;
case 'source-map':
options.sourceMap = true;
if (match[2]) {
sourceMapOptions.sourceMapFullFilename = match[2];
}
break;
case 'source-map-rootpath':
if (checkArgFunc(arg, match[2])) {
sourceMapOptions.sourceMapRootpath = match[2];
}
break;
case 'source-map-basepath':
if (checkArgFunc(arg, match[2])) {
sourceMapOptions.sourceMapBasepath = match[2];
}
break;
case 'source-map-inline':
case 'source-map-map-inline':
sourceMapFileInline = true;
options.sourceMap = true;
break;
case 'source-map-include-source':
case 'source-map-less-inline':
sourceMapOptions.outputSourceFiles = true;
break;
case 'source-map-url':
if (checkArgFunc(arg, match[2])) {
sourceMapOptions.sourceMapURL = match[2];
}
break;
case 'rp':
case 'rootpath':
if (checkArgFunc(arg, match[2])) {
options.rootpath = match[2].replace(/\\/g, '/');
}
break;
case "ru":
case "relative-urls":
options.relativeUrls = true;
break;
case "sm":
case "strict-math":
if (checkArgFunc(arg, match[2])) {
options.strictMath = checkBooleanArg(match[2]);
}
break;
case "su":
case "strict-units":
if (checkArgFunc(arg, match[2])) {
options.strictUnits = checkBooleanArg(match[2]);
}
break;
case "global-var":
if (checkArgFunc(arg, match[2])) {
if (!options.globalVars) {
options.globalVars = {};
}
parseVariableOption(match[2], options.globalVars);
}
break;
case "modify-var":
if (checkArgFunc(arg, match[2])) {
if (!options.modifyVars) {
options.modifyVars = {};
}
parseVariableOption(match[2], options.modifyVars);
}
break;
case 'url-args':
if (checkArgFunc(arg, match[2])) {
options.urlArgs = match[2];
}
break;
case 'plugin':
var splitupArg = match[2].match(/^([^=]+)(=(.*))?/),
name = splitupArg[1],
pluginOptions = splitupArg[3];
queuePlugins.push({ name: name, options: pluginOptions });
break;
default:
queuePlugins.push({ name: arg, options: match[2], default: true });
break;
}
});
if (queuePlugins.length > 0) {
processPluginQueue();
}
else {
render();
}
})();

View File

@@ -43,4 +43,6 @@ module.exports = function(window, options) {
options.onReady = true;
}
options.javascriptEnabled = (options.javascriptEnabled || options.inlineJavaScript) ? true : false;
};

View File

@@ -3,14 +3,30 @@
* used in the browser distributed version of less
* to kick-start less using the browser api
*/
/*global window, document */
/* global window, document */
// shim Promise if required
require('promise/polyfill.js');
// TODO - consider switching this out for a recommendation for this polyfill?
// <script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
// Browsers have good Promise support
require("promise/polyfill");
var options = window.less || {};
var options = require('../less/default-options')();
if (window.less) {
for (key in window.less) {
if (window.less.hasOwnProperty(key)) {
options[key] = window.less[key];
}
}
}
require("./add-default-options")(window, options);
options.plugins = options.plugins || [];
if (window.LESS_PLUGINS) {
options.plugins = options.plugins.concat(window.LESS_PLUGINS);
}
var less = module.exports = require("./index")(window, options);
window.less = less;
@@ -31,7 +47,7 @@ if (options.onReady) {
if (/!watch/.test(window.location.hash)) {
less.watch();
}
// Simulate synchronous stylesheet loading by blocking page rendering
// Simulate synchronous stylesheet loading by hiding page rendering
if (!options.async) {
css = 'body { display: none !important }';
head = document.head || document.getElementsByTagName('head')[0];

View File

@@ -17,8 +17,8 @@ module.exports = function(window, options, logger) {
if (modifyVars) {
cache.setItem(path + ':vars', JSON.stringify(modifyVars));
}
} catch(e) {
//TODO - could do with adding more robust error handling
} catch (e) {
// TODO - could do with adding more robust error handling
logger.error('failed to save "' + path + '" to local storage for caching.');
}
}

View File

@@ -24,7 +24,7 @@ module.exports = function(window, less, options) {
}
};
if (e.extract) {
if (e.line) {
errorline(e, 0, '');
errorline(e, 1, 'line');
errorline(e, 2, '');
@@ -112,7 +112,7 @@ module.exports = function(window, less, options) {
}
function removeErrorConsole(path) {
//no action
// no action
}
function removeError(path) {
@@ -130,7 +130,7 @@ module.exports = function(window, less, options) {
var filename = e.filename || rootHref;
var errors = [];
var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
" in " + filename + " ";
" in " + filename;
var errorline = function (e, i, classname) {
if (e.extract[i] !== undefined) {
@@ -140,11 +140,11 @@ module.exports = function(window, less, options) {
}
};
if (e.extract) {
if (e.line) {
errorline(e, 0, '');
errorline(e, 1, 'line');
errorline(e, 2, '');
content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' +
content += ' on line ' + e.line + ', column ' + (e.column + 1) + ':\n' +
errors.join('\n');
}
if (e.stack && (e.extract || options.logLevel >= 4)) {

View File

@@ -1,4 +1,4 @@
/*global window, XMLHttpRequest */
/* global window, XMLHttpRequest */
module.exports = function(options, logger) {
@@ -6,22 +6,7 @@ module.exports = function(options, logger) {
var fileCache = {};
//TODOS - move log somewhere. pathDiff and doing something similar in node. use pathDiff in the other browser file for the initial load
function getXMLHttpRequest() {
if (window.XMLHttpRequest && (window.location.protocol !== "file:" || !("ActiveXObject" in window))) {
return new XMLHttpRequest();
} else {
try {
/*global ActiveXObject */
return new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
logger.error("browser doesn't support AJAX.");
return null;
}
}
}
// TODOS - move log somewhere. pathDiff and doing something similar in node. use pathDiff in the other browser file for the initial load
var FileManager = function() {
};
@@ -38,7 +23,7 @@ module.exports = function(options, logger) {
};
FileManager.prototype.doXHR = function doXHR(url, type, callback, errback) {
var xhr = getXMLHttpRequest();
var xhr = new XMLHttpRequest();
var async = options.isFileProtocol ? options.fileAsync : true;
if (typeof xhr.overrideMimeType === 'function') {
@@ -82,36 +67,43 @@ module.exports = function(options, logger) {
fileCache = {};
};
FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, options, environment, callback) {
FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, options, environment) {
// TODO: Add prefix support like less-node?
// What about multiple paths?
if (currentDirectory && !this.isPathAbsolute(filename)) {
filename = currentDirectory + filename;
}
filename = options.ext ? this.tryAppendExtension(filename, options.ext) : filename;
options = options || {};
// sheet may be set to the stylesheet for the initial load or a collection of properties including
// some context variables for imports
var hrefParts = this.extractUrlParts(filename, window.location.href);
var href = hrefParts.url;
if (options.useFileCache && fileCache[href]) {
try {
var lessText = fileCache[href];
callback(null, { contents: lessText, filename: href, webInfo: { lastModified: new Date() }});
} catch (e) {
callback({filename: href, message: "Error loading file " + href + " error was " + e.message});
var self = this;
return new Promise(function(resolve, reject) {
if (options.useFileCache && fileCache[href]) {
try {
var lessText = fileCache[href];
return resolve({ contents: lessText, filename: href, webInfo: { lastModified: new Date() }});
} catch (e) {
return reject({ filename: href, message: "Error loading file " + href + " error was " + e.message });
}
}
return;
}
this.doXHR(href, options.mime, function doXHRCallback(data, lastModified) {
// per file cache
fileCache[href] = data;
self.doXHR(href, options.mime, function doXHRCallback(data, lastModified) {
// per file cache
fileCache[href] = data;
// Use remote copy (re-parse)
callback(null, { contents: data, filename: href, webInfo: { lastModified: lastModified }});
}, function doXHRError(status, url) {
callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")", href: href });
// Use remote copy (re-parse)
resolve({ contents: data, filename: href, webInfo: { lastModified: lastModified }});
}, function doXHRError(status, url) {
reject({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")", href: href });
});
});
};

View File

@@ -8,42 +8,29 @@ var addDataAttr = require("./utils").addDataAttr,
module.exports = function(window, options) {
var document = window.document;
var less = require('../less')();
//module.exports = less;
less.options = options;
var environment = less.environment,
FileManager = require("./file-manager")(options, less.logger),
fileManager = new FileManager();
environment.addFileManager(fileManager);
less.FileManager = FileManager;
less.PluginLoader = require("./plugin-loader");
require("./log-listener")(less, options);
var errors = require("./error-reporting")(window, less, options);
var cache = less.cache = options.cache || require("./cache")(window, options, less.logger);
require('./image-size')(less.environment);
//Setup user functions
// Setup user functions - Deprecate?
if (options.functions) {
less.functions.functionRegistry.addMultiple(options.functions);
}
var typePattern = /^text\/(x-)?less$/;
function postProcessCSS(styles) { // deprecated, use a plugin for postprocesstasks
if (options.postProcessor && typeof options.postProcessor === 'function') {
styles = options.postProcessor.call(styles, styles) || styles;
}
return styles;
}
function clone(obj) {
var cloned = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
cloned[prop] = obj[prop];
}
}
return cloned;
return JSON.parse(JSON.stringify(obj || {}));
}
// only really needed for phantom
@@ -67,7 +54,7 @@ module.exports = function(window, options) {
var lessText = style.innerHTML || '';
instanceOptions.filename = document.location.href.replace(/#.*$/, '');
/*jshint loopfunc:true */
/* jshint loopfunc:true */
// use closure to store current style
less.render(lessText, instanceOptions,
bind(function(style, e, result) {
@@ -123,7 +110,7 @@ module.exports = function(window, options) {
}
//TODO add tests around how this behaves when reloading
// TODO add tests around how this behaves when reloading
errors.remove(path);
instanceOptions.rootFileInfo = newFileInfo;
@@ -132,20 +119,20 @@ module.exports = function(window, options) {
e.href = path;
callback(e);
} else {
result.css = postProcessCSS(result.css);
cache.setCSS(sheet.href, webInfo.lastModified, instanceOptions.modifyVars, result.css);
callback(null, result.css, data, sheet, webInfo, path);
}
});
}
fileManager.loadFile(sheet.href, null, instanceOptions, environment, function(e, loadedFile) {
if (e) {
callback(e);
return;
}
loadInitialFileCallback(loadedFile);
});
fileManager.loadFile(sheet.href, null, instanceOptions, environment)
.then(function(loadedFile) {
loadInitialFileCallback(loadedFile);
}).catch(function(err) {
console.log(err);
callback(err);
});
}
function loadStyleSheets(callback, reload, modifyVars) {

View File

@@ -0,0 +1,25 @@
// TODO: Add tests for browser @plugin
/*global window */
var AbstractPluginLoader = require("../less/environment/abstract-plugin-loader.js");
/**
* Browser Plugin Loader
*/
var PluginLoader = function(less) {
this.less = less;
// shim for browser require?
this.require = require;
};
PluginLoader.prototype = new AbstractPluginLoader();
PluginLoader.prototype.loadPlugin = function(filename, basePath, context, environment, fileManager) {
return new Promise(function(fulfill, reject) {
fileManager.loadFile(filename, basePath, context, environment)
.then(fulfill).catch(reject);
});
};
module.exports = PluginLoader;

View File

@@ -16,7 +16,7 @@ module.exports = {
try {
options[opt] = JSON.parse(tag.dataset[opt]);
}
catch(_) {}
catch (_) {}
}
}
}

View File

@@ -1,14 +1,10 @@
var path = require('path'),
fs = require('./fs'),
PromiseConstructor,
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
AbstractFileManager = require("../less/environment/abstract-file-manager.js");
try {
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
} catch(e) {
}
var FileManager = function() {
this.files = {};
};
FileManager.prototype = new AbstractFileManager();
@@ -21,88 +17,122 @@ FileManager.prototype.supportsSync = function(filename, currentDirectory, option
};
FileManager.prototype.loadFile = function(filename, currentDirectory, options, environment, callback) {
var fullFilename,
data,
isAbsoluteFilename = this.isPathAbsolute(filename),
filenamesTried = [];
filenamesTried = [],
self = this,
prefix = filename.slice(0, 1),
explicit = prefix === "." || prefix === "/",
result = null;
options = options || {};
if (options.syncImport || !PromiseConstructor) {
data = this.loadFileSync(filename, currentDirectory, options, environment, 'utf-8');
callback(data.error, data);
return;
}
var paths = isAbsoluteFilename ? [''] : [currentDirectory];
var paths = isAbsoluteFilename ? [""] : [currentDirectory];
if (options.paths) { paths.push.apply(paths, options.paths); }
// Search node_modules
if (!explicit) { paths.push.apply(paths, this.modulePaths); }
if (!isAbsoluteFilename && paths.indexOf('.') === -1) { paths.push('.'); }
// promise is guaranteed to be asyncronous
// which helps as it allows the file handle
// to be closed before it continues with the next file
return new PromiseConstructor(function(fulfill, reject) {
var prefixes = options.prefixes || [''];
var fileParts = this.extractUrlParts(filename);
if (options.syncImport) {
getFileData(returnData, returnData);
if (callback) {
callback(result.error, result);
}
else {
return result;
}
}
else {
// promise is guaranteed to be asyncronous
// which helps as it allows the file handle
// to be closed before it continues with the next file
return new PromiseConstructor(getFileData);
}
function returnData(data) {
if (!data.filename) {
result = { error: data };
}
else {
result = data;
}
}
function getFileData(fulfill, reject) {
(function tryPathIndex(i) {
if (i < paths.length) {
fullFilename = filename;
if (paths[i]) {
fullFilename = path.join(paths[i], fullFilename);
}
fs.stat(fullFilename, function (err) {
if (err) {
filenamesTried.push(fullFilename);
tryPathIndex(i + 1);
} else {
fs.readFile(fullFilename, 'utf-8', function(e, data) {
if (e) { reject(e); return; }
(function tryPrefix(j) {
if (j < prefixes.length) {
fullFilename = fileParts.rawPath + prefixes[j] + fileParts.filename;
if (paths[i]) {
fullFilename = path.join(paths[i], fullFilename);
}
if (paths[i].indexOf('node_modules') > -1) {
try {
fullFilename = require.resolve(fullFilename);
}
catch (e) {}
}
fullFilename = options.ext ? self.tryAppendExtension(fullFilename, options.ext) : fullFilename;
if (self.files[fullFilename]) {
fulfill({ contents: self.files[fullFilename], filename: fullFilename});
}
else {
var readFileArgs = [fullFilename];
if (!options.rawBuffer) {
readFileArgs.push('utf-8');
}
if (options.syncImport) {
try {
var data = fs.readFileSync.apply(this, readFileArgs);
self.files[fullFilename] = data;
fulfill({ contents: data, filename: fullFilename});
}
catch (e) {
filenamesTried.push(fullFilename);
return tryPrefix(j + 1);
}
}
else {
readFileArgs.push(function(e, data) {
if (e) {
filenamesTried.push(fullFilename);
return tryPrefix(j + 1);
}
self.files[fullFilename] = data;
fulfill({ contents: data, filename: fullFilename});
});
fs.readFile.apply(this, readFileArgs);
}
}
fulfill({ contents: data, filename: fullFilename});
});
}
});
else {
tryPathIndex(i + 1);
}
})(0);
} else {
reject({ type: 'File', message: "'" + filename + "' wasn't found. Tried - " + filenamesTried.join(",") });
}
}(0));
});
}
};
FileManager.prototype.loadFileSync = function(filename, currentDirectory, options, environment, encoding) {
var fullFilename, paths, filenamesTried = [], isAbsoluteFilename = this.isPathAbsolute(filename) , data;
options = options || {};
paths = isAbsoluteFilename ? [""] : [currentDirectory];
if (options.paths) {
paths.push.apply(paths, options.paths);
}
if (!isAbsoluteFilename && paths.indexOf('.') === -1) {
paths.push('.');
}
var err, result;
for (var i = 0; i < paths.length; i++) {
try {
fullFilename = filename;
if (paths[i]) {
fullFilename = path.join(paths[i], fullFilename);
}
filenamesTried.push(fullFilename);
fs.statSync(fullFilename);
break;
} catch (e) {
fullFilename = null;
}
}
if (!fullFilename) {
err = { type: 'File', message: "'" + filename + "' wasn't found. Tried - " + filenamesTried.join(",") };
result = { error: err };
} else {
data = fs.readFileSync(fullFilename, encoding);
result = { contents: data, filename: fullFilename};
}
return result;
FileManager.prototype.loadFileSync = function(filename, currentDirectory, options, environment) {
options.syncImport = true;
return this.loadFile(filename, currentDirectory, options, environment);
};
module.exports = FileManager;

View File

@@ -3,7 +3,7 @@ try
{
fs = require("graceful-fs");
}
catch(e)
catch (e)
{
fs = require("fs");
}

View File

@@ -1,7 +1,7 @@
module.exports = function(environment) {
var Dimension = require("../less/tree/dimension"),
Expression = require("../less/tree/expression"),
functionRegistry = require("./../less/functions/function-registry");
Expression = require("../less/tree/expression"),
functionRegistry = require("./../less/functions/function-registry");
function imageSize(functionContext, filePathNode) {
var filePath = filePathNode.value;

View File

@@ -3,7 +3,8 @@ var environment = require("./environment"),
UrlFileManager = require("./url-file-manager"),
createFromEnvironment = require("../less"),
less = createFromEnvironment(environment, [new FileManager(), new UrlFileManager()]),
lesscHelper = require('./lessc-helper');
lesscHelper = require('./lessc-helper'),
path = require('path');
// allow people to create less with their own environment
less.createFromEnvironment = createFromEnvironment;
@@ -12,61 +13,12 @@ less.PluginLoader = require("./plugin-loader");
less.fs = require("./fs");
less.FileManager = FileManager;
less.UrlFileManager = UrlFileManager;
less.formatError = function(ctx, options) {
options = options || {};
var message = "";
var extract = ctx.extract;
var error = [];
var stylize = options.color ? lesscHelper.stylize : function (str) { return str; };
// only output a stack if it isn't a less error
if (ctx.stack && !ctx.type) { return stylize(ctx.stack, 'red'); }
if (!ctx.hasOwnProperty('index') || !extract) {
return ctx.stack || ctx.message;
}
if (typeof extract[0] === 'string') {
error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
}
if (typeof extract[1] === 'string') {
var errorTxt = ctx.line + ' ';
if (extract[1]) {
errorTxt += extract[1].slice(0, ctx.column) +
stylize(stylize(stylize(extract[1].substr(ctx.column, 1), 'bold') +
extract[1].slice(ctx.column + 1), 'red'), 'inverse');
}
error.push(errorTxt);
}
if (typeof extract[2] === 'string') {
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
}
error = error.join('\n') + stylize('', 'reset') + '\n';
message += stylize(ctx.type + 'Error: ' + ctx.message, 'red');
if (ctx.filename) {
message += stylize(' in ', 'red') + ctx.filename +
stylize(' on line ' + ctx.line + ', column ' + (ctx.column + 1) + ':', 'grey');
}
message += '\n' + error;
if (ctx.callLine) {
message += stylize('from ', 'red') + (ctx.filename || '') + '/n';
message += stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract + '/n';
}
return message;
};
less.writeError = function (ctx, options) {
options = options || {};
if (options.silent) { return; }
console.error(less.formatError(ctx, options));
};
// Set up options
less.options = require('../less/default-options')();
less.options.paths = [
path.join(process.cwd(), "node_modules")
];
// provide image-size functionality
require('./image-size')(less.environment);

View File

@@ -3,7 +3,7 @@
// helper functions for lessc
var lessc_helper = {
//Stylize a string
// Stylize a string
stylize : function(str, style) {
var styles = {
'reset' : [0, 0],
@@ -19,49 +19,49 @@ var lessc_helper = {
'\x1b[' + styles[style][1] + 'm';
},
//Print command line options
// Print command line options
printUsage: function() {
console.log("usage: lessc [option option=parameter ...] <source> [destination]");
console.log("");
console.log("If source is set to `-' (dash or hyphen-minus), input is read from stdin.");
console.log("");
console.log("options:");
console.log(" -h, --help Prints help (this message) and exit.");
console.log(" --include-path=PATHS Sets include paths. Separated by `:'. `;' also supported on windows.");
console.log(" -M, --depends Outputs a makefile import dependency list to stdout.");
console.log(" --no-color Disables colorized output.");
console.log(" --no-ie-compat Disables IE compatibility checks.");
console.log(" --no-js Disables JavaScript in less files");
console.log(" -l, --lint Syntax check only (lint).");
console.log(" -s, --silent Suppresses output of error messages.");
console.log(" --strict-imports Forces evaluation of imports.");
console.log(" --insecure Allows imports from insecure https hosts.");
console.log(" -v, --version Prints version number and exit.");
console.log(" --verbose Be verbose.");
console.log(" --source-map[=FILENAME] Outputs a v3 sourcemap to the filename (or output filename.map).");
console.log(" --source-map-rootpath=X Adds this path onto the sourcemap filename and less file paths.");
console.log(" --source-map-basepath=X Sets sourcemap base path, defaults to current working directory.");
console.log(" --source-map-less-inline Puts the less files into the map instead of referencing them.");
console.log(" --source-map-map-inline Puts the map (and any less files) as a base64 data uri into the output css file.");
console.log(" --source-map-url=URL Sets a custom URL to map file, for sourceMappingURL comment");
console.log(" in generated CSS file.");
console.log(" -rp, --rootpath=URL Sets rootpath for url rewriting in relative imports and urls");
console.log(" Works with or without the relative-urls option.");
console.log(" -ru, --relative-urls Re-writes relative urls to the base less file.");
console.log(" -sm=on|off Turns on or off strict math, where in strict mode, math.");
console.log(" --strict-math=on|off Requires brackets. This option may default to on and then");
console.log(" be removed in the future.");
console.log(" -su=on|off Allows mixed units, e.g. 1px+1em or 1px*1px which have units");
console.log(" --strict-units=on|off that cannot be represented.");
console.log(" --global-var='VAR=VALUE' Defines a variable that can be referenced by the file.");
console.log(" --modify-var='VAR=VALUE' Modifies a variable already declared in the file.");
console.log(" --url-args='QUERYSTRING' Adds params into url tokens (e.g. 42, cb=42 or 'a=1&b=2')");
console.log(" --plugin=PLUGIN=OPTIONS Loads a plugin. You can also omit the --plugin= if the plugin begins");
console.log(" less-plugin. E.g. the clean css plugin is called less-plugin-clean-css");
console.log(" once installed (npm install less-plugin-clean-css), use either with");
console.log(" --plugin=less-plugin-clean-css or just --clean-css");
console.log(" specify options afterwards e.g. --plugin=less-plugin-clean-css=\"advanced\"");
console.log(" or --clean-css=\"advanced\"");
console.log(" -h, --help Prints help (this message) and exit.");
console.log(" --include-path=PATHS Sets include paths. Separated by `:'. `;' also supported on windows.");
console.log(" -M, --depends Outputs a makefile import dependency list to stdout.");
console.log(" --no-color Disables colorized output.");
console.log(" --ie-compat Enables IE8 compatibility checks.");
console.log(" --js Enables inline JavaScript in less files");
console.log(" -l, --lint Syntax check only (lint).");
console.log(" -s, --silent Suppresses output of error messages.");
console.log(" --strict-imports Forces evaluation of imports.");
console.log(" --insecure Allows imports from insecure https hosts.");
console.log(" -v, --version Prints version number and exit.");
console.log(" --verbose Be verbose.");
console.log(" --source-map[=FILENAME] Outputs a v3 sourcemap to the filename (or output filename.map).");
console.log(" --source-map-rootpath=X Adds this path onto the sourcemap filename and less file paths.");
console.log(" --source-map-basepath=X Sets sourcemap base path, defaults to current working directory.");
console.log(" --source-map-include-source Puts the less files into the map instead of referencing them.");
console.log(" --source-map-inline Puts the map (and any less files) as a base64 data uri into the output css file.");
console.log(" --source-map-url=URL Sets a custom URL to map file, for sourceMappingURL comment");
console.log(" in generated CSS file.");
console.log(" -rp, --rootpath=URL Sets rootpath for url rewriting in relative imports and urls");
console.log(" Works with or without the relative-urls option.");
console.log(" -ru, --relative-urls Re-writes relative urls to the base less file.");
console.log(" -sm=on|off Turns on or off strict math, where in strict mode, math.");
console.log(" --strict-math=on|off Requires brackets. This option may default to on and then");
console.log(" be removed in the future.");
console.log(" -su=on|off Allows mixed units, e.g. 1px+1em or 1px*1px which have units");
console.log(" --strict-units=on|off that cannot be represented.");
console.log(" --global-var='VAR=VALUE' Defines a variable that can be referenced by the file.");
console.log(" --modify-var='VAR=VALUE' Modifies a variable already declared in the file.");
console.log(" --url-args='QUERYSTRING' Adds params into url tokens (e.g. 42, cb=42 or 'a=1&b=2')");
console.log(" --plugin=PLUGIN=OPTIONS Loads a plugin. You can also omit the --plugin= if the plugin begins");
console.log(" less-plugin. E.g. the clean css plugin is called less-plugin-clean-css");
console.log(" once installed (npm install less-plugin-clean-css), use either with");
console.log(" --plugin=less-plugin-clean-css or just --clean-css");
console.log(" specify options afterwards e.g. --plugin=less-plugin-clean-css=\"advanced\"");
console.log(" or --clean-css=\"advanced\"");
console.log("");
console.log("-------------------------- Deprecated ----------------");
console.log(" --line-numbers=TYPE Outputs filename and line numbers.");

View File

@@ -1,91 +1,54 @@
var path = require("path");
var path = require("path"),
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
AbstractPluginLoader = require("../less/environment/abstract-plugin-loader.js");
/**
* Node Plugin Loader
*/
var PluginLoader = function(less) {
this.less = less;
};
PluginLoader.prototype.tryLoadPlugin = function(name, argument) {
var plugin = this.tryRequirePlugin(name);
if (plugin) {
// support plugins being a function
// so that the plugin can be more usable programmatically
if (typeof plugin === "function") {
plugin = new plugin();
}
if (plugin.minVersion) {
if (this.compareVersion(plugin.minVersion, this.less.version) < 0) {
console.log("plugin " + name + " requires version " + this.versionToString(plugin.minVersion));
return null;
this.require = require;
this.requireRelative = function(prefix) {
prefix = path.dirname(prefix);
return function(id) {
var str = id.substr(0, 2);
if (str === '..' || str === './') {
return require(path.join(prefix, id));
}
}
if (argument) {
if (!plugin.setOptions) {
console.log("options have been provided but the plugin " + name + "does not support any options");
return null;
else {
return require(id);
}
try {
plugin.setOptions(argument);
};
};
};
PluginLoader.prototype = new AbstractPluginLoader();
PluginLoader.prototype.loadPlugin = function(filename, basePath, context, environment, fileManager) {
var self = this;
var prefix = filename.slice(0, 1);
var explicit = prefix === "." || prefix === "/" || filename.slice(-3).toLowerCase() === ".js";
if (!explicit) {
context.prefixes = ['less-plugin-', ''];
}
return new PromiseConstructor(function(fulfill, reject) {
fileManager.loadFile(filename, basePath, context, environment).then(
function(data) {
try {
self.require = self.requireRelative(data.filename);
fulfill(data);
}
catch (e) {
reject(e);
}
}
catch(e) {
console.log("Error setting options on plugin " + name);
console.log(e.message);
return null;
}
}
return plugin;
}
return null;
};
PluginLoader.prototype.compareVersion = function(aVersion, bVersion) {
for (var i = 0; i < aVersion.length; i++) {
if (aVersion[i] !== bVersion[i]) {
return parseInt(aVersion[i]) > parseInt(bVersion[i]) ? -1 : 1;
}
}
return 0;
};
PluginLoader.prototype.versionToString = function(version) {
var versionString = "";
for (var i = 0; i < version.length; i++) {
versionString += (versionString ? "." : "") + version[i];
}
return versionString;
};
PluginLoader.prototype.tryRequirePlugin = function(name) {
// is at the same level as the less.js module
try {
return require("../../../" + name);
}
catch(e) {
}
// is installed as a sub dependency of the current folder
try {
return require(path.join(process.cwd(), "node_modules", name));
}
catch(e) {
}
// is referenced relative to the current directory
try {
return require(path.join(process.cwd(), name));
}
catch(e) {
}
// unlikely - would have to be a dependency of where this code was running (less.js)...
if (name[0] !== '.') {
try {
return require(name);
}
catch(e) {
}
}
};
PluginLoader.prototype.printUsage = function(plugins) {
for (var i = 0; i < plugins.length; i++) {
var plugin = plugins[i];
if (plugin.printUsage) {
plugin.printUsage();
}
}
).catch(function(err) {
reject(err);
});
});
};
module.exports = PluginLoader;

View File

@@ -21,7 +21,7 @@ UrlFileManager.prototype.loadFile = function(filename, currentDirectory, options
return new PromiseConstructor(function(fulfill, reject) {
if (request === undefined) {
try { request = require('request'); }
catch(e) { request = null; }
catch (e) { request = null; }
}
if (!request) {
reject({ type: 'File', message: "optional dependency 'request' required to import over http(s)\n" });

View File

@@ -1,6 +1,5 @@
/* jshint rhino:true, unused: false */
/* jscs:disable validateIndentation */
/*global name:true, less, loadStyleSheet, os */
/* global name:true, less, loadStyleSheet, os */
function formatError(ctx, options) {
options = options || {};
@@ -9,7 +8,7 @@ function formatError(ctx, options) {
var extract = ctx.extract;
var error = [];
// var stylize = options.color ? require('./lessc_helper').stylize : function (str) { return str; };
// var stylize = options.color ? require('./lessc_helper').stylize : function (str) { return str; };
var stylize = function (str) { return str; };
// only output a stack if it isn't a less error
@@ -81,7 +80,7 @@ function loadStyleSheet(sheet, callback, reload, remaining) {
}
try {
callback(e, root, input, sheet, { local: false, lastModified: 0, remaining: remaining }, sheetName);
} catch(e) {
} catch (e) {
writeError(e);
}
});
@@ -146,25 +145,10 @@ function writeFile(filename, content) {
// Command line integration via Rhino
(function (args) {
var options = {
depends: false,
compress: false,
cleancss: false,
max_line_len: -1,
silent: false,
verbose: false,
lint: false,
paths: [],
color: true,
strictImports: false,
rootpath: '',
relativeUrls: false,
ieCompat: true,
strictMath: false,
strictUnits: false
};
var options = require('../default-options');
var continueProcessing = true,
currentErrorcode;
currentErrorcode;
var checkArgFunc = function(arg, option) {
if (!option) {
@@ -226,8 +210,8 @@ function writeFile(filename, content) {
break;
case 'h':
case 'help':
//TODO
// require('../lib/less/lessc_helper').printUsage();
// TODO
// require('../lib/less/lessc_helper').printUsage();
continueProcessing = false;
break;
case 'x':
@@ -270,7 +254,7 @@ function writeFile(filename, content) {
.split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':')
.map(function(p) {
if (p) {
// return path.resolve(process.cwd(), p);
// return path.resolve(process.cwd(), p);
return p;
}
});
@@ -351,20 +335,20 @@ function writeFile(filename, content) {
var name = args[0];
if (name && name != '-') {
// name = path.resolve(process.cwd(), name);
// name = path.resolve(process.cwd(), name);
}
var output = args[1];
var outputbase = args[1];
if (output) {
options.sourceMapOutputFilename = output;
// output = path.resolve(process.cwd(), output);
// output = path.resolve(process.cwd(), output);
if (warningMessages) {
console.log(warningMessages);
}
}
// options.sourceMapBasepath = process.cwd();
// options.sourceMapBasepath = '';
// options.sourceMapBasepath = process.cwd();
// options.sourceMapBasepath = '';
if (options.sourceMap === true) {
console.log("output: " + output);
@@ -382,24 +366,25 @@ function writeFile(filename, content) {
console.log("lessc: no inout files");
console.log("");
// TODO
// require('../lib/less/lessc_helper').printUsage();
// require('../lib/less/lessc_helper').printUsage();
currentErrorcode = 1;
return;
}
// var ensureDirectory = function (filepath) {
// var dir = path.dirname(filepath),
// cmd,
// existsSync = fs.existsSync || path.existsSync;
// if (!existsSync(dir)) {
// if (mkdirp === undefined) {
// try {mkdirp = require('mkdirp');}
// catch(e) { mkdirp = null; }
// }
// cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
// cmd(dir);
// }
// };
/*
var ensureDirectory = function (filepath) {
var dir = path.dirname(filepath),
cmd,
existsSync = fs.existsSync || path.existsSync;
if (!existsSync(dir)) {
if (mkdirp === undefined) {
try {mkdirp = require('mkdirp');}
catch(e) { mkdirp = null; }
}
cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
cmd(dir);
}
}; */
if (options.depends) {
if (!outputbase) {
@@ -443,7 +428,7 @@ function writeFile(filename, content) {
}
});
}
catch(e) {
catch (e) {
writeError(e, options);
quit(1);
}

View File

@@ -40,18 +40,18 @@ contexts.Parse = function(options) {
};
var evalCopyProperties = [
'paths', // additional include paths
'compress', // whether to compress
'ieCompat', // whether to enforce IE compatibility (IE8 data-uri)
'strictMath', // whether math has to be within parenthesis
'strictUnits', // whether units need to evaluate correctly
'sourceMap', // whether to output a source map
'importMultiple', // whether we are currently importing multiple copies
'urlArgs', // whether to add args into url tokens
'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true
'pluginManager', // Used as the plugin manager for the session
'importantScope' // used to bubble up !important statements
];
'paths', // additional include paths
'compress', // whether to compress
'ieCompat', // whether to enforce IE compatibility (IE8 data-uri)
'strictMath', // whether math has to be within parenthesis
'strictUnits', // whether units need to evaluate correctly
'sourceMap', // whether to output a source map
'importMultiple', // whether we are currently importing multiple copies
'urlArgs', // whether to add args into url tokens
'javascriptEnabled', // option - whether Inline JavaScript is enabled. if undefined, defaults to false
'pluginManager', // Used as the plugin manager for the session
'importantScope' // used to bubble up !important statements
];
contexts.Eval = function(options, frames) {
copyFromOriginal(options, this, evalCopyProperties);
@@ -73,7 +73,11 @@ contexts.Eval.prototype.outOfParenthesis = function () {
this.parensStack.pop();
};
contexts.Eval.prototype.mathOn = true;
contexts.Eval.prototype.isMathOn = function () {
if (!this.mathOn) {
return false;
}
return this.strictMath ? (this.parensStack && this.parensStack.length) : true;
};
@@ -83,13 +87,13 @@ contexts.Eval.prototype.isPathRelative = function (path) {
contexts.Eval.prototype.normalizePath = function( path ) {
var
segments = path.split("/").reverse(),
segment;
segments = path.split("/").reverse(),
segment;
path = [];
while (segments.length !== 0 ) {
segment = segments.pop();
switch( segment ) {
switch ( segment ) {
case ".":
break;
case "..":
@@ -108,4 +112,4 @@ contexts.Eval.prototype.normalizePath = function( path ) {
return path.join("/");
};
//todo - do the same for the toCSS ?
// todo - do the same for the toCSS ?

View File

@@ -0,0 +1,65 @@
// Export a new default each time
module.exports = function() {
return {
/* Outputs a makefile import dependency list to stdout. */
depends: false,
/* Compress using less built-in compression.
* This does an okay job but does not utilise all the tricks of
* dedicated css compression. */
compress: false,
/* Runs the less parser and just reports errors without any output. */
lint: false,
/* Sets available include paths.
* If the file in an @import rule does not exist at that exact location,
* less will look for it at the location(s) passed to this option.
* You might use this for instance to specify a path to a library which
* you want to be referenced simply and relatively in the less files. */
paths: [],
/* color output in the terminal */
color: true,
/* The strictImports controls whether the compiler will allow an @import inside of either
* @media blocks or (a later addition) other selector blocks.
* See: https://github.com/less/less.js/issues/656 */
strictImports: false,
/* Allow Imports from Insecure HTTPS Hosts */
insecure: false,
/* Allows you to add a path to every generated import and url in your css.
* This does not affect less import statements that are processed, just ones
* that are left in the output css. */
rootpath: '',
/* By default URLs are kept as-is, so if you import a file in a sub-directory
* that references an image, exactly the same URL will be output in the css.
* This option allows you to re-write URL's in imported files so that the
* URL is always relative to the base imported file */
relativeUrls: false,
/* Compatibility with IE8. Used for limiting data-uri length */
ieCompat: false, // true until 3.0
/* Without this option on, Less will try and process all math in your css */
strictMath: false,
/* Without this option, less attempts to guess at the output unit when it does maths. */
strictUnits: false,
/* Effectively the declaration is put at the top of your base Less file,
* meaning it can be used but it also can be overridden if this variable
* is defined in the file. */
globalVars: null,
/* As opposed to the global variable option, this puts the declaration at the
* end of your base file, meaning it will override anything defined in your Less file. */
modifyVars: null,
/* This option allows you to specify a argument to go on to every URL. */
urlArgs: ''
}
}

View File

@@ -35,13 +35,14 @@ abstractFileManager.prototype.alwaysMakePathsAbsolute = function() {
abstractFileManager.prototype.isPathAbsolute = function(filename) {
return (/^(?:[a-z-]+:|\/|\\|#)/i).test(filename);
};
// TODO: pull out / replace?
abstractFileManager.prototype.join = function(basePath, laterPath) {
if (!basePath) {
return laterPath;
}
return basePath + laterPath;
};
abstractFileManager.prototype.pathDiff = function pathDiff(url, baseUrl) {
// diff between two paths to create a relative path
@@ -75,7 +76,7 @@ abstractFileManager.prototype.extractUrlParts = function extractUrlParts(url, ba
var urlPartsRegex = /^((?:[a-z-]+:)?\/{2}(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i,
urlParts = url.match(urlPartsRegex),
returner = {}, directories = [], i, baseUrlParts;
returner = {}, rawDirectories = [], directories = [], i, baseUrlParts;
if (!urlParts) {
throw new Error("Could not parse sheet href - '" + url + "'");
@@ -94,27 +95,26 @@ abstractFileManager.prototype.extractUrlParts = function extractUrlParts(url, ba
}
if (urlParts[3]) {
directories = urlParts[3].replace(/\\/g, "/").split("/");
rawDirectories = urlParts[3].replace(/\\/g, "/").split("/");
// extract out . before .. so .. doesn't absorb a non-directory
for (i = 0; i < directories.length; i++) {
if (directories[i] === ".") {
directories.splice(i, 1);
i -= 1;
}
}
// collapse '..' and skip '.'
for (i = 0; i < rawDirectories.length; i++) {
for (i = 0; i < directories.length; i++) {
if (directories[i] === ".." && i > 0) {
directories.splice(i - 1, 2);
i -= 2;
if (rawDirectories[i] === "..") {
directories.pop();
}
else if (rawDirectories[i] !== ".") {
directories.push(rawDirectories[i]);
}
}
}
returner.hostPart = urlParts[1];
returner.directories = directories;
returner.rawPath = (urlParts[1] || "") + rawDirectories.join("/");
returner.path = (urlParts[1] || "") + directories.join("/");
returner.filename = urlParts[4];
returner.fileUrl = returner.path + (urlParts[4] || "");
returner.url = returner.fileUrl + (urlParts[5] || "");
return returner;

View File

@@ -0,0 +1,169 @@
var functionRegistry = require("../functions/function-registry"),
LessError = require('../less-error');
var AbstractPluginLoader = function() {
};
function error(msg, type) {
throw new LessError(
{
type: type || 'Syntax',
message: msg
}
);
}
AbstractPluginLoader.prototype.evalPlugin = function(contents, context, imports, pluginOptions, fileInfo) {
var loader,
registry,
pluginObj,
localModule,
pluginManager,
filename;
pluginManager = context.pluginManager;
if (fileInfo) {
if (typeof fileInfo === "string") {
filename = fileInfo;
}
else {
filename = fileInfo.filename;
}
}
var shortname = (new this.less.FileManager()).extractUrlParts(filename).filename;
if (filename) {
pluginObj = pluginManager.get(filename);
if (pluginObj) {
this.trySetOptions(pluginObj, filename, shortname, pluginOptions);
try {
if (pluginObj.use) {
pluginObj.use.call(this.context, pluginObj);
}
}
catch (e) {
e.message = 'Error during @plugin call';
return new this.less.LessError(e, imports, filename);
}
return pluginObj;
}
}
localModule = {
exports: {},
pluginManager: pluginManager,
fileInfo: fileInfo
};
registry = functionRegistry.create();
var registerPlugin = function(obj) {
pluginObj = obj;
};
try {
loader = new Function("module", "require", "registerPlugin", "functions", "tree", "less", "fileInfo", contents);
loader(localModule, this.require, registerPlugin, registry, this.less.tree, this.less, fileInfo);
} catch (e) {
return new this.less.LessError(e, imports, filename);
}
if (!pluginObj) {
pluginObj = localModule.exports;
}
pluginObj = this.validatePlugin(pluginObj, filename, shortname);
if (pluginObj) {
// Run on first load
pluginManager.addPlugin(pluginObj, fileInfo.filename, registry);
pluginObj.functions = registry.getLocalFunctions();
pluginObj.imports = imports;
pluginObj.filename = filename;
this.trySetOptions(pluginObj, filename, shortname, pluginOptions);
// Run every @plugin call
try {
if (pluginObj.use) {
pluginObj.use.call(this.context, pluginObj);
}
}
catch (e) {
e.message = 'Error during @plugin call';
return new this.less.LessError(e, imports, filename);
}
}
else {
return new this.less.LessError({ message: "Not a valid plugin" });
}
return pluginObj;
};
AbstractPluginLoader.prototype.trySetOptions = function(plugin, filename, name, options) {
if (options) {
if (!plugin.setOptions) {
error("Options have been provided but the plugin " + name + " does not support any options.");
return null;
}
try {
plugin.setOptions(options);
}
catch (e) {
error("Error setting options on plugin " + name + '\n' + e.message);
return null;
}
}
};
AbstractPluginLoader.prototype.validatePlugin = function(plugin, filename, name) {
if (plugin) {
// support plugins being a function
// so that the plugin can be more usable programmatically
if (typeof plugin === "function") {
plugin = new plugin();
}
if (plugin.minVersion) {
if (this.compareVersion(plugin.minVersion, this.less.version) < 0) {
error("Plugin " + name + " requires version " + this.versionToString(plugin.minVersion));
return null;
}
}
return plugin;
}
return null;
};
AbstractPluginLoader.prototype.compareVersion = function(aVersion, bVersion) {
if (typeof aVersion === "string") {
aVersion = aVersion.match(/^(\d+)\.?(\d+)?\.?(\d+)?/);
aVersion.shift();
}
for (var i = 0; i < aVersion.length; i++) {
if (aVersion[i] !== bVersion[i]) {
return parseInt(aVersion[i]) > parseInt(bVersion[i]) ? -1 : 1;
}
}
return 0;
};
AbstractPluginLoader.prototype.versionToString = function(version) {
var versionString = "";
for (var i = 0; i < version.length; i++) {
versionString += (versionString ? "." : "") + version[i];
}
return versionString;
};
AbstractPluginLoader.prototype.printUsage = function(plugins) {
for (var i = 0; i < plugins.length; i++) {
var plugin = plugins[i];
if (plugin.printUsage) {
plugin.printUsage();
}
}
};
module.exports = AbstractPluginLoader;

View File

@@ -0,0 +1,15 @@
var functionRegistry = require("./function-registry"),
Anonymous = require("../tree/anonymous"),
Keyword = require("../tree/keyword");
functionRegistry.addMultiple({
boolean: function(condition) {
return condition ? Keyword.True : Keyword.False;
},
'if': function(condition, trueValue, falseValue) {
return condition ? trueValue
: (falseValue || new Anonymous);
}
});

View File

@@ -278,7 +278,7 @@ colorFunctions = {
if (typeof dark === 'undefined') {
dark = colorFunctions.rgba(0, 0, 0, 1.0);
}
//Figure out which is actually light and dark!
// Figure out which is actually light and dark:
if (dark.luma() > light.luma()) {
var t = light;
light = dark;
@@ -295,6 +295,44 @@ colorFunctions = {
return dark;
}
},
// Changes made in 2.7.0 - Reverted in 3.0.0
// contrast: function (color, color1, color2, threshold) {
// // Return which of `color1` and `color2` has the greatest contrast with `color`
// // according to the standard WCAG contrast ratio calculation.
// // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
// // The threshold param is no longer used, in line with SASS.
// // filter: contrast(3.2);
// // should be kept as is, so check for color
// if (!color.rgb) {
// return null;
// }
// if (typeof color1 === 'undefined') {
// color1 = colorFunctions.rgba(0, 0, 0, 1.0);
// }
// if (typeof color2 === 'undefined') {
// color2 = colorFunctions.rgba(255, 255, 255, 1.0);
// }
// var contrast1, contrast2;
// var luma = color.luma();
// var luma1 = color1.luma();
// var luma2 = color2.luma();
// // Calculate contrast ratios for each color
// if (luma > luma1) {
// contrast1 = (luma + 0.05) / (luma1 + 0.05);
// } else {
// contrast1 = (luma1 + 0.05) / (luma + 0.05);
// }
// if (luma > luma2) {
// contrast2 = (luma + 0.05) / (luma2 + 0.05);
// } else {
// contrast2 = (luma2 + 0.05) / (luma + 0.05);
// }
// if (contrast1 > contrast2) {
// return color1;
// } else {
// return color2;
// }
// },
argb: function (color) {
return new Anonymous(color.toARGB());
},

View File

@@ -1,6 +1,7 @@
module.exports = function(environment) {
var Quoted = require("../tree/quoted"),
URL = require("../tree/url"),
utils = require('../utils'),
functionRegistry = require("./function-registry"),
fallback = function(functionThis, node) {
return new URL(node, functionThis.index, functionThis.currentFileInfo).eval(functionThis.context);
@@ -26,8 +27,10 @@ module.exports = function(environment) {
fragment = filePath.slice(fragmentStart);
filePath = filePath.slice(0, fragmentStart);
}
var context = utils.clone(this.context);
context.rawBuffer = true;
var fileManager = environment.getFileManager(filePath, currentDirectory, this.context, environment, true);
var fileManager = environment.getFileManager(filePath, currentDirectory, context, environment, true);
if (!fileManager) {
return fallback(this, filePathNode);
@@ -53,7 +56,7 @@ module.exports = function(environment) {
useBase64 = /;base64$/.test(mimetype);
}
var fileSync = fileManager.loadFileSync(filePath, currentDirectory, this.context, environment);
var fileSync = fileManager.loadFileSync(filePath, currentDirectory, context, environment);
if (!fileSync.contents) {
logger.warn("Skipped data-uri embedding of " + filePath + " because file not found");
return fallback(this, filePathNode || mimetypeNode);

View File

@@ -11,8 +11,8 @@ var functionCaller = function(name, context, index, currentFileInfo) {
functionCaller.prototype.isValid = function() {
return Boolean(this.func);
};
functionCaller.prototype.call = function(args) {
functionCaller.prototype.call = function(args) {
// This code is terrible and should be replaced as per this issue...
// https://github.com/less/less.js/issues/2477
if (Array.isArray(args)) {

View File

@@ -7,7 +7,7 @@ function makeRegistry( base ) {
name = name.toLowerCase();
if (this._data.hasOwnProperty(name)) {
//TODO warn
// TODO warn
}
this._data[name] = func;
},
@@ -20,8 +20,14 @@ function makeRegistry( base ) {
get: function(name) {
return this._data[name] || ( base && base.get( name ));
},
inherit : function() {
getLocalFunctions: function() {
return this._data;
},
inherit: function() {
return makeRegistry( this );
},
create: function(base) {
return makeRegistry(base);
}
};
}

View File

@@ -4,7 +4,8 @@ module.exports = function(environment) {
functionCaller: require("./function-caller")
};
//register functions
// register functions
require("./boolean");
require("./default");
require("./color");
require("./color-blending");

View File

@@ -5,7 +5,7 @@ var Dimension = require("../tree/dimension"),
var minMax = function (isMin, args) {
args = Array.prototype.slice.call(args);
switch(args.length) {
switch (args.length) {
case 0: throw { type: "Argument", message: "one or more arguments required" };
}
var i, j, current, currentUnified, referenceUnified, unit, unitStatic, unitClone,
@@ -27,7 +27,7 @@ var minMax = function (isMin, args) {
j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit];
if (j === undefined) {
if (unitStatic !== undefined && unit !== unitStatic) {
throw{ type: "Argument", message: "incompatible types" };
throw { type: "Argument", message: "incompatible types" };
}
values[unit] = order.length;
order.push(current);

View File

@@ -19,12 +19,12 @@ functionRegistry.addMultiple({
result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement);
return new Quoted(string.quote || '', result, string.escaped);
},
'%': function (string /* arg, arg, ...*/) {
'%': function (string /* arg, arg, ... */) {
var args = Array.prototype.slice.call(arguments, 1),
result = string.value;
for (var i = 0; i < args.length; i++) {
/*jshint loopfunc:true */
/* jshint loopfunc:true */
result = result.replace(/%[sda]/i, function(token) {
var value = ((args[i].type === "Quoted") &&
token.match(/s/i)) ? args[i].value : args[i].toCSS();

View File

@@ -15,12 +15,12 @@ module.exports = function(environment) {
renderEnv = {compress: false},
returner,
directionValue = direction.toCSS(renderEnv),
i, color, position, positionValue, alpha;
i, color, position, positionValue, alpha;
function throwArgumentDescriptor() {
throw { type: "Argument",
message: "svg-gradient expects direction, start_color [start_position], [color position,]...," +
" end_color [end_position] or direction, color list" };
message: "svg-gradient expects direction, start_color [start_position], [color position,]...," +
" end_color [end_position] or direction, color list" };
}
if (arguments.length == 2) {
@@ -61,7 +61,7 @@ module.exports = function(environment) {
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' +
'<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>';
for (i = 0; i < stops.length; i+= 1) {
for (i = 0; i < stops.length; i += 1) {
if (stops[i] instanceof Expression) {
color = stops[i].value[0];
position = stops[i].value[1];

View File

@@ -1,6 +1,8 @@
var contexts = require("./contexts"),
Parser = require('./parser/parser'),
FunctionImporter = require('./plugins/function-importer');
LessError = require('./less-error'),
utils = require('./utils'),
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
module.exports = function(environment) {
@@ -13,7 +15,8 @@ module.exports = function(environment) {
// 'entryPath' - absolute path to the entry file
// 'reference' - whether the file should not be output and only output parts that are referenced
var ImportManager = function(context, rootFileInfo) {
var ImportManager = function(less, context, rootFileInfo) {
this.less = less;
this.rootFilename = rootFileInfo.filename;
this.paths = context.paths || []; // Search paths, when importing
this.contents = {}; // map - filename to contents of all the files
@@ -25,16 +28,19 @@ module.exports = function(environment) {
this.queue = []; // Files which haven't been imported yet
this.files = {}; // Holds the imported parse trees.
};
/**
* Add an import to be imported
* @param path - the raw path
* @param tryAppendLessExtension - whether to try appending the less extension (if the path has no extension)
* @param tryAppendExtension - whether to try appending a file extension (.less or .js if the path has no extension)
* @param currentFileInfo - the current file info (used for instance to work out relative paths)
* @param importOptions - import options
* @param callback - callback for when it is imported
*/
ImportManager.prototype.push = function (path, tryAppendLessExtension, currentFileInfo, importOptions, callback) {
var importManager = this;
ImportManager.prototype.push = function (path, tryAppendExtension, currentFileInfo, importOptions, callback) {
var importManager = this,
pluginLoader = this.context.pluginManager.Loader;
this.queue.push(path);
var fileParsedFunc = function (e, root, fullPath) {
@@ -45,7 +51,9 @@ module.exports = function(environment) {
callback(null, {rules:[]}, false, null);
}
else {
importManager.files[fullPath] = root;
if (!importManager.files[fullPath]) {
importManager.files[fullPath] = { root: root, options: importOptions };
}
if (e && !importManager.error) { importManager.error = e; }
callback(e, root, importedEqualsRoot, fullPath);
}
@@ -65,12 +73,9 @@ module.exports = function(environment) {
return;
}
if (tryAppendLessExtension) {
path = fileManager.tryAppendExtension(path, importOptions.plugin ? ".js" : ".less");
}
var loadFileCallback = function(loadedFile) {
var resolvedFilename = loadedFile.filename,
var plugin,
resolvedFilename = loadedFile.filename,
contents = loadedFile.contents.replace(/^\uFEFF/, '');
// Pass on an updated rootpath if path of imported file is relative and file
@@ -102,30 +107,56 @@ module.exports = function(environment) {
newFileInfo.reference = true;
}
if (importOptions.plugin) {
new FunctionImporter(newEnv, newFileInfo).eval(contents, function (e, root) {
fileParsedFunc(e, root, resolvedFilename);
});
if (importOptions.isPlugin) {
plugin = pluginLoader.evalPlugin(contents, newEnv, importManager, importOptions.pluginArgs, newFileInfo);
if (plugin instanceof LessError) {
fileParsedFunc(plugin, null, resolvedFilename);
}
else {
fileParsedFunc(null, plugin, resolvedFilename);
}
} else if (importOptions.inline) {
fileParsedFunc(null, contents, resolvedFilename);
} else {
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (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);
});
}
}
};
var promise, context = utils.clone(this.context);
var promise = fileManager.loadFile(path, currentFileInfo.currentDirectory, this.context, environment,
function(err, loadedFile) {
if (err) {
fileParsedFunc(err);
} else {
loadFileCallback(loadedFile);
}
});
if (tryAppendExtension) {
context.ext = importOptions.isPlugin ? ".js" : ".less";
}
if (importOptions.isPlugin) {
promise = pluginLoader.loadPlugin(path, currentFileInfo.currentDirectory, context, environment, fileManager);
}
else {
promise = fileManager.loadFile(path, currentFileInfo.currentDirectory, context, environment,
function(err, loadedFile) {
if (err) {
fileParsedFunc(err);
} else {
loadFileCallback(loadedFile);
}
});
}
if (promise) {
promise.then(loadFileCallback, fileParsedFunc);
}
};
return ImportManager;
};

View File

@@ -1,12 +1,13 @@
module.exports = function(environment, fileManagers) {
var SourceMapOutput, SourceMapBuilder, ParseTree, ImportManager, Environment;
var less = {
version: [2, 7, 3],
var initial = {
version: [3, 0, 0],
data: require('./data'),
tree: require('./tree'),
Environment: (Environment = require("./environment/environment")),
AbstractFileManager: require("./environment/abstract-file-manager"),
AbstractPluginLoader: require("./environment/abstract-plugin-loader"),
environment: (environment = new Environment(environment, fileManagers)),
visitors: require('./visitors'),
Parser: require('./parser/parser'),
@@ -25,5 +26,30 @@ 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) {
/* eslint guard-for-in: 0 */
t = initial.tree[n];
if (typeof t === "function") {
api[n.toLowerCase()] = ctor(t);
}
else {
api[n] = Object.create(null);
for (var o in t) {
/* eslint guard-for-in: 0 */
api[n][o.toLowerCase()] = ctor(t[o]);
}
}
}
return api;
};

View File

@@ -1,11 +1,34 @@
var utils = require("./utils");
var utils = require('./utils');
/**
* This is a centralized class of any error that could be thrown internally (mostly by the parser).
* Besides standard .message it keeps some additional data like a path to the file where the error
* occurred along with line and column numbers.
*
* @class
* @extends Error
* @type {module.LessError}
*
* @prop {string} type
* @prop {string} filename
* @prop {number} index
* @prop {number} line
* @prop {number} column
* @prop {number} callLine
* @prop {number} callExtract
* @prop {string[]} extract
*
* @param {Object} e - An error object to wrap around or just a descriptive object
* @param {Object} importManager - An instance of ImportManager (see import-manager.js)
* @param {string} [currentFilename]
*/
var LessError = module.exports = function LessError(e, importManager, currentFilename) {
Error.call(this);
var filename = e.filename || currentFilename;
this.message = e.message;
this.stack = e.stack;
if (importManager && filename) {
var input = importManager.contents[filename],
loc = utils.getLocation(e.index, input),
@@ -18,17 +41,32 @@ var LessError = module.exports = function LessError(e, importManager, currentFil
this.filename = filename;
this.index = e.index;
this.line = typeof line === 'number' ? line + 1 : null;
this.column = col;
if (!this.line && this.stack) {
var found = this.stack.match(/(<anonymous>|Function):(\d+):(\d+)/);
if (found) {
if (found[2]) {
this.line = parseInt(found[2]) - 2;
}
if (found[3]) {
this.column = parseInt(found[3]);
}
}
}
this.callLine = callLine + 1;
this.callExtract = lines[callLine];
this.column = col;
this.extract = [
lines[line - 1],
lines[line],
lines[line + 1]
lines[this.line - 2],
lines[this.line - 1],
lines[this.line]
];
}
this.message = e.message;
this.stack = e.stack;
};
if (typeof Object.create === 'undefined') {
@@ -40,3 +78,64 @@ if (typeof Object.create === 'undefined') {
}
LessError.prototype.constructor = LessError;
/**
* An overridden version of the default Object.prototype.toString
* which uses additional information to create a helpful message.
*
* @param {Object} options
* @returns {string}
*/
LessError.prototype.toString = function(options) {
options = options || {};
var message = '';
var extract = this.extract || [];
var error = [];
var stylize = function (str) { return str; };
if (options.stylize) {
var type = typeof options.stylize;
if (type !== 'function') {
throw Error('options.stylize should be a function, got a ' + type + '!');
}
stylize = options.stylize;
}
if (this.line !== null) {
if (typeof extract[0] === 'string') {
error.push(stylize((this.line - 1) + ' ' + extract[0], 'grey'));
}
if (typeof extract[1] === 'string') {
var errorTxt = this.line + ' ';
if (extract[1]) {
errorTxt += extract[1].slice(0, this.column) +
stylize(stylize(stylize(extract[1].substr(this.column, 1), 'bold') +
extract[1].slice(this.column + 1), 'red'), 'inverse');
}
error.push(errorTxt);
}
if (typeof extract[2] === 'string') {
error.push(stylize((this.line + 1) + ' ' + extract[2], 'grey'));
}
error = error.join('\n') + stylize('', 'reset') + '\n';
}
message += stylize(this.type + 'Error: ' + this.message, 'red');
if (this.filename) {
message += stylize(' in ', 'red') + this.filename;
}
if (this.line) {
message += stylize(' on line ' + this.line + ', column ' + (this.column + 1) + ':', 'grey');
}
message += '\n' + error;
if (this.callLine) {
message += stylize('from ', 'red') + (this.filename || '') + '/n';
message += stylize(this.callLine, 'grey') + ' ' + this.callExtract + '/n';
}
return message;
};

View File

@@ -1,15 +1,19 @@
var PromiseConstructor,
contexts = require("./contexts"),
Parser = require('./parser/parser'),
PluginManager = require('./plugin-manager');
PluginManager = require('./plugin-manager'),
LessError = require('./less-error'),
utils = require('./utils');
module.exports = function(environment, ParseTree, ImportManager) {
var parse = function (input, options, callback) {
options = options || {};
if (typeof options === 'function') {
callback = options;
options = {};
options = utils.defaults(this.options, {});
}
else {
options = utils.defaults(this.options, options || {});
}
if (!callback) {
@@ -29,9 +33,8 @@ module.exports = function(environment, ParseTree, ImportManager) {
} else {
var context,
rootFileInfo,
pluginManager = new PluginManager(this);
pluginManager = new PluginManager(this, true);
pluginManager.addPlugins(options.plugins);
options.pluginManager = pluginManager;
context = new contexts.Parse(options);
@@ -55,13 +58,33 @@ module.exports = function(environment, ParseTree, ImportManager) {
}
}
var imports = new ImportManager(context, rootFileInfo);
var imports = new ImportManager(this, context, rootFileInfo);
this.importManager = imports;
// TODO: allow the plugins to be just a list of paths or names
// Do an async plugin queue like lessc
if (options.plugins) {
options.plugins.forEach(function(plugin) {
var evalResult, contents;
if (plugin.fileContent) {
contents = plugin.fileContent.replace(/^\uFEFF/, '');
evalResult = pluginManager.Loader.evalPlugin(contents, context, imports, plugin.options, plugin.filename);
if (evalResult instanceof LessError) {
return callback(evalResult);
}
}
else {
pluginManager.addPlugin(plugin);
}
});
}
new Parser(context, imports, rootFileInfo)
.parse(input, function (e, root) {
if (e) { return callback(e); }
callback(null, root, imports, options);
}, options);
if (e) { return callback(e); }
callback(null, root, imports, options);
}, options);
}
};
return parse;

View File

@@ -1,11 +1,11 @@
var chunker = require('./chunker');
module.exports = function() {
var input, // LeSS input string
var input, // Less input string
j, // current chunk
saveStack = [], // holds state for backtracking
furthest, // furthest index the parser has gone to
furthestPossibleErrorMessage,// if this is furthest we got to, this is the probably cause
furthestPossibleErrorMessage, // if this is furthest we got to, this is the probably cause
chunks, // chunkified input
current, // current chunk
currentPos, // index of current chunk, in `input`
@@ -156,7 +156,7 @@ module.exports = function() {
for (var i = 1; i + currentPosition < length; i++) {
var nextChar = input.charAt(i + currentPosition);
switch(nextChar) {
switch (nextChar) {
case "\\":
i++;
continue;
@@ -209,7 +209,7 @@ module.exports = function() {
parserInput.peekNotNumeric = function() {
var c = input.charCodeAt(parserInput.i);
//Is the first char of the dimension 0-9, '.', '+' or '-'
// Is the first char of the dimension 0-9, '.', '+' or '-'
return (c > CHARCODE_9 || c < CHARCODE_PLUS) || c === CHARCODE_FORWARD_SLASH || c === CHARCODE_COMMA;
};

View File

@@ -2,7 +2,8 @@ var LessError = require('../less-error'),
tree = require("../tree"),
visitors = require("../visitors"),
getParserInput = require("./parser-input"),
utils = require("../utils");
utils = require("../utils"),
functionRegistry = require('../functions/function-registry');
//
// less.js - parser
@@ -35,8 +36,8 @@ var LessError = require('../less-error'),
// Token matching is done with the `$` function, which either takes
// a terminal string or regexp, or a non-terminal function to call.
// It also takes care of moving all the indices forwards.
//`
//
var Parser = function Parser(context, imports, fileInfo) {
var parsers,
parserInput = getParserInput();
@@ -80,11 +81,61 @@ 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
//
return {
parserInput: parserInput,
imports: imports,
fileInfo: fileInfo,
parseNode: parseNode,
//
// Parse an input string into an abstract syntax tree,
// @param str A string containing 'less' markup
@@ -130,9 +181,13 @@ var Parser = function Parser(context, imports, fileInfo) {
}, imports);
});
root = new(tree.Ruleset)(null, this.parsers.primary());
tree.Node.prototype.parse = this;
root = new tree.Ruleset(null, this.parsers.primary());
tree.Node.prototype.rootNode = root;
root.root = true;
root.firstRoot = true;
root.functionRegistry = functionRegistry.inherit();
} catch (e) {
return callback(new LessError(e, imports, fileInfo.filename));
}
@@ -197,7 +252,7 @@ var Parser = function Parser(context, imports, fileInfo) {
//
// The basic structure of the syntax tree generated is as follows:
//
// Ruleset -> Rule -> Value -> Expression -> Entity
// Ruleset -> Declaration -> Value -> Expression -> Entity
//
// Here's some Less code:
//
@@ -211,9 +266,9 @@ var Parser = function Parser(context, imports, fileInfo) {
// And here's what the parse tree might look like:
//
// Ruleset (Selector '.class', [
// Rule ("color", Value ([Expression [Color #fff]]))
// Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
// Rule ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
// Declaration ("color", Value ([Expression [Color #fff]]))
// Declaration ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
// Declaration ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
// Ruleset (Selector [Element '>', '.child'], [...])
// ])
//
@@ -230,7 +285,7 @@ var Parser = function Parser(context, imports, fileInfo) {
// rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
// as represented by this simplified grammar:
//
// primary → (ruleset | rule)+
// primary → (ruleset | declaration)+
// ruleset → selector+ block
// block → '{' primary '}'
//
@@ -260,8 +315,8 @@ var Parser = function Parser(context, imports, fileInfo) {
continue;
}
node = mixin.definition() || this.rule() || this.ruleset() ||
mixin.call() || this.rulesetCall() || this.entities.call() || this.directive();
node = mixin.definition() || this.declaration() || this.ruleset() ||
mixin.call() || this.variableCall() || this.entities.call() || this.atrule();
if (node) {
root.push(node);
} else {
@@ -319,7 +374,7 @@ var Parser = function Parser(context, imports, fileInfo) {
// black border-collapse
//
keyword: function () {
var k = parserInput.$char("%") || parserInput.$re(/^[_A-Za-z-][_A-Za-z0-9-]*/);
var k = parserInput.$char("%") || parserInput.$re(/^\[?[_A-Za-z-][_A-Za-z0-9-]*\]?/);
if (k) {
return tree.Color.fromKeyword(k) || new(tree.Keyword)(k);
}
@@ -330,13 +385,10 @@ var Parser = function Parser(context, imports, fileInfo) {
//
// rgb(255, 0, 255)
//
// We also try to catch IE's `alpha()`, but let the `alpha` parser
// deal with the details.
//
// The arguments are parsed with the `entities.arguments` parser.
//
call: function () {
var name, nameLC, args, alpha, index = parserInput.i;
var name, args, func, index = parserInput.i;
// http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
if (parserInput.peek(/^url\(/i)) {
@@ -346,70 +398,98 @@ var Parser = function Parser(context, imports, fileInfo) {
parserInput.save();
name = parserInput.$re(/^([\w-]+|%|progid:[\w\.]+)\(/);
if (!name) { parserInput.forget(); return; }
if (!name) {
parserInput.forget();
return;
}
name = name[1];
nameLC = name.toLowerCase();
if (nameLC === 'alpha') {
alpha = parsers.alpha();
if (alpha) {
func = this.customFuncCall(name);
if (func) {
args = func.parse();
if (args && func.stop) {
parserInput.forget();
return alpha;
return args;
}
}
args = this.arguments();
args = this.arguments(args);
if (! parserInput.$char(')')) {
if (!parserInput.$char(')')) {
parserInput.restore("Could not parse call arguments or missing ')'");
return;
}
parserInput.forget();
return new(tree.Call)(name, args, index, fileInfo);
},
arguments: function () {
var argsSemiColon = [], argsComma = [],
expressions = [],
isSemiColonSeparated, value, arg;
//
// Parsing rules for functions with non-standard args, e.g.:
//
// boolean(not(2 > 1))
//
// This is a quick prototype, to be modified/improved when
// more custom-parsed funcs come (e.g. `selector(...)`)
//
customFuncCall: function (name) {
/* Ideally the table is to be moved out of here for faster perf.,
but it's quite tricky since it relies on all these `parsers`
and `expect` available only here */
return {
alpha: f(parsers.ieAlpha, true),
boolean: f(condition),
'if': f(condition)
}[name.toLowerCase()];
function f(parse, stop) {
return {
parse: parse, // parsing function
stop: stop // when true - stop after parse() and return its result,
// otherwise continue for plain args
};
}
function condition() {
return [expect(parsers.condition, 'expected condition')];
}
},
arguments: function (prevArgs) {
var argsComma = prevArgs || [],
argsSemiColon = [],
isSemiColonSeparated, value;
parserInput.save();
while (true) {
if (prevArgs) {
prevArgs = false;
} else {
value = parsers.detachedRuleset() || this.assignment() || parsers.expression();
if (!value) {
break;
}
arg = parsers.detachedRuleset() || this.assignment() || parsers.expression();
if (value.value && value.value.length == 1) {
value = value.value[0];
}
if (!arg) {
break;
argsComma.push(value);
}
value = arg;
if (arg.value && arg.value.length == 1) {
value = arg.value[0];
}
if (value) {
expressions.push(value);
}
argsComma.push(value);
if (parserInput.$char(',')) {
continue;
}
if (parserInput.$char(';') || isSemiColonSeparated) {
isSemiColonSeparated = true;
if (expressions.length > 1) {
value = new(tree.Value)(expressions);
}
value = (argsComma.length < 1) ? argsComma[0]
: new tree.Value(argsComma);
argsSemiColon.push(value);
expressions = [];
argsComma = [];
}
}
@@ -467,15 +547,17 @@ var Parser = function Parser(context, imports, fileInfo) {
return;
}
value = this.quoted() || this.variable() ||
value = this.quoted() || this.variable() || this.property() ||
parserInput.$re(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || "";
parserInput.autoCommentAbsorb = true;
expectChar(')');
return new(tree.URL)((value.value != null || value instanceof tree.Variable) ?
value : new(tree.Anonymous)(value), index, fileInfo);
return new(tree.URL)((value.value != null ||
value instanceof tree.Variable ||
value instanceof tree.Property) ?
value : new(tree.Anonymous)(value, index), index, fileInfo);
},
//
@@ -502,7 +584,27 @@ var Parser = function Parser(context, imports, fileInfo) {
return new(tree.Variable)("@" + curly[1], index, fileInfo);
}
},
//
// A Property accessor, such as `$color`, in
//
// background-color: $color
//
property: function () {
var name, index = parserInput.i;
if (parserInput.currentChar() === '$' && (name = parserInput.$re(/^\$[\w-]+/))) {
return new(tree.Property)(name, index, fileInfo);
}
},
// A property entity useing the protective {} e.g. ${prop}
propertyCurly: function () {
var curly, index = parserInput.i;
if (parserInput.currentChar() === '$' && (curly = parserInput.$re(/^\$\{([\w-]+)\}/))) {
return new(tree.Property)("$" + curly[1], index, fileInfo);
}
},
//
// A Hexadecimal color
//
@@ -612,15 +714,17 @@ var Parser = function Parser(context, imports, fileInfo) {
},
//
// The variable part of a variable definition. Used in the `rule` parser
// Call a variable value
//
// @fink();
// @fink()
//
rulesetCall: function () {
variableCall: function () {
var name;
if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\(\s*\)\s*;/))) {
return new tree.RulesetCall(name[1]);
if (parserInput.currentChar() === '@'
&& (name = parserInput.$re(/^(@[\w-]+)\(\s*\)/))
&& parsers.end()) {
return new tree.VariableCall(name[1]);
}
},
@@ -637,7 +741,7 @@ var Parser = function Parser(context, imports, fileInfo) {
do {
option = null;
elements = null;
while (! (option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
while (!(option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
e = this.element();
if (!e) {
break;
@@ -756,7 +860,7 @@ var Parser = function Parser(context, imports, fileInfo) {
.push({ variadic: true });
break;
}
arg = entities.variable() || entities.literal() || entities.keyword();
arg = entities.variable() || entities.property() || entities.literal() || entities.keyword();
}
if (!arg) {
@@ -779,7 +883,7 @@ var Parser = function Parser(context, imports, fileInfo) {
val = arg;
}
if (val && val instanceof tree.Variable) {
if (val && (val instanceof tree.Variable || val instanceof tree.Property)) {
if (parserInput.$char(':')) {
if (expressions.length > 0) {
if (isSemiColonSeparated) {
@@ -925,11 +1029,11 @@ var Parser = function Parser(context, imports, fileInfo) {
var entities = this.entities;
return this.comment() || entities.literal() || entities.variable() || entities.url() ||
entities.call() || entities.keyword() || entities.javascript();
entities.property() || entities.call() || entities.keyword() || entities.javascript();
},
//
// A Rule terminator. Note that we use `peek()` to check for '}',
// A Declaration terminator. Note that we use `peek()` to check for '}',
// because the `block` rule will be expecting it, but we still need to make sure
// it's there, if ';' was omitted.
//
@@ -942,17 +1046,18 @@ var Parser = function Parser(context, imports, fileInfo) {
//
// alpha(opacity=88)
//
alpha: function () {
ieAlpha: function () {
var value;
// http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
if (! parserInput.$re(/^opacity=/i)) { return; }
if (!parserInput.$re(/^opacity=/i)) { return; }
value = parserInput.$re(/^\d+/);
if (!value) {
value = expect(this.entities.variable, "Could not parse alpha");
value = expect(parsers.entities.variable, "Could not parse alpha");
value = '@{' + value.name.slice(1) + '}';
}
expectChar(')');
return new(tree.Alpha)(value);
return new tree.Quoted('', 'alpha(opacity=' + value + ')');
},
//
@@ -978,10 +1083,10 @@ var Parser = function Parser(context, imports, fileInfo) {
parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[\.#:](?=@)/) ||
this.entities.variableCurly();
if (! e) {
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 {
@@ -1032,14 +1137,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
@@ -1048,7 +1147,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');
@@ -1079,7 +1178,7 @@ var Parser = function Parser(context, imports, fileInfo) {
if (allExtends) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
},
attribute: function () {
if (! parserInput.$char('[')) { return; }
if (!parserInput.$char('[')) { return; }
var entities = this.entities,
key, val, op;
@@ -1138,7 +1237,7 @@ var Parser = function Parser(context, imports, fileInfo) {
}
while (true) {
s = this.lessSelector();
s = this.selector();
if (!s) {
break;
}
@@ -1151,7 +1250,7 @@ var Parser = function Parser(context, imports, fileInfo) {
if (s.condition && selectors.length > 1) {
error("Guards are only currently allowed on a single selector.");
}
if (! parserInput.$char(',')) { break; }
if (!parserInput.$char(',')) { break; }
if (s.condition) {
error("Guards are only currently allowed on a single selector.");
}
@@ -1169,7 +1268,7 @@ var Parser = function Parser(context, imports, fileInfo) {
parserInput.restore();
}
},
rule: function (tryAnonymous) {
declaration: function () {
var name, value, startOfRule = parserInput.i, c = parserInput.currentChar(), important, merge, isVariable;
if (c === '.' || c === '#' || c === '&' || c === ':') { return; }
@@ -1191,22 +1290,16 @@ var Parser = function Parser(context, imports, fileInfo) {
// where each item is a tree.Keyword or tree.Variable
merge = !isVariable && name.length > 1 && name.pop().value;
// prefer to try to parse first if its a variable or we are compressing
// but always fallback on the other one
var tryValueFirst = !tryAnonymous && (context.compress || isVariable);
// Try to store values as anonymous
// If we need the value later we'll re-parse it in ruleset.parseValue
value = this.anonymousValue();
if (value) {
parserInput.forget();
// anonymous values absorb the end ';' which is required for them to work
return new (tree.Declaration)(name, value, false, merge, startOfRule, fileInfo);
}
if (tryValueFirst) {
value = this.value();
}
if (!value) {
value = this.anonymousValue();
if (value) {
parserInput.forget();
// anonymous values absorb the end ';' which is required for them to work
return new (tree.Rule)(name, value, false, merge, startOfRule, fileInfo);
}
}
if (!tryValueFirst && !value) {
value = this.value();
}
@@ -1215,26 +1308,25 @@ var Parser = function Parser(context, imports, fileInfo) {
if (value && this.end()) {
parserInput.forget();
return new (tree.Rule)(name, value, important, merge, startOfRule, fileInfo);
} else {
return new (tree.Declaration)(name, value, important, merge, startOfRule, fileInfo);
}
else {
parserInput.restore();
if (value && !tryAnonymous) {
return this.rule(true);
}
}
} else {
parserInput.forget();
parserInput.restore();
}
},
anonymousValue: function () {
var match = parserInput.$re(/^([^@+\/'"*`(;{}-]*);/);
var index = parserInput.i;
var match = parserInput.$re(/^([^@\$+\/'"*`(;{}-]*);/);
if (match) {
return new(tree.Anonymous)(match[1]);
return new(tree.Anonymous)(match[1], index);
}
},
//
// An @import directive
// An @import atrule
//
// @import "lib";
//
@@ -1272,13 +1364,13 @@ var Parser = function Parser(context, imports, fileInfo) {
var o, options = {}, optionName, value;
// list of options, surrounded by parens
if (! parserInput.$char('(')) { return null; }
if (!parserInput.$char('(')) { return null; }
do {
o = this.importOption();
if (o) {
optionName = o;
value = true;
switch(optionName) {
switch (optionName) {
case "css":
optionName = "less";
value = false;
@@ -1289,7 +1381,7 @@ var Parser = function Parser(context, imports, fileInfo) {
break;
}
options[optionName] = value;
if (! parserInput.$char(',')) { break; }
if (!parserInput.$char(',')) { break; }
}
} while (o);
expectChar(')');
@@ -1315,7 +1407,7 @@ var Parser = function Parser(context, imports, fileInfo) {
e = this.value();
if (parserInput.$char(')')) {
if (p && e) {
nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, parserInput.i, fileInfo, true)));
nodes.push(new(tree.Paren)(new(tree.Declaration)(p, e, null, null, parserInput.i, fileInfo, true)));
} else if (e) {
nodes.push(new(tree.Paren)(e));
} else {
@@ -1339,12 +1431,12 @@ var Parser = function Parser(context, imports, fileInfo) {
e = this.mediaFeature();
if (e) {
features.push(e);
if (! parserInput.$char(',')) { break; }
if (!parserInput.$char(',')) { break; }
} else {
e = entities.variable();
if (e) {
features.push(e);
if (! parserInput.$char(',')) { break; }
if (!parserInput.$char(',')) { break; }
}
}
} while (e);
@@ -1384,45 +1476,68 @@ var Parser = function Parser(context, imports, fileInfo) {
},
//
// A @plugin directive, used to import compiler extensions dynamically.
// A @plugin directive, used to import plugins dynamically.
//
// @plugin "lib";
//
// Depending on our environment, importing is done differently:
// In the browser, it's an XHR request, in Node, it would be a
// file-system operation. The function used for importing is
// stored in `import`, which we pass to the Import constructor.
// @plugin (args) "lib";
//
plugin: function () {
var path,
var path, args, options,
index = parserInput.i,
dir = parserInput.$re(/^@plugin?\s+/);
if (dir) {
var options = { plugin : true };
args = this.pluginArgs();
if (args) {
options = {
pluginArgs: args,
isPlugin: true
};
}
else {
options = { isPlugin: true };
}
if ((path = this.entities.quoted() || this.entities.url())) {
if (!parserInput.$char(';')) {
parserInput.i = index;
error("missing semi-colon on plugin");
error("missing semi-colon on @plugin");
}
return new(tree.Import)(path, null, options, index, fileInfo);
}
else {
parserInput.i = index;
error("malformed plugin statement");
error("malformed @plugin statement");
}
}
},
pluginArgs: function() {
// list of options, surrounded by parens
parserInput.save();
if (!parserInput.$char('(')) {
parserInput.restore();
return null;
}
var args = parserInput.$re(/^\s*([^\);]+)\)\s*/);
if (args[1]) {
parserInput.forget();
return args[1].trim();
}
else {
parserInput.restore();
return null;
}
},
//
// A CSS Directive
// A CSS AtRule
//
// @charset "utf-8";
//
directive: function () {
atrule: function () {
var index = parserInput.i, name, value, rules, nonVendorSpecificName,
hasIdentifier, hasExpression, hasUnknown, hasBlock = true, isRooted = true;
@@ -1444,7 +1559,7 @@ var Parser = function Parser(context, imports, fileInfo) {
nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1);
}
switch(nonVendorSpecificName) {
switch (nonVendorSpecificName) {
case "@charset":
hasIdentifier = true;
hasBlock = false;
@@ -1493,13 +1608,13 @@ var Parser = function Parser(context, imports, fileInfo) {
if (rules || (!hasBlock && value && parserInput.$char(';'))) {
parserInput.forget();
return new (tree.Directive)(name, value, rules, index, fileInfo,
return new (tree.AtRule)(name, value, rules, index, fileInfo,
context.dumpLineNumbers ? getDebugInfo(index) : null,
isRooted
);
}
parserInput.restore("directive options not recognised");
parserInput.restore("at-rule options not recognised");
},
//
@@ -1511,18 +1626,18 @@ var Parser = function Parser(context, imports, fileInfo) {
// and before the `;`.
//
value: function () {
var e, expressions = [];
var e, expressions = [], index = parserInput.i;
do {
e = this.expression();
if (e) {
expressions.push(e);
if (! parserInput.$char(',')) { break; }
if (!parserInput.$char(',')) { break; }
}
} while (e);
if (expressions.length > 0) {
return new(tree.Value)(expressions);
return new(tree.Value)(expressions, index);
}
},
important: function () {
@@ -1761,13 +1876,14 @@ var Parser = function Parser(context, imports, fileInfo) {
operand: function () {
var entities = this.entities, negate;
if (parserInput.peek(/^-[@\(]/)) {
if (parserInput.peek(/^-[@\$\(]/)) {
negate = parserInput.$char('-');
}
var o = this.sub() || entities.dimension() ||
entities.color() || entities.variable() ||
entities.call() || entities.colorKeyword();
entities.property() || entities.call() ||
entities.colorKeyword();
if (negate) {
o.parensInOp = true;
@@ -1785,7 +1901,7 @@ var Parser = function Parser(context, imports, fileInfo) {
// @var * 2
//
expression: function () {
var entities = [], e, delim;
var entities = [], e, delim, index = parserInput.i;
do {
e = this.comment();
@@ -1800,7 +1916,7 @@ var Parser = function Parser(context, imports, fileInfo) {
if (!parserInput.peek(/^\/[\/*]/)) {
delim = parserInput.$char('/');
if (delim) {
entities.push(new(tree.Anonymous)(delim));
entities.push(new(tree.Anonymous)(delim, index));
}
}
}
@@ -1838,7 +1954,7 @@ var Parser = function Parser(context, imports, fileInfo) {
match(/^(\*?)/);
while (true) {
if (!match(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/)) {
if (!match(/^((?:[\w-]+)|(?:[@\$]\{[\w-]+\}))/)) {
break;
}
}
@@ -1854,10 +1970,11 @@ var Parser = function Parser(context, imports, fileInfo) {
}
for (k = 0; k < name.length; k++) {
s = name[k];
name[k] = (s.charAt(0) !== '@') ?
name[k] = (s.charAt(0) !== '@' && s.charAt(0) !== '$') ?
new(tree.Keyword)(s) :
new(tree.Variable)('@' + s.slice(2, -1),
index[k], fileInfo);
(s.charAt(0) === '@' ?
new(tree.Variable)('@' + s.slice(2, -1), index[k], fileInfo) :
new(tree.Property)('$' + s.slice(2, -1), index[k], fileInfo));
}
return name;
}

View File

@@ -8,7 +8,18 @@ var PluginManager = function(less) {
this.postProcessors = [];
this.installedPlugins = [];
this.fileManagers = [];
this.iterator = -1;
this.pluginCache = {};
this.Loader = new less.PluginLoader(less);
};
var pm, PluginManagerFactory = function(less, newFactory) {
if (newFactory || !pm) {
pm = new PluginManager(less);
}
return pm;
};
/**
* Adds all the plugins in the array
* @param {Array} plugins
@@ -23,11 +34,25 @@ PluginManager.prototype.addPlugins = function(plugins) {
/**
*
* @param plugin
* @param {String} filename
*/
PluginManager.prototype.addPlugin = function(plugin) {
PluginManager.prototype.addPlugin = function(plugin, filename, functionRegistry) {
this.installedPlugins.push(plugin);
plugin.install(this.less, this);
if (filename) {
this.pluginCache[filename] = plugin;
}
if (plugin.install) {
plugin.install(this.less, this, functionRegistry || this.less.functions.functionRegistry);
}
};
/**
*
* @param filename
*/
PluginManager.prototype.get = function(filename) {
return this.pluginCache[filename];
};
/**
* Adds a visitor. The visitor object has options on itself to determine
* when it should run.
@@ -103,6 +128,20 @@ PluginManager.prototype.getPostProcessors = function() {
PluginManager.prototype.getVisitors = function() {
return this.visitors;
};
PluginManager.prototype.visitor = function() {
var self = this;
return {
first: function() {
self.iterator = -1;
return self.visitors[self.iterator];
},
get: function() {
self.iterator += 1;
return self.visitors[self.iterator];
}
};
};
/**
*
* @returns {Array}
@@ -111,4 +150,6 @@ PluginManager.prototype.getVisitors = function() {
PluginManager.prototype.getFileManagers = function() {
return this.fileManagers;
};
module.exports = PluginManager;
//
module.exports = PluginManagerFactory;

View File

@@ -1,35 +0,0 @@
var LessError = require('../less-error'),
tree = require("../tree");
var FunctionImporter = module.exports = function FunctionImporter(context, fileInfo) {
this.fileInfo = fileInfo;
};
FunctionImporter.prototype.eval = function(contents, callback) {
var loaded = {},
loader,
registry;
registry = {
add: function(name, func) {
loaded[name] = func;
},
addMultiple: function(functions) {
Object.keys(functions).forEach(function(name) {
loaded[name] = functions[name];
});
}
};
try {
loader = new Function("functions", "tree", "fileInfo", contents);
loader(registry, tree, this.fileInfo);
} catch(e) {
callback(new LessError({
message: "Plugin evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" ,
filename: this.fileInfo.filename
}), null );
}
callback(null, { functions: loaded });
};

View File

@@ -1,10 +1,14 @@
var PromiseConstructor;
var PromiseConstructor,
utils = require('./utils');
module.exports = function(environment, ParseTree, ImportManager) {
var render = function (input, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
options = utils.defaults(this.options, {});
}
else {
options = utils.defaults(this.options, options || {});
}
if (!callback) {

View File

@@ -26,6 +26,9 @@ module.exports = function (SourceMapOutput, environment) {
if (this.options.sourceMapInputFilename) {
this.sourceMapInputFilename = sourceMapOutput.normalizeFilename(this.options.sourceMapInputFilename);
}
if (this.options.sourceMapBasepath !== undefined && this.sourceMapURL !== undefined) {
this.sourceMapURL = sourceMapOutput.removeBasepath(this.sourceMapURL);
}
return css + this.getCSSAppendage();
};

View File

@@ -28,21 +28,26 @@ module.exports = function (environment) {
this._column = 0;
};
SourceMapOutput.prototype.normalizeFilename = function(filename) {
filename = filename.replace(/\\/g, '/');
if (this._sourceMapBasepath && filename.indexOf(this._sourceMapBasepath) === 0) {
filename = filename.substring(this._sourceMapBasepath.length);
if (filename.charAt(0) === '\\' || filename.charAt(0) === '/') {
filename = filename.substring(1);
SourceMapOutput.prototype.removeBasepath = function(path) {
if (this._sourceMapBasepath && path.indexOf(this._sourceMapBasepath) === 0) {
path = path.substring(this._sourceMapBasepath.length);
if (path.charAt(0) === '\\' || path.charAt(0) === '/') {
path = path.substring(1);
}
}
return path;
};
SourceMapOutput.prototype.normalizeFilename = function(filename) {
filename = filename.replace(/\\/g, '/');
filename = this.removeBasepath(filename);
return (this._sourceMapRootpath || "") + filename;
};
SourceMapOutput.prototype.add = function(chunk, fileInfo, index, mapLines) {
//ignore adding empty strings
// ignore adding empty strings
if (!chunk) {
return;
}

View File

@@ -13,7 +13,7 @@ module.exports = function(root, options) {
//
// `{ color: new tree.Color('#f01') }` will become:
//
// new tree.Rule('@color',
// new tree.Declaration('@color',
// new tree.Value([
// new tree.Expression([
// new tree.Color('#f01')
@@ -25,50 +25,49 @@ module.exports = function(root, options) {
variables = Object.keys(variables).map(function (k) {
var value = variables[k];
if (! (value instanceof tree.Value)) {
if (! (value instanceof tree.Expression)) {
if (!(value instanceof tree.Value)) {
if (!(value instanceof tree.Expression)) {
value = new tree.Expression([value]);
}
value = new tree.Value([value]);
}
return new tree.Rule('@' + k, value, false, null, 0);
return new tree.Declaration('@' + k, value, false, null, 0);
});
evalEnv.frames = [new tree.Ruleset(null, variables)];
}
var preEvalVisitors = [],
visitors = [
var visitors = [
new visitor.JoinSelectorVisitor(),
new visitor.MarkVisibleSelectorsVisitor(true),
new visitor.ExtendVisitor(),
new visitor.ToCSSVisitor({compress: Boolean(options.compress)})
], i;
], v, visitorIterator;
// first() / get() allows visitors to be added while visiting
if (options.pluginManager) {
var pluginVisitors = options.pluginManager.getVisitors();
for (i = 0; i < pluginVisitors.length; i++) {
var pluginVisitor = pluginVisitors[i];
if (pluginVisitor.isPreEvalVisitor) {
preEvalVisitors.push(pluginVisitor);
} else {
if (pluginVisitor.isPreVisitor) {
visitors.splice(0, 0, pluginVisitor);
} else {
visitors.push(pluginVisitor);
}
visitorIterator = options.pluginManager.visitor();
visitorIterator.first();
while ((v = visitorIterator.get())) {
if (v.isPreEvalVisitor) {
v.run(root);
}
}
}
for (i = 0; i < preEvalVisitors.length; i++) {
preEvalVisitors[i].run(root);
}
evaldRoot = root.eval(evalEnv);
for (i = 0; i < visitors.length; i++) {
for (var i = 0; i < visitors.length; i++) {
visitors[i].run(evaldRoot);
}
if (options.pluginManager) {
visitorIterator.first();
while ((v = visitorIterator.get())) {
if (!v.isPreEvalVisitor) {
v.run(evaldRoot);
}
}
}
return evaldRoot;
};

View File

@@ -1,28 +0,0 @@
var Node = require("./node");
var Alpha = function (val) {
this.value = val;
};
Alpha.prototype = new Node();
Alpha.prototype.type = "Alpha";
Alpha.prototype.accept = function (visitor) {
this.value = visitor.visit(this.value);
};
Alpha.prototype.eval = function (context) {
if (this.value.eval) { return new Alpha(this.value.eval(context)); }
return this;
};
Alpha.prototype.genCSS = function (context, output) {
output.add("alpha(opacity=");
if (this.value.genCSS) {
this.value.genCSS(context, output);
} else {
output.add(this.value);
}
output.add(")");
};
module.exports = Alpha;

View File

@@ -2,9 +2,9 @@ var Node = require("./node");
var Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike, visibilityInfo) {
this.value = value;
this.index = index;
this._index = index;
this._fileInfo = currentFileInfo;
this.mapLines = mapLines;
this.currentFileInfo = currentFileInfo;
this.rulesetLike = (typeof rulesetLike === 'undefined') ? false : rulesetLike;
this.allowRoot = true;
this.copyVisibilityInfo(visibilityInfo);
@@ -12,7 +12,7 @@ var Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike,
Anonymous.prototype = new Node();
Anonymous.prototype.type = "Anonymous";
Anonymous.prototype.eval = function () {
return new Anonymous(this.value, this.index, this.currentFileInfo, this.mapLines, this.rulesetLike, this.visibilityInfo());
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;
@@ -21,6 +21,9 @@ Anonymous.prototype.isRulesetLike = function() {
return this.rulesetLike;
};
Anonymous.prototype.genCSS = function (context, output) {
output.add(this.value, this.currentFileInfo, this.index, this.mapLines);
this.nodeVisible = Boolean(this.value);
if (this.nodeVisible) {
output.add(this.value, this._fileInfo, this._index, this.mapLines);
}
};
module.exports = Anonymous;

View File

@@ -1,34 +1,36 @@
var Node = require("./node"),
Selector = require("./selector"),
Ruleset = require("./ruleset");
Ruleset = require("./ruleset"),
Anonymous = require('./anonymous');
var Directive = function (name, value, rules, index, currentFileInfo, debugInfo, isRooted, visibilityInfo) {
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;
} else {
this.rules = [rules];
this.rules[0].selectors = (new Selector([], null, null, this.index, currentFileInfo)).createEmptySelectors();
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.currentFileInfo = currentFileInfo;
this._index = index;
this._fileInfo = currentFileInfo;
this.debugInfo = debugInfo;
this.isRooted = isRooted || false;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
};
Directive.prototype = new Node();
Directive.prototype.type = "Directive";
Directive.prototype.accept = function (visitor) {
AtRule.prototype = new Node();
AtRule.prototype.type = "AtRule";
AtRule.prototype.accept = function (visitor) {
var value = this.value, rules = this.rules;
if (rules) {
this.rules = visitor.visitArray(rules);
@@ -37,15 +39,15 @@ Directive.prototype.accept = function (visitor) {
this.value = visitor.visit(value);
}
};
Directive.prototype.isRulesetLike = function() {
AtRule.prototype.isRulesetLike = function() {
return this.rules || !this.isCharset();
};
Directive.prototype.isCharset = function() {
AtRule.prototype.isCharset = function() {
return "@charset" === this.name;
};
Directive.prototype.genCSS = function (context, output) {
AtRule.prototype.genCSS = function (context, output) {
var value = this.value, rules = this.rules;
output.add(this.name, this.currentFileInfo, this.index);
output.add(this.name, this.fileInfo(), this.getIndex());
if (value) {
output.add(' ');
value.genCSS(context, output);
@@ -56,14 +58,14 @@ Directive.prototype.genCSS = function (context, output) {
output.add(';');
}
};
Directive.prototype.eval = function (context) {
AtRule.prototype.eval = function (context) {
var mediaPathBackup, mediaBlocksBackup, value = this.value, rules = this.rules;
//media stored inside other directive should not bubble over it
//backpup media bubbling information
// media stored inside other atrule should not bubble over it
// backpup media bubbling information
mediaPathBackup = context.mediaPath;
mediaBlocksBackup = context.mediaBlocks;
//deleted media bubbling information
// deleted media bubbling information
context.mediaPath = [];
context.mediaBlocks = [];
@@ -75,32 +77,32 @@ Directive.prototype.eval = function (context) {
rules = [rules[0].eval(context)];
rules[0].root = true;
}
//restore media bubbling information
// restore media bubbling information
context.mediaPath = mediaPathBackup;
context.mediaBlocks = mediaBlocksBackup;
return new Directive(this.name, value, rules,
this.index, this.currentFileInfo, this.debugInfo, this.isRooted, this.visibilityInfo());
return new AtRule(this.name, value, rules,
this.getIndex(), this.fileInfo(), this.debugInfo, this.isRooted, this.visibilityInfo());
};
Directive.prototype.variable = function (name) {
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);
}
};
Directive.prototype.find = function () {
AtRule.prototype.find = function () {
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], arguments);
}
};
Directive.prototype.rulesets = function () {
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]);
}
};
Directive.prototype.outputRuleset = function (context, output, rules) {
AtRule.prototype.outputRuleset = function (context, output, rules) {
var ruleCnt = rules.length, i;
context.tabLevel = (context.tabLevel | 0) + 1;
@@ -131,4 +133,4 @@ Directive.prototype.outputRuleset = function (context, output, rules) {
context.tabLevel--;
};
module.exports = Directive;
module.exports = AtRule;

View File

@@ -1,4 +1,5 @@
var Node = require("./node"),
Anonymous = require("./anonymous"),
FunctionCaller = require("../functions/function-caller");
//
// A function call node.
@@ -6,8 +7,9 @@ var Node = require("./node"),
var Call = function (name, args, index, currentFileInfo) {
this.name = name;
this.args = args;
this.index = index;
this.currentFileInfo = currentFileInfo;
this.mathOn = name === 'calc' ? false : true;
this._index = index;
this._fileInfo = currentFileInfo;
};
Call.prototype = new Node();
Call.prototype.type = "Call";
@@ -28,30 +30,55 @@ Call.prototype.accept = function (visitor) {
// The function should receive the value, not the variable.
//
Call.prototype.eval = function (context) {
var args = this.args.map(function (a) { return a.eval(context); }),
result, funcCaller = new FunctionCaller(this.name, context, this.index, this.currentFileInfo);
/**
* Turn off math for calc(), and switch back on for evaluating nested functions
*/
var currentMathContext = context.mathOn;
context.mathOn = this.mathOn;
var args = this.args.map(function (a) { return a.eval(context); });
context.mathOn = currentMathContext;
var result, funcCaller = new FunctionCaller(this.name, context, this.getIndex(), this.fileInfo());
if (funcCaller.isValid()) {
try {
result = funcCaller.call(args);
} catch (e) {
throw { type: e.type || "Runtime",
message: "error evaluating function `" + this.name + "`" +
(e.message ? ': ' + e.message : ''),
index: this.index, filename: this.currentFileInfo.filename };
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.index = this.index;
result.currentFileInfo = this.currentFileInfo;
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;
}
}
return new Call(this.name, args, this.index, this.currentFileInfo);
return new Call(this.name, args, this.getIndex(), this.fileInfo());
};
Call.prototype.genCSS = function (context, output) {
output.add(this.name + "(", this.currentFileInfo, this.index);
output.add(this.name + "(", this.fileInfo(), this.getIndex());
for (var i = 0; i < this.args.length; i++) {
this.args[i].genCSS(context, output);

View File

@@ -99,7 +99,7 @@ Color.prototype.toCSS = function (context, doNotCompress) {
// we create a new Color node to hold the result.
//
Color.prototype.operate = function (context, op, other) {
var rgb = [];
var rgb = new Array(3);
var alpha = this.alpha * (1 - other.alpha) + other.alpha;
for (var c = 0; c < 3; c++) {
rgb[c] = this._operate(context, op, this.rgb[c], other.rgb[c]);
@@ -132,7 +132,7 @@ Color.prototype.toHSL = function () {
}
return { h: h * 360, s: s, l: l, a: a };
};
//Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
// Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
Color.prototype.toHSV = function () {
var r = this.rgb[0] / 255,
g = this.rgb[1] / 255,
@@ -152,7 +152,7 @@ Color.prototype.toHSV = function () {
if (max === min) {
h = 0;
} else {
switch(max) {
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;

View File

@@ -4,15 +4,15 @@ var Node = require("./node"),
var Comment = function (value, isLineComment, index, currentFileInfo) {
this.value = value;
this.isLineComment = isLineComment;
this.index = index;
this.currentFileInfo = currentFileInfo;
this._index = index;
this._fileInfo = currentFileInfo;
this.allowRoot = true;
};
Comment.prototype = new Node();
Comment.prototype.type = "Comment";
Comment.prototype.genCSS = function (context, output) {
if (this.debugInfo) {
output.add(getDebugInfo(context, this), this.currentFileInfo, this.index);
output.add(getDebugInfo(context, this), this.fileInfo(), this.getIndex());
}
output.add(this.value);
};

View File

@@ -4,7 +4,7 @@ var Condition = function (op, l, r, i, negate) {
this.op = op.trim();
this.lvalue = l;
this.rvalue = r;
this.index = i;
this._index = i;
this.negate = negate;
};
Condition.prototype = new Node();

View File

@@ -1,7 +1,7 @@
var debugInfo = function(context, ctx, lineSeparator) {
var result = "";
if (context.dumpLineNumbers && !context.compress) {
switch(context.dumpLineNumbers) {
switch (context.dumpLineNumbers) {
case 'comments':
result = debugInfo.asComment(ctx);
break;

View File

@@ -1,18 +1,20 @@
var Node = require("./node"),
Value = require("./value"),
Keyword = require("./keyword");
Keyword = require("./keyword"),
Anonymous = require("./anonymous");
var Rule = function (name, value, important, merge, index, currentFileInfo, inline, variable) {
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;
this.currentFileInfo = currentFileInfo;
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);
};
function evalName(context, name) {
@@ -24,21 +26,21 @@ function evalName(context, name) {
return value;
}
Rule.prototype = new Node();
Rule.prototype.type = "Rule";
Rule.prototype.genCSS = function (context, output) {
output.add(this.name + (context.compress ? ':' : ': '), this.currentFileInfo, this.index);
Declaration.prototype = new Node();
Declaration.prototype.type = "Declaration";
Declaration.prototype.genCSS = function (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.currentFileInfo.filename;
catch (e) {
e.index = this._index;
e.filename = this._fileInfo.filename;
throw e;
}
output.add(this.important + ((this.inline || (context.lastRule && context.compress)) ? "" : ";"), this.currentFileInfo, this.index);
output.add(this.important + ((this.inline || (context.lastRule && context.compress)) ? "" : ";"), this._fileInfo, this._index);
};
Rule.prototype.eval = function (context) {
Declaration.prototype.eval = function (context) {
var strictMathBypass = false, name = this.name, evaldValue, variable = this.variable;
if (typeof name !== "string") {
// expand 'primitive' name directly to get
@@ -57,7 +59,7 @@ Rule.prototype.eval = function (context) {
if (!this.variable && evaldValue.type === "DetachedRuleset") {
throw { message: "Rulesets cannot be evaluated on a property.",
index: this.index, filename: this.currentFileInfo.filename };
index: this.getIndex(), filename: this.fileInfo().filename };
}
var important = this.important,
importantResult = context.importantScope.pop();
@@ -65,17 +67,17 @@ Rule.prototype.eval = function (context) {
important = importantResult.important;
}
return new Rule(name,
return new Declaration(name,
evaldValue,
important,
this.merge,
this.index, this.currentFileInfo, this.inline,
this.getIndex(), this.fileInfo(), this.inline,
variable);
}
catch(e) {
catch (e) {
if (typeof e.index !== 'number') {
e.index = this.index;
e.filename = this.currentFileInfo.filename;
e.index = this.getIndex();
e.filename = this.fileInfo().filename;
}
throw e;
}
@@ -85,12 +87,12 @@ Rule.prototype.eval = function (context) {
}
}
};
Rule.prototype.makeImportant = function () {
return new Rule(this.name,
Declaration.prototype.makeImportant = function () {
return new Declaration(this.name,
this.value,
"!important",
this.merge,
this.index, this.currentFileInfo, this.inline);
this.getIndex(), this.fileInfo(), this.inline);
};
module.exports = Rule;
module.exports = Declaration;

View File

@@ -1,9 +1,11 @@
var Node = require("./node"),
contexts = require("../contexts");
contexts = require("../contexts"),
utils = require("../utils");
var DetachedRuleset = function (ruleset, frames) {
this.ruleset = ruleset;
this.frames = frames;
this.setParent(this.ruleset, this);
};
DetachedRuleset.prototype = new Node();
DetachedRuleset.prototype.type = "DetachedRuleset";
@@ -12,7 +14,7 @@ DetachedRuleset.prototype.accept = function (visitor) {
this.ruleset = visitor.visit(this.ruleset);
};
DetachedRuleset.prototype.eval = function (context) {
var frames = this.frames || context.frames.slice(0);
var frames = this.frames || utils.copyArray(context.frames);
return new DetachedRuleset(this.ruleset, frames);
};
DetachedRuleset.prototype.callEval = function (context) {

View File

@@ -8,8 +8,12 @@ var Node = require("./node"),
//
var 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);
};
Dimension.prototype = new Node();
@@ -57,7 +61,7 @@ Dimension.prototype.genCSS = function (context, output) {
// we default to the first Dimension's unit,
// so `1px + 2` will yield `3px`.
Dimension.prototype.operate = function (context, op, other) {
/*jshint noempty:false */
/* jshint noempty:false */
var value = this._operate(context, op, this.value, other.value),
unit = this.unit.clone();

View File

@@ -2,7 +2,7 @@ var Node = require("./node"),
Paren = require("./paren"),
Combinator = require("./combinator");
var Element = function (combinator, value, index, currentFileInfo, info) {
var Element = function (combinator, value, index, currentFileInfo, visibilityInfo) {
this.combinator = combinator instanceof Combinator ?
combinator : new Combinator(combinator);
@@ -13,9 +13,10 @@ var Element = function (combinator, value, index, currentFileInfo, info) {
} else {
this.value = "";
}
this.index = index;
this.currentFileInfo = currentFileInfo;
this.copyVisibilityInfo(info);
this._index = index;
this._fileInfo = currentFileInfo;
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.combinator, this);
};
Element.prototype = new Node();
Element.prototype.type = "Element";
@@ -29,17 +30,17 @@ Element.prototype.accept = function (visitor) {
Element.prototype.eval = function (context) {
return new Element(this.combinator,
this.value.eval ? this.value.eval(context) : this.value,
this.index,
this.currentFileInfo, this.visibilityInfo());
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
};
Element.prototype.clone = function () {
return new Element(this.combinator,
this.value,
this.index,
this.currentFileInfo, this.visibilityInfo());
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
};
Element.prototype.genCSS = function (context, output) {
output.add(this.toCSS(context), this.currentFileInfo, this.index);
output.add(this.toCSS(context), this.fileInfo(), this.getIndex());
};
Element.prototype.toCSS = function (context) {
context = context || {};

View File

@@ -4,14 +4,14 @@ var Node = require("./node"),
var Extend = function Extend(selector, option, index, currentFileInfo, visibilityInfo) {
this.selector = selector;
this.option = option;
this.index = index;
this.object_id = Extend.next_id++;
this.parent_ids = [this.object_id];
this.currentFileInfo = currentFileInfo || {};
this._index = index;
this._fileInfo = currentFileInfo;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
switch(option) {
switch (option) {
case "all":
this.allowBefore = true;
this.allowAfter = true;
@@ -21,6 +21,7 @@ var Extend = function Extend(selector, option, index, currentFileInfo, visibilit
this.allowAfter = false;
break;
}
this.setParent(this.selector, this);
};
Extend.next_id = 0;
@@ -30,12 +31,12 @@ 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.index, this.currentFileInfo, this.visibilityInfo());
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.index, this.currentFileInfo, this.visibilityInfo());
return new Extend(this.selector, this.option, this.getIndex(), this.fileInfo(), this.visibilityInfo());
};
//it concatenates (joins) all selectors in selector array
// it concatenates (joins) all selectors in selector array
Extend.prototype.findSelfSelectors = function (selectors) {
var selfElements = [],
i,

View File

@@ -3,7 +3,9 @@ var Node = require("./node"),
URL = require("./url"),
Quoted = require("./quoted"),
Ruleset = require("./ruleset"),
Anonymous = require("./anonymous");
Anonymous = require("./anonymous"),
utils = require("../utils"),
LessError = require("../less-error");
//
// CSS @import node
@@ -19,21 +21,23 @@ var Node = require("./node"),
//
var Import = function (path, features, options, index, currentFileInfo, visibilityInfo) {
this.options = options;
this.index = index;
this._index = index;
this._fileInfo = currentFileInfo;
this.path = path;
this.features = features;
this.currentFileInfo = currentFileInfo;
this.allowRoot = true;
if (this.options.less !== undefined || this.options.inline) {
this.css = !this.options.less || this.options.inline;
} else {
var pathValue = this.getPath();
if (pathValue && /[#\.\&\?\/]css([\?;].*)?$/.test(pathValue)) {
if (pathValue && /[#\.\&\?]css([\?;].*)?$/.test(pathValue)) {
this.css = true;
}
}
this.copyVisibilityInfo(visibilityInfo);
this.setParent(this.features, this);
this.setParent(this.path, this);
};
//
@@ -52,13 +56,13 @@ Import.prototype.accept = function (visitor) {
this.features = visitor.visit(this.features);
}
this.path = visitor.visit(this.path);
if (!this.options.plugin && !this.options.inline && this.root) {
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.currentFileInfo.reference === undefined) {
output.add("@import ", this.currentFileInfo, this.index);
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(" ");
@@ -89,11 +93,11 @@ Import.prototype.evalForImport = function (context) {
path = path.value;
}
return new Import(path.eval(context), this.features, this.options, this.index, this.currentFileInfo, this.visibilityInfo());
return new Import(path.eval(context), this.features, this.options, this._index, this._fileInfo, this.visibilityInfo());
};
Import.prototype.evalPath = function (context) {
var path = this.path.eval(context);
var rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
var rootpath = this._fileInfo && this._fileInfo.rootpath;
if (!(path instanceof URL)) {
if (rootpath) {
@@ -113,8 +117,8 @@ Import.prototype.eval = function (context) {
if (this.options.reference || this.blocksVisibility()) {
if (result.length || result.length === 0) {
result.forEach(function (node) {
node.addVisibilityBlock();
}
node.addVisibilityBlock();
}
);
} else {
result.addVisibilityBlock();
@@ -126,11 +130,21 @@ Import.prototype.doEval = function (context) {
var ruleset, registry,
features = this.features && this.features.eval(context);
if (this.options.plugin) {
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 [];
}
@@ -144,20 +158,20 @@ Import.prototype.doEval = function (context) {
}
if (this.options.inline) {
var contents = new Anonymous(this.root, 0,
{
filename: this.importedFilename,
reference: this.path.currentFileInfo && this.path.currentFileInfo.reference
}, true, true);
{
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) {
var newImport = new Import(this.evalPath(context), features, this.options, this.index);
var newImport = new Import(this.evalPath(context), features, this.options, this._index);
if (!newImport.css && this.error) {
throw this.error;
}
return newImport;
} else {
ruleset = new Ruleset(null, this.root.rules.slice(0));
ruleset = new Ruleset(null, utils.copyArray(this.root.rules));
ruleset.evalImports(context);
return this.features ? new Media(ruleset.rules, this.features.value) : ruleset.rules;

View File

@@ -1,15 +1,15 @@
var tree = {};
var tree = Object.create(null);
tree.Node = require('./node');
tree.Alpha = require('./alpha');
tree.Color = require('./color');
tree.Directive = require('./directive');
tree.AtRule = require('./atrule');
tree.DetachedRuleset = require('./detached-ruleset');
tree.Operation = require('./operation');
tree.Dimension = require('./dimension');
tree.Unit = require('./unit');
tree.Keyword = require('./keyword');
tree.Variable = require('./variable');
tree.Property = require('./property');
tree.Ruleset = require('./ruleset');
tree.Element = require('./element');
tree.Attribute = require('./attribute');
@@ -17,7 +17,7 @@ tree.Combinator = require('./combinator');
tree.Selector = require('./selector');
tree.Quoted = require('./quoted');
tree.Expression = require('./expression');
tree.Rule = require('./rule');
tree.Declaration = require('./declaration');
tree.Call = require('./call');
tree.URL = require('./url');
tree.Import = require('./import');
@@ -36,6 +36,6 @@ tree.Media = require('./media');
tree.UnicodeDescriptor = require('./unicode-descriptor');
tree.Negative = require('./negative');
tree.Extend = require('./extend');
tree.RulesetCall = require('./ruleset-call');
tree.VariableCall = require('./variable-call');
module.exports = tree;

View File

@@ -6,8 +6,8 @@ var JsEvalNode = require("./js-eval-node"),
var JavaScript = function (string, escaped, index, currentFileInfo) {
this.escaped = escaped;
this.expression = string;
this.index = index;
this.currentFileInfo = currentFileInfo;
this._index = index;
this._fileInfo = currentFileInfo;
};
JavaScript.prototype = new JsEvalNode();
JavaScript.prototype.type = "JavaScript";
@@ -17,7 +17,7 @@ JavaScript.prototype.eval = function(context) {
if (typeof result === 'number') {
return new Dimension(result);
} else if (typeof result === 'string') {
return new Quoted('"' + result + '"', result, this.escaped, this.index);
return new Quoted('"' + result + '"', result, this.escaped, this._index);
} else if (Array.isArray(result)) {
return new Anonymous(result.join(', '));
} else {

View File

@@ -10,28 +10,28 @@ JsEvalNode.prototype.evaluateJavaScript = function (expression, context) {
that = this,
evalContext = {};
if (context.javascriptEnabled !== undefined && !context.javascriptEnabled) {
throw { message: "You are using JavaScript, which has been disabled.",
filename: this.currentFileInfo.filename,
index: this.index };
if (!context.javascriptEnabled) {
throw { message: "Inline JavaScript is not enabled. Is it set in your options?",
filename: this.fileInfo().filename,
index: this.getIndex() };
}
expression = expression.replace(/@\{([\w-]+)\}/g, function (_, name) {
return that.jsify(new Variable('@' + name, that.index, that.currentFileInfo).eval(context));
return 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.currentFileInfo.filename,
index: this.index };
filename: this.fileInfo().filename,
index: this.getIndex() };
}
var variables = context.frames[0].variables();
for (var k in variables) {
if (variables.hasOwnProperty(k)) {
/*jshint loopfunc:true */
/* jshint loopfunc:true */
evalContext[k.slice(1)] = {
value: variables[k].value,
toJS: function () {
@@ -45,8 +45,8 @@ JsEvalNode.prototype.evaluateJavaScript = function (expression, context) {
result = expression.call(evalContext);
} catch (e) {
throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" ,
filename: this.currentFileInfo.filename,
index: this.index };
filename: this.fileInfo().filename,
index: this.getIndex() };
}
return result;
};

View File

@@ -3,23 +3,27 @@ var Ruleset = require("./ruleset"),
Selector = require("./selector"),
Anonymous = require("./anonymous"),
Expression = require("./expression"),
Directive = require("./directive");
AtRule = require("./atrule"),
utils = require("../utils");
var Media = function (value, features, index, currentFileInfo, visibilityInfo) {
this.index = index;
this.currentFileInfo = currentFileInfo;
this._index = index;
this._fileInfo = currentFileInfo;
var selectors = (new Selector([], null, null, this.index, this.currentFileInfo)).createEmptySelectors();
var 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);
};
Media.prototype = new Directive();
Media.prototype = new AtRule();
Media.prototype.type = "Media";
Media.prototype.isRulesetLike = true;
Media.prototype.isRulesetLike = function() { return true; };
Media.prototype.accept = function (visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
@@ -29,7 +33,7 @@ Media.prototype.accept = function (visitor) {
}
};
Media.prototype.genCSS = function (context, output) {
output.add('@media ', this.currentFileInfo, this.index);
output.add('@media ', this._fileInfo, this._index);
this.features.genCSS(context, output);
this.outputRuleset(context, output, this.rules);
};
@@ -39,24 +43,13 @@ Media.prototype.eval = function (context) {
context.mediaPath = [];
}
var media = new Media(null, [], this.index, this.currentFileInfo, this.visibilityInfo());
var media = new Media(null, [], this._index, this._fileInfo, this.visibilityInfo());
if (this.debugInfo) {
this.rules[0].debugInfo = this.debugInfo;
media.debugInfo = this.debugInfo;
}
var strictMathBypass = false;
if (!context.strictMath) {
strictMathBypass = true;
context.strictMath = true;
}
try {
media.features = this.features.eval(context);
}
finally {
if (strictMathBypass) {
context.strictMath = false;
}
}
media.features = this.features.eval(context);
context.mediaPath.push(media);
context.mediaBlocks.push(media);
@@ -76,10 +69,11 @@ Media.prototype.evalTop = function (context) {
// Render all dependent Media blocks.
if (context.mediaBlocks.length > 1) {
var selectors = (new Selector([], null, null, this.index, this.currentFileInfo)).createEmptySelectors();
var 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;
@@ -116,6 +110,7 @@ Media.prototype.evalNested = function (context) {
return new Expression(path);
}));
this.setParent(this.features, this);
// Fake a tree-node that doesn't output anything.
return new Ruleset([], []);
@@ -140,6 +135,7 @@ Media.prototype.bubbleSelectors = function (selectors) {
if (!selectors) {
return;
}
this.rules = [new Ruleset(selectors.slice(0), [this.rules[0]])];
this.rules = [new Ruleset(utils.copyArray(selectors), [this.rules[0]])];
this.setParent(this.rules, this);
};
module.exports = Media;

View File

@@ -6,10 +6,11 @@ var Node = require("./node"),
var MixinCall = function (elements, args, index, currentFileInfo, important) {
this.selector = new Selector(elements);
this.arguments = args || [];
this.index = index;
this.currentFileInfo = currentFileInfo;
this._index = index;
this._fileInfo = currentFileInfo;
this.important = important;
this.allowRoot = true;
this.setParent(this.selector, this);
};
MixinCall.prototype = new Node();
MixinCall.prototype.type = "MixinCall";
@@ -117,7 +118,7 @@ MixinCall.prototype.eval = function (context) {
if ((count[defTrue] + count[defFalse]) > 1) {
throw { type: 'Runtime',
message: 'Ambiguous use of `default()` found when matching for `' + this.format(args) + '`',
index: this.index, filename: this.currentFileInfo.filename };
index: this.getIndex(), filename: this.fileInfo().filename };
}
}
@@ -135,7 +136,7 @@ MixinCall.prototype.eval = function (context) {
this._setVisibilityToReplacement(newRules);
Array.prototype.push.apply(rules, newRules);
} catch (e) {
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
throw { message: e.message, index: this.getIndex(), filename: this.fileInfo().filename, stack: e.stack };
}
}
}
@@ -148,11 +149,11 @@ MixinCall.prototype.eval = function (context) {
if (isOneFound) {
throw { type: 'Runtime',
message: 'No matching definition was found for `' + this.format(args) + '`',
index: this.index, filename: this.currentFileInfo.filename };
index: this.getIndex(), filename: this.fileInfo().filename };
} else {
throw { type: 'Name',
message: this.selector.toCSS().trim() + " is undefined",
index: this.index, filename: this.currentFileInfo.filename };
index: this.getIndex(), filename: this.fileInfo().filename };
}
};

View File

@@ -1,13 +1,14 @@
var Selector = require("./selector"),
Element = require("./element"),
Ruleset = require("./ruleset"),
Rule = require("./rule"),
Declaration = require("./declaration"),
Expression = require("./expression"),
contexts = require("../contexts");
contexts = require("../contexts"),
utils = require("../utils");
var Definition = function (name, params, rules, condition, variadic, frames, visibilityInfo) {
this.name = name;
this.selectors = [new Selector([new Element(null, name, this.index, this.currentFileInfo)])];
this.selectors = [new Selector([new Element(null, name, this._index, this._fileInfo)])];
this.params = params;
this.condition = condition;
this.variadic = variadic;
@@ -42,10 +43,10 @@ Definition.prototype.accept = function (visitor) {
}
};
Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArguments) {
/*jshint boss:true */
/* jshint boss:true */
var frame = new Ruleset(null, null),
varargs, arg,
params = this.params.slice(0),
params = utils.copyArray(this.params),
i, j, val, name, isNamedFound, argIndex, argsLength = 0;
if (mixinEnv.frames && mixinEnv.frames[0] && mixinEnv.frames[0].functionRegistry) {
@@ -54,7 +55,7 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
mixinEnv = new contexts.Eval(mixinEnv, [frame].concat(mixinEnv.frames));
if (args) {
args = args.slice(0);
args = utils.copyArray(args);
argsLength = args.length;
for (i = 0; i < argsLength; i++) {
@@ -64,7 +65,7 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
for (j = 0; j < params.length; j++) {
if (!evaldArguments[j] && name === params[j].name) {
evaldArguments[j] = arg.value.eval(context);
frame.prependRule(new Rule(name, arg.value.eval(context)));
frame.prependRule(new Declaration(name, arg.value.eval(context)));
isNamedFound = true;
break;
}
@@ -92,7 +93,7 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
for (j = argIndex; j < argsLength; j++) {
varargs.push(args[j].value.eval(context));
}
frame.prependRule(new Rule(name, new Expression(varargs).eval(context)));
frame.prependRule(new Declaration(name, new Expression(varargs).eval(context)));
} else {
val = arg && arg.value;
if (val) {
@@ -105,7 +106,7 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
' (' + argsLength + ' for ' + this.arity + ')' };
}
frame.prependRule(new Rule(name, val));
frame.prependRule(new Declaration(name, val));
evaldArguments[i] = val;
}
}
@@ -132,7 +133,7 @@ Definition.prototype.makeImportant = function() {
return result;
};
Definition.prototype.eval = function (context) {
return new Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || context.frames.slice(0));
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) {
var _arguments = [],
@@ -140,9 +141,9 @@ Definition.prototype.evalCall = function (context, args, important) {
frame = this.evalParams(context, new contexts.Eval(context, mixinFrames), args, _arguments),
rules, ruleset;
frame.prependRule(new Rule('@arguments', new Expression(_arguments).eval(context)));
frame.prependRule(new Declaration('@arguments', new Expression(_arguments).eval(context)));
rules = this.rules.slice(0);
rules = utils.copyArray(this.rules);
ruleset = new Ruleset(null, rules);
ruleset.originalRuleset = this;
@@ -155,7 +156,7 @@ Definition.prototype.evalCall = function (context, args, important) {
Definition.prototype.matchCondition = function (args, context) {
if (this.condition && !this.condition.eval(
new contexts.Eval(context,
[this.evalParams(context, /* the parameter variables*/
[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
@@ -173,7 +174,7 @@ Definition.prototype.matchArgs = function (args, context) {
}
}, 0);
if (! this.variadic) {
if (!this.variadic) {
if (requiredArgsCnt < this.required) {
return false;
}

View File

@@ -1,5 +1,39 @@
var Node = function() {
this.parent = null;
this.visibilityBlocks = undefined;
this.nodeVisible = undefined;
this.rootNode = null;
this.parsed = null;
var self = this;
Object.defineProperty(this, "currentFileInfo", {
get: function() { return self.fileInfo(); }
});
Object.defineProperty(this, "index", {
get: function() { return self.getIndex(); }
});
};
Node.prototype.setParent = function(nodes, parent) {
function set(node) {
if (node && node instanceof Node) {
node.parent = parent;
}
}
if (Array.isArray(nodes)) {
nodes.forEach(set);
}
else {
set(nodes);
}
};
Node.prototype.getIndex = function() {
return this._index || (this.parent && this.parent.getIndex()) || 0;
};
Node.prototype.fileInfo = function() {
return this._fileInfo || (this.parent && this.parent.fileInfo()) || {};
};
Node.prototype.isRulesetLike = function() { return false; };
Node.prototype.toCSS = function (context) {
var strs = [];
this.genCSS(context, {
@@ -29,8 +63,8 @@ Node.prototype._operate = function (context, op, a, b) {
};
Node.prototype.fround = function(context, value) {
var precision = context && context.numPrecision;
//add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded...
return (precision == null) ? value : Number((value + 2e-16).toFixed(precision));
// add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999...) are properly rounded:
return (precision) ? Number((value + 2e-16).toFixed(precision)) : value;
};
Node.compare = function (a, b) {
/* returns:
@@ -90,13 +124,13 @@ Node.prototype.removeVisibilityBlock = function () {
}
this.visibilityBlocks = this.visibilityBlocks - 1;
};
//Turns on node visibility - if called node will be shown in output regardless
//of whether it comes from import by reference or not
// Turns on node visibility - if called node will be shown in output regardless
// of whether it comes from import by reference or not
Node.prototype.ensureVisibility = function () {
this.nodeVisible = true;
};
//Turns off node visibility - if called node will NOT be shown in output regardless
//of whether it comes from import by reference or not
// Turns off node visibility - if called node will NOT be shown in output regardless
// of whether it comes from import by reference or not
Node.prototype.ensureInvisibility = function () {
this.nodeVisible = false;
};

View File

@@ -25,7 +25,7 @@ Operation.prototype.eval = function (context) {
}
if (!a.operate) {
throw { type: "Operation",
message: "Operation on an invalid type" };
message: "Operation on an invalid type" };
}
return a.operate(context, this.op, b);

70
lib/less/tree/property.js Normal file
View File

@@ -0,0 +1,70 @@
var Node = require("./node"),
Declaration = require("./declaration");
var Property = function (name, index, currentFileInfo) {
this.name = name;
this._index = index;
this._fileInfo = currentFileInfo;
};
Property.prototype = new Node();
Property.prototype.type = "Property";
Property.prototype.eval = function (context) {
var property, name = this.name;
// TODO: shorten this reference
var 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() };
}
this.evaluating = true;
property = this.find(context.frames, function (frame) {
var v, vArr = frame.property(name);
if (vArr) {
for (var 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) {
var importantScope = context.importantScope[context.importantScope.length - 1];
importantScope.important = v.important;
}
v = v.value.eval(context);
return v;
}
});
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 (var i = 0, r; i < obj.length; i++) {
r = fun.call(obj, obj[i]);
if (r) { return r; }
}
return null;
};
module.exports = Property;

View File

@@ -1,19 +1,19 @@
var Node = require("./node"),
JsEvalNode = require("./js-eval-node"),
Variable = require("./variable");
Variable = require("./variable"),
Property = require("./property");
var 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.currentFileInfo = currentFileInfo;
this._index = index;
this._fileInfo = currentFileInfo;
};
Quoted.prototype = new JsEvalNode();
Quoted.prototype = new Node();
Quoted.prototype.type = "Quoted";
Quoted.prototype.genCSS = function (context, output) {
if (!this.escaped) {
output.add(this.quote, this.currentFileInfo, this.index);
output.add(this.quote, this.fileInfo(), this.getIndex());
}
output.add(this.value);
if (!this.escaped) {
@@ -21,15 +21,16 @@ Quoted.prototype.genCSS = function (context, output) {
}
};
Quoted.prototype.containsVariables = function() {
return this.value.match(/(`([^`]+)`)|@\{([\w-]+)\}/);
return this.value.match(/@\{([\w-]+)\}/);
};
Quoted.prototype.eval = function (context) {
var that = this, value = this.value;
var javascriptReplacement = function (_, exp) {
return String(that.evaluateJavaScript(exp, context));
var variableReplacement = function (_, name) {
var v = new Variable('@' + name, that.getIndex(), that.fileInfo()).eval(context, true);
return (v instanceof Quoted) ? v.value : v.toCSS();
};
var interpolationReplacement = function (_, name) {
var v = new Variable('@' + name, that.index, that.currentFileInfo).eval(context, true);
var propertyReplacement = function (_, name) {
var v = new Property('$' + name, that.getIndex(), that.fileInfo()).eval(context, true);
return (v instanceof Quoted) ? v.value : v.toCSS();
};
function iterativeReplace(value, regexp, replacementFnc) {
@@ -40,9 +41,9 @@ Quoted.prototype.eval = function (context) {
} while (value !== evaluatedValue);
return evaluatedValue;
}
value = iterativeReplace(value, /`([^`]+)`/g, javascriptReplacement);
value = iterativeReplace(value, /@\{([\w-]+)\}/g, interpolationReplacement);
return new Quoted(this.quote + value + this.quote, value, this.escaped, this.index, this.currentFileInfo);
value = iterativeReplace(value, /@\{([\w-]+)\}/g, variableReplacement);
value = iterativeReplace(value, /\$\{([\w-]+)\}/g, propertyReplacement);
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

View File

@@ -1,25 +1,35 @@
var Node = require("./node"),
Rule = require("./rule"),
Declaration = require("./declaration"),
Keyword = require("./keyword"),
Comment = require("./comment"),
Paren = require("./paren"),
Selector = require("./selector"),
Element = require("./element"),
Paren = require("./paren"),
Anonymous = require("./anonymous"),
contexts = require("../contexts"),
globalFunctionRegistry = require("../functions/function-registry"),
defaultFunc = require("../functions/default"),
getDebugInfo = require("./debug-info");
getDebugInfo = require("./debug-info"),
utils = require("../utils");
var Ruleset = function (selectors, rules, strictImports, visibilityInfo) {
this.selectors = selectors;
this.rules = rules;
this._lookups = {};
this._variables = null;
this._properties = null;
this.strictImports = strictImports;
this.copyVisibilityInfo(visibilityInfo);
this.allowRoot = true;
this.setParent(this.selectors, this);
this.setParent(this.rules, this);
};
Ruleset.prototype = new Node();
Ruleset.prototype.type = "Ruleset";
Ruleset.prototype.isRuleset = true;
Ruleset.prototype.isRulesetLike = true;
Ruleset.prototype.isRulesetLike = function() { return true; };
Ruleset.prototype.accept = function (visitor) {
if (this.paths) {
this.paths = visitor.visitArray(this.paths, true);
@@ -35,14 +45,14 @@ Ruleset.prototype.eval = function (context) {
selCnt, selector, i, hasOnePassingSelector = false;
if (thisSelectors && (selCnt = thisSelectors.length)) {
selectors = [];
selectors = new Array(selCnt);
defaultFunc.error({
type: "Syntax",
message: "it is currently only allowed in parametric mixin guards,"
});
for (i = 0; i < selCnt; i++) {
selector = thisSelectors[i].eval(context);
selectors.push(selector);
selectors[i] = selector;
if (selector.evaldCondition) {
hasOnePassingSelector = true;
}
@@ -52,7 +62,7 @@ Ruleset.prototype.eval = function (context) {
hasOnePassingSelector = true;
}
var rules = this.rules ? this.rules.slice(0) : null,
var rules = this.rules ? utils.copyArray(this.rules) : null,
ruleset = new Ruleset(selectors, rules, this.strictImports, this.visibilityInfo()),
rule, subRule;
@@ -100,21 +110,21 @@ Ruleset.prototype.eval = function (context) {
// Store the frames around mixin definitions,
// so they can be evaluated like closures when the time comes.
var rsRules = ruleset.rules, rsRuleCnt = rsRules ? rsRules.length : 0;
for (i = 0; i < rsRuleCnt; i++) {
if (rsRules[i].evalFirst) {
rsRules[i] = rsRules[i].eval(context);
var rsRules = ruleset.rules;
for (i = 0; (rule = rsRules[i]); i++) {
if (rule.evalFirst) {
rsRules[i] = rule.eval(context);
}
}
var mediaBlockCount = (context.mediaBlocks && context.mediaBlocks.length) || 0;
// Evaluate mixin calls.
for (i = 0; i < rsRuleCnt; i++) {
if (rsRules[i].type === "MixinCall") {
/*jshint loopfunc:true */
rules = rsRules[i].eval(context).filter(function(r) {
if ((r instanceof Rule) && r.variable) {
for (i = 0; (rule = rsRules[i]); i++) {
if (rule.type === "MixinCall") {
/* jshint loopfunc:true */
rules = rule.eval(context).filter(function(r) {
if ((r instanceof Declaration) && r.variable) {
// do not pollute the scope if the variable is
// already there. consider returning false here
// but we need a way to "return" variable from mixins
@@ -123,47 +133,44 @@ Ruleset.prototype.eval = function (context) {
return true;
});
rsRules.splice.apply(rsRules, [i, 1].concat(rules));
rsRuleCnt += rules.length - 1;
i += rules.length - 1;
ruleset.resetCache();
} else if (rsRules[i].type === "RulesetCall") {
/*jshint loopfunc:true */
rules = rsRules[i].eval(context).rules.filter(function(r) {
if ((r instanceof Rule) && r.variable) {
} else if (rule.type === "VariableCall") {
/* jshint loopfunc:true */
rules = rule.eval(context).rules.filter(function(r) {
if ((r instanceof Declaration) && r.variable) {
// do not pollute the scope at all
return false;
}
return true;
});
rsRules.splice.apply(rsRules, [i, 1].concat(rules));
rsRuleCnt += rules.length - 1;
i += rules.length - 1;
ruleset.resetCache();
}
}
// Evaluate everything else
for (i = 0; i < rsRules.length; i++) {
rule = rsRules[i];
for (i = 0; (rule = rsRules[i]); i++) {
if (!rule.evalFirst) {
rsRules[i] = rule = rule.eval ? rule.eval(context) : rule;
}
}
// Evaluate everything else
for (i = 0; i < rsRules.length; i++) {
rule = rsRules[i];
for (i = 0; (rule = rsRules[i]); i++) {
// for rulesets, check if it is a css guard and can be removed
if (rule instanceof Ruleset && rule.selectors && rule.selectors.length === 1) {
// check if it can be folded in (e.g. & where)
if (rule.selectors[0].isJustParentSelector()) {
rsRules.splice(i--, 1);
for (var j = 0; j < rule.rules.length; j++) {
subRule = rule.rules[j];
subRule.copyVisibilityInfo(rule.visibilityInfo());
if (!(subRule instanceof Rule) || !subRule.variable) {
rsRules.splice(++i, 0, subRule);
for (var j = 0; (subRule = rule.rules[j]); j++) {
if (subRule instanceof Node) {
subRule.copyVisibilityInfo(rule.visibilityInfo());
if (!(subRule instanceof Declaration) || !subRule.variable) {
rsRules.splice(++i, 0, subRule);
}
}
}
}
@@ -191,7 +198,7 @@ Ruleset.prototype.evalImports = function(context) {
importRules = rules[i].eval(context);
if (importRules && (importRules.length || importRules.length === 0)) {
rules.splice.apply(rules, [i, 1].concat(importRules));
i+= importRules.length - 1;
i += importRules.length - 1;
} else {
rules.splice(i, 1, importRules);
}
@@ -230,12 +237,13 @@ Ruleset.prototype.matchCondition = function (args, context) {
Ruleset.prototype.resetCache = function () {
this._rulesets = null;
this._variables = null;
this._properties = null;
this._lookups = {};
};
Ruleset.prototype.variables = function () {
if (!this._variables) {
this._variables = !this.rules ? {} : this.rules.reduce(function (hash, r) {
if (r instanceof Rule && r.variable === true) {
if (r instanceof Declaration && r.variable === true) {
hash[r.name] = r;
}
// when evaluating variables in an import statement, imports have not been eval'd
@@ -254,17 +262,81 @@ Ruleset.prototype.variables = function () {
}
return this._variables;
};
Ruleset.prototype.properties = function () {
if (!this._properties) {
this._properties = !this.rules ? {} : this.rules.reduce(function (hash, r) {
if (r instanceof Declaration && r.variable !== true) {
var name = (r.name.length === 1) && (r.name[0] instanceof Keyword) ?
r.name[0].value : r.name;
// Properties don't overwrite as they can merge
if (!hash['$' + name]) {
hash['$' + name] = [ r ];
}
else {
hash['$' + name].push(r);
}
}
return hash;
}, {});
}
return this._properties;
};
Ruleset.prototype.variable = function (name) {
return this.variables()[name];
var decl = this.variables()[name];
if (decl) {
return this.parseValue(decl);
}
};
Ruleset.prototype.property = function (name) {
var decl = this.properties()[name];
if (decl) {
return this.parseValue(decl);
}
};
Ruleset.prototype.parseValue = function(toParse) {
var self = this;
function transformDeclaration(decl) {
if (decl.value instanceof Anonymous && !decl.parsed) {
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;
}
});
return decl;
}
else {
return decl;
}
}
if (!Array.isArray(toParse)) {
return transformDeclaration.call(self, toParse);
}
else {
var nodes = [];
toParse.forEach(function(n) {
nodes.push(transformDeclaration.call(self, n));
});
return nodes;
}
};
Ruleset.prototype.rulesets = function () {
if (!this.rules) { return []; }
var filtRules = [], rules = this.rules, cnt = rules.length,
var filtRules = [], rules = this.rules,
i, rule;
for (i = 0; i < cnt; i++) {
rule = rules[i];
for (i = 0; (rule = rules[i]); i++) {
if (rule.isRuleset) {
filtRules.push(rule);
}
@@ -279,6 +351,7 @@ Ruleset.prototype.prependRule = function (rule) {
} else {
this.rules = [ rule ];
}
this.setParent(rule, this);
};
Ruleset.prototype.find = function (selector, self, filter) {
self = self || this;
@@ -329,25 +402,10 @@ Ruleset.prototype.genCSS = function (context, output) {
tabSetStr = context.compress ? '' : Array(context.tabLevel).join(" "),
sep;
function isRulesetLikeNode(rule) {
// if it has nested rules, then it should be treated like a ruleset
// medias and comments do not have nested rules, but should be treated like rulesets anyway
// some directives and anonymous nodes are ruleset like, others are not
if (typeof rule.isRulesetLike === "boolean") {
return rule.isRulesetLike;
} else if (typeof rule.isRulesetLike === "function") {
return rule.isRulesetLike();
}
//anything else is assumed to be a rule
return false;
}
var charsetNodeIndex = 0;
var importNodeIndex = 0;
for (i = 0; i < this.rules.length; i++) {
rule = this.rules[i];
if (rule.type === "Comment") {
for (i = 0; (rule = this.rules[i]); i++) {
if (rule instanceof Comment) {
if (importNodeIndex === i) {
importNodeIndex++;
}
@@ -398,15 +456,14 @@ Ruleset.prototype.genCSS = function (context, output) {
}
// Compile rules and rulesets
for (i = 0; i < ruleNodes.length; i++) {
rule = ruleNodes[i];
for (i = 0; (rule = ruleNodes[i]); i++) {
if (i + 1 === ruleNodes.length) {
context.lastRule = true;
}
var currentLastRule = context.lastRule;
if (isRulesetLikeNode(rule)) {
if (rule.isRulesetLike(rule)) {
context.lastRule = false;
}
@@ -418,7 +475,7 @@ Ruleset.prototype.genCSS = function (context, output) {
context.lastRule = currentLastRule;
if (!context.lastRule) {
if (!context.lastRule && rule.isVisible()) {
output.add(context.compress ? '' : ('\n' + tabRuleStr));
} else {
context.lastRule = false;
@@ -448,9 +505,9 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
if (elementsToPak.length === 0) {
replacementParen = new Paren(elementsToPak[0]);
} else {
var insideParent = [];
var insideParent = new Array(elementsToPak.length);
for (j = 0; j < elementsToPak.length; j++) {
insideParent.push(new Element(null, elementsToPak[j], originalElement.index, originalElement.currentFileInfo));
insideParent[j] = new Element(null, elementsToPak[j], originalElement._index, originalElement._fileInfo);
}
replacementParen = new Paren(new Selector(insideParent));
}
@@ -459,7 +516,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
function createSelector(containedElement, originalElement) {
var element, selector;
element = new Element(null, containedElement, originalElement.index, originalElement.currentFileInfo);
element = new Element(null, containedElement, originalElement._index, originalElement._fileInfo);
selector = new Selector([element]);
return selector;
}
@@ -472,12 +529,12 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
// our new selector path
newSelectorPath = [];
//construct the joined selector - if & is the first thing this will be empty,
// construct the joined selector - if & is the first thing this will be empty,
// if not newJoinedSelector will be the last set of elements in the selector
if (beginningPath.length > 0) {
newSelectorPath = beginningPath.slice(0);
newSelectorPath = utils.copyArray(beginningPath);
lastSelector = newSelectorPath.pop();
newJoinedSelector = originalSelector.createDerived(lastSelector.elements.slice(0));
newJoinedSelector = originalSelector.createDerived(utils.copyArray(lastSelector.elements));
}
else {
newJoinedSelector = originalSelector.createDerived([]);
@@ -493,7 +550,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
combinator = parentEl.combinator;
}
// join the elements so far with the first part of the parent
newJoinedSelector.elements.push(new Element(combinator, parentEl.value, replacedElement.index, replacedElement.currentFileInfo));
newJoinedSelector.elements.push(new Element(combinator, parentEl.value, replacedElement._index, replacedElement._fileInfo));
newJoinedSelector.elements = newJoinedSelector.elements.concat(addPath[0].elements.slice(1));
}
@@ -502,7 +559,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
newSelectorPath.push(newJoinedSelector);
}
//put together the parent selectors after the join (e.g. the rest of the parent)
// put together the parent selectors after the join (e.g. the rest of the parent)
if (addPath.length > 1) {
var restOfPath = addPath.slice(1);
restOfPath = restOfPath.map(function (selector) {
@@ -536,9 +593,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
return;
}
for (i = 0; i < selectors.length; i++) {
sel = selectors[i];
for (i = 0; (sel = selectors[i]); i++) {
// if the previous thing in sel is a parent this needs to join on to it
if (sel.length > 0) {
sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements));
@@ -566,12 +621,12 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
var i, j, k, currentElements, newSelectors, selectorsMultiplied, sel, el, hadParentSelector = false, length, lastSelector;
function findNestedSelector(element) {
var maybeSelector;
if (element.value.type !== 'Paren') {
if (!(element.value instanceof Paren)) {
return null;
}
maybeSelector = element.value.value;
if (maybeSelector.type !== 'Selector') {
if (!(maybeSelector instanceof Selector)) {
return null;
}
@@ -587,8 +642,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
[]
];
for (i = 0; i < inSelector.elements.length; i++) {
el = inSelector.elements[i];
for (i = 0; (el = inSelector.elements[i]); i++) {
// non parent reference elements just get added
if (el.value !== "&") {
var nestedSelector = findNestedSelector(el);
@@ -600,7 +654,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
var nestedPaths = [], replaced, replacedNewSelectors = [];
replaced = replaceParentSelector(nestedPaths, context, nestedSelector);
hadParentSelector = hadParentSelector || replaced;
//the nestedPaths array should have only one member - replaceParentSelector does not multiply selectors
// the nestedPaths array should have only one member - replaceParentSelector does not multiply selectors
for (k = 0; k < nestedPaths.length; k++) {
var replacementSelector = createSelector(createParenthesis(nestedPaths[k], el), el);
addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector, replacedNewSelectors);
@@ -630,7 +684,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
// the combinator used on el should now be applied to the next element instead so that
// it is not lost
if (sel.length > 0) {
sel[0].elements.push(new Element(el.combinator, '', el.index, el.currentFileInfo));
sel[0].elements.push(new Element(el.combinator, '', el._index, el._fileInfo));
}
selectorsMultiplied.push(sel);
}
@@ -662,7 +716,6 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
paths.push(newSelectors[i]);
lastSelector = newSelectors[i][length - 1];
newSelectors[i][length - 1] = lastSelector.createDerived(lastSelector.elements, inSelector.extendList);
//newSelectors[i][length - 1].copyVisibilityInfo(inSelector.visibilityInfo());
}
}
@@ -685,12 +738,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
if (context.length > 0) {
newPaths = [];
for (i = 0; i < context.length; i++) {
//var concatenated = [];
//context[i].forEach(function(entry) {
// var newEntry = entry.createDerived(entry.elements, entry.extendList, entry.evaldCondition);
// newEntry.copyVisibilityInfo(selector.visibilityInfo());
// concatenated.push(newEntry);
//}, this);
var concatenated = context[i].map(deriveSelector.bind(this, selector.visibilityInfo()));
concatenated.push(selector);

View File

@@ -1,15 +1,17 @@
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.currentFileInfo = currentFileInfo || {};
if (!condition) {
this.evaldCondition = true;
}
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.type = "Selector";
@@ -25,16 +27,35 @@ Selector.prototype.accept = function (visitor) {
}
};
Selector.prototype.createDerived = function(elements, extendList, evaldCondition) {
var info = this.visibilityInfo();
evaldCondition = (evaldCondition != null) ? evaldCondition : this.evaldCondition;
var newSelector = new Selector(elements, extendList || this.extendList, null, this.index, this.currentFileInfo, info);
newSelector.evaldCondition = evaldCondition;
elements = this.getElements(elements);
var 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 (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() {
var el = new Element('', '&', this.index, this.currentFileInfo),
sels = [new Selector([el], null, null, this.index, this.currentFileInfo)];
var el = new Element('', '&', this._index, this._fileInfo),
sels = [new Selector([el], null, null, this._index, this._fileInfo)];
sels[0].mediaEmpty = true;
return sels;
};
@@ -43,14 +64,13 @@ Selector.prototype.match = function (other) {
len = elements.length,
olen, i;
other.CacheElements();
olen = other._elements.length;
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._elements[i]) {
if (elements[i].value !== other[i]) {
return 0;
}
}
@@ -58,9 +78,9 @@ Selector.prototype.match = function (other) {
return olen; // return number of matched elements
};
Selector.prototype.CacheElements = function() {
if (this._elements) {
return;
Selector.prototype.mixinElements = function() {
if (this.mixinElements_) {
return this.mixinElements_;
}
var elements = this.elements.map( function(v) {
@@ -75,7 +95,7 @@ Selector.prototype.CacheElements = function() {
elements = [];
}
this._elements = elements;
return (this.mixinElements_ = elements);
};
Selector.prototype.isJustParentSelector = function() {
return !this.mediaEmpty &&
@@ -95,14 +115,11 @@ Selector.prototype.eval = function (context) {
Selector.prototype.genCSS = function (context, output) {
var i, element;
if ((!context || !context.firstSelector) && this.elements[0].combinator.value === "") {
output.add(' ', this.currentFileInfo, this.index);
output.add(' ', this.fileInfo(), this.getIndex());
}
if (!this._css) {
//TODO caching? speed comparison?
for (i = 0; i < this.elements.length; i++) {
element = this.elements[i];
element.genCSS(context, output);
}
for (i = 0; i < this.elements.length; i++) {
element = this.elements[i];
element.genCSS(context, output);
}
};
Selector.prototype.getIsOutput = function() {

View File

@@ -1,9 +1,10 @@
var Node = require("./node"),
unitConversions = require("../data/unit-conversions");
unitConversions = require("../data/unit-conversions"),
utils = require("../utils");
var Unit = function (numerator, denominator, backupUnit) {
this.numerator = numerator ? numerator.slice(0).sort() : [];
this.denominator = denominator ? denominator.slice(0).sort() : [];
this.numerator = numerator ? utils.copyArray(numerator).sort() : [];
this.denominator = denominator ? utils.copyArray(denominator).sort() : [];
if (backupUnit) {
this.backupUnit = backupUnit;
} else if (numerator && numerator.length) {
@@ -14,7 +15,7 @@ var Unit = function (numerator, denominator, backupUnit) {
Unit.prototype = new Node();
Unit.prototype.type = "Unit";
Unit.prototype.clone = function () {
return new Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit);
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.
@@ -64,7 +65,7 @@ Unit.prototype.usedUnits = function() {
var group, result = {}, mapUnit, groupName;
mapUnit = function (atomicUnit) {
/*jshint loopfunc:true */
/* jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit) && !result[groupName]) {
result[groupName] = atomicUnit;
}

View File

@@ -2,8 +2,8 @@ var Node = require("./node");
var URL = function (val, index, currentFileInfo, isEvald) {
this.value = val;
this.currentFileInfo = currentFileInfo;
this.index = index;
this._index = index;
this._fileInfo = currentFileInfo;
this.isEvald = isEvald;
};
URL.prototype = new Node();
@@ -22,7 +22,7 @@ URL.prototype.eval = function (context) {
if (!this.isEvald) {
// Add the base path if the URL is relative
rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
rootpath = this.fileInfo() && this.fileInfo().rootpath;
if (rootpath &&
typeof val.value === "string" &&
context.isPathRelative(val.value)) {
@@ -49,6 +49,6 @@ URL.prototype.eval = function (context) {
}
}
return new URL(val, this.index, this.currentFileInfo, true);
return new URL(val, this.getIndex(), this.fileInfo(), true);
};
module.exports = URL;

View File

@@ -1,10 +1,15 @@
var Node = require("./node");
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";

View File

@@ -1,14 +1,14 @@
var Node = require("./node"),
Variable = require("./variable");
var RulesetCall = function (variable) {
var VariableCall = function (variable) {
this.variable = variable;
this.allowRoot = true;
};
RulesetCall.prototype = new Node();
RulesetCall.prototype.type = "RulesetCall";
RulesetCall.prototype.eval = function (context) {
VariableCall.prototype = new Node();
VariableCall.prototype.type = "VariableCall";
VariableCall.prototype.eval = function (context) {
var detachedRuleset = new Variable(this.variable).eval(context);
return detachedRuleset.callEval(context);
};
module.exports = RulesetCall;
module.exports = VariableCall;

View File

@@ -2,8 +2,8 @@ var Node = require("./node");
var Variable = function (name, index, currentFileInfo) {
this.name = name;
this.index = index;
this.currentFileInfo = currentFileInfo || {};
this._index = index;
this._fileInfo = currentFileInfo;
};
Variable.prototype = new Node();
Variable.prototype.type = "Variable";
@@ -11,14 +11,14 @@ Variable.prototype.eval = function (context) {
var variable, name = this.name;
if (name.indexOf('@@') === 0) {
name = '@' + new Variable(name.slice(1), this.index, this.currentFileInfo).eval(context).value;
name = '@' + new Variable(name.slice(1), this.getIndex(), this.fileInfo()).eval(context).value;
}
if (this.evaluating) {
throw { type: 'Name',
message: "Recursive variable definition for " + name,
filename: this.currentFileInfo.filename,
index: this.index };
message: "Recursive variable definition for " + name,
filename: this.fileInfo().filename,
index: this.getIndex() };
}
this.evaluating = true;
@@ -38,9 +38,9 @@ Variable.prototype.eval = function (context) {
return variable;
} else {
throw { type: 'Name',
message: "variable " + name + " is undefined",
filename: this.currentFileInfo.filename,
index: this.index };
message: "variable " + name + " is undefined",
filename: this.fileInfo().filename,
index: this.getIndex() };
}
};
Variable.prototype.find = function (obj, fun) {

View File

@@ -1,3 +1,4 @@
/* jshint proto: true */
module.exports = {
getLocation: function(index, inputStream) {
var n = index + 1,
@@ -16,5 +17,53 @@ module.exports = {
line: line,
column: column
};
},
copyArray: function(arr) {
var i, length = arr.length,
copy = new Array(length);
for (i = 0; i < length; i++) {
copy[i] = arr[i];
}
return copy;
},
clone: function (obj) {
var cloned = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
cloned[prop] = obj[prop];
}
}
return cloned;
},
defaults: function(obj1, obj2) {
if (!obj2._defaults || obj2._defaults !== obj1) {
for (var prop in obj1) {
if (obj1.hasOwnProperty(prop)) {
if (!obj2.hasOwnProperty(prop)) {
obj2[prop] = obj1[prop];
}
else if (Array.isArray(obj1[prop])
&& Array.isArray(obj2[prop])) {
obj1[prop].forEach(function(p) {
if (obj2[prop].indexOf(p) === -1) {
obj2[prop].push(p);
}
});
}
}
}
}
obj2._defaults = obj1;
return obj2;
},
merge: function(obj1, obj2) {
for (var prop in obj2) {
if (obj2.hasOwnProperty(prop)) {
obj1[prop] = obj2[prop];
}
}
return obj1;
}
};

View File

@@ -1,8 +1,9 @@
var tree = require("../tree"),
Visitor = require("./visitor"),
logger = require("../logger");
logger = require("../logger"),
utils = require("../utils");
/*jshint loopfunc:true */
/* jshint loopfunc:true */
var ExtendFinderVisitor = function() {
this._visitor = new Visitor(this);
@@ -16,7 +17,7 @@ ExtendFinderVisitor.prototype = {
root.allExtends = this.allExtendsStack[0];
return root;
},
visitRule: function (ruleNode, visitArgs) {
visitDeclaration: function (declNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
@@ -46,7 +47,7 @@ ExtendFinderVisitor.prototype = {
selector = selectorPath[selectorPath.length - 1],
selExtendList = selector.extendList;
extendList = selExtendList ? selExtendList.slice(0).concat(allSelectorsExtendList)
extendList = selExtendList ? utils.copyArray(selExtendList).concat(allSelectorsExtendList)
: allSelectorsExtendList;
if (extendList) {
@@ -79,11 +80,11 @@ ExtendFinderVisitor.prototype = {
visitMediaOut: function (mediaNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
},
visitDirective: function (directiveNode, visitArgs) {
directiveNode.allExtends = [];
this.allExtendsStack.push(directiveNode.allExtends);
visitAtRule: function (atRuleNode, visitArgs) {
atRuleNode.allExtends = [];
this.allExtendsStack.push(atRuleNode.allExtends);
},
visitDirectiveOut: function (directiveNode) {
visitAtRuleOut: function (atRuleNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
}
};
@@ -109,17 +110,17 @@ ProcessExtendsVisitor.prototype = {
extendList.filter(function(extend) {
return !extend.hasFoundMatches && extend.parent_ids.length == 1;
}).forEach(function(extend) {
var selector = "_unknown_";
try {
selector = extend.selector.toCSS({});
}
catch(_) {}
var selector = "_unknown_";
try {
selector = extend.selector.toCSS({});
}
catch (_) {}
if (!indices[extend.index + ' ' + selector]) {
indices[extend.index + ' ' + selector] = true;
logger.warn("extend '" + selector + "' has no matches");
}
});
if (!indices[extend.index + ' ' + selector]) {
indices[extend.index + ' ' + selector] = true;
logger.warn("extend '" + selector + "' has no matches");
}
});
},
doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
//
@@ -136,7 +137,7 @@ ProcessExtendsVisitor.prototype = {
iterationCount = iterationCount || 0;
//loop through comparing every extend with every target extend.
// loop through comparing every extend with every target extend.
// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
// and the second is the target.
@@ -166,7 +167,7 @@ ProcessExtendsVisitor.prototype = {
newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector, extend.isVisible());
// but now we create a new extend from it
newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0, targetExtend.currentFileInfo, info);
newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0, targetExtend.fileInfo(), info);
newExtend.selfSelectors = newSelector;
// add the extend onto the list of extends for that selector
@@ -202,7 +203,7 @@ ProcessExtendsVisitor.prototype = {
selectorOne = extendsToAdd[0].selfSelectors[0].toCSS();
selectorTwo = extendsToAdd[0].selector.toCSS();
}
catch(e) {}
catch (e) {}
throw { message: "extend circular reference detected. One of the circular extends is currently:" +
selectorOne + ":extend(" + selectorTwo + ")"};
}
@@ -214,7 +215,7 @@ ProcessExtendsVisitor.prototype = {
return extendsToAdd;
}
},
visitRule: function (ruleNode, visitArgs) {
visitDeclaration: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
@@ -367,7 +368,7 @@ ProcessExtendsVisitor.prototype = {
},
extendSelector:function (matches, selectorPath, replacementSelector, isVisible) {
//for a set of matches, replace each match with the replacement selector
// for a set of matches, replace each match with the replacement selector
var currentSelectorPathIndex = 0,
currentSelectorPathElementIndex = 0,
@@ -384,8 +385,8 @@ ProcessExtendsVisitor.prototype = {
firstElement = new tree.Element(
match.initialCombinator,
replacementSelector.elements[0].value,
replacementSelector.elements[0].index,
replacementSelector.elements[0].currentFileInfo
replacementSelector.elements[0].getIndex(),
replacementSelector.elements[0].fileInfo()
);
if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
@@ -446,12 +447,12 @@ ProcessExtendsVisitor.prototype = {
var lastIndex = this.allExtendsStack.length - 1;
this.allExtendsStack.length = lastIndex;
},
visitDirective: function (directiveNode, visitArgs) {
var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length - 1]);
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
visitAtRule: function (atRuleNode, visitArgs) {
var newAllExtends = atRuleNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length - 1]);
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, atRuleNode.allExtends));
this.allExtendsStack.push(newAllExtends);
},
visitDirectiveOut: function (directiveNode) {
visitAtRuleOut: function (atRuleNode) {
var lastIndex = this.allExtendsStack.length - 1;
this.allExtendsStack.length = lastIndex;
}

View File

@@ -1,6 +1,7 @@
var contexts = require("../contexts"),
Visitor = require("./visitor"),
ImportSequencer = require("./import-sequencer");
ImportSequencer = require("./import-sequencer"),
utils = require("../utils");
var ImportVisitor = function(importer, finish) {
@@ -21,7 +22,7 @@ ImportVisitor.prototype = {
// process the contents
this._visitor.visit(root);
}
catch(e) {
catch (e) {
this.error = e;
}
@@ -39,7 +40,7 @@ ImportVisitor.prototype = {
if (!importNode.css || inlineCSS) {
var context = new contexts.Eval(this.context, this.context.frames.slice(0));
var context = new contexts.Eval(this.context, utils.copyArray(this.context.frames));
var importParent = context.frames[0];
this.importCount++;
@@ -57,8 +58,8 @@ ImportVisitor.prototype = {
try {
evaldImportNode = importNode.evalForImport(context);
} catch(e) {
if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
} catch (e) {
if (!e.filename) { e.index = importNode.getIndex(); e.filename = importNode.fileInfo().filename; }
// attempt to eval properly and treat as css
importNode.css = true;
// if that fails, this error will be thrown
@@ -84,7 +85,7 @@ ImportVisitor.prototype = {
var onImported = this.onImported.bind(this, evaldImportNode, context),
sequencedOnImported = this._sequencer.addImport(onImported);
this._importer.push(evaldImportNode.getPath(), tryAppendLessExtension, evaldImportNode.currentFileInfo,
this._importer.push(evaldImportNode.getPath(), tryAppendLessExtension, evaldImportNode.fileInfo(),
evaldImportNode.options, sequencedOnImported);
} else {
this.importCount--;
@@ -96,14 +97,14 @@ ImportVisitor.prototype = {
onImported: function (importNode, context, e, root, importedAtRoot, fullPath) {
if (e) {
if (!e.filename) {
e.index = importNode.index; e.filename = importNode.currentFileInfo.filename;
e.index = importNode.getIndex(); e.filename = importNode.fileInfo().filename;
}
this.error = e;
}
var importVisitor = this,
inlineCSS = importNode.options.inline,
isPlugin = importNode.options.plugin,
isPlugin = importNode.options.isPlugin,
isOptional = importNode.options.optional,
duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector;
@@ -149,22 +150,22 @@ ImportVisitor.prototype = {
importVisitor._sequencer.tryRun();
}
},
visitRule: function (ruleNode, visitArgs) {
if (ruleNode.value.type === "DetachedRuleset") {
this.context.frames.unshift(ruleNode);
visitDeclaration: function (declNode, visitArgs) {
if (declNode.value.type === "DetachedRuleset") {
this.context.frames.unshift(declNode);
} else {
visitArgs.visitDeeper = false;
}
},
visitRuleOut : function(ruleNode) {
if (ruleNode.value.type === "DetachedRuleset") {
visitDeclarationOut: function(declNode) {
if (declNode.value.type === "DetachedRuleset") {
this.context.frames.shift();
}
},
visitDirective: function (directiveNode, visitArgs) {
this.context.frames.unshift(directiveNode);
visitAtRule: function (atRuleNode, visitArgs) {
this.context.frames.unshift(atRuleNode);
},
visitDirectiveOut: function (directiveNode) {
visitAtRuleOut: function (atRuleNode) {
this.context.frames.shift();
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {

View File

@@ -9,7 +9,7 @@ JoinSelectorVisitor.prototype = {
run: function (root) {
return this._visitor.visit(root);
},
visitRule: function (ruleNode, visitArgs) {
visitDeclaration: function (declNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
@@ -22,7 +22,7 @@ JoinSelectorVisitor.prototype = {
this.contexts.push(paths);
if (! rulesetNode.root) {
if (!rulesetNode.root) {
selectors = rulesetNode.selectors;
if (selectors) {
selectors = selectors.filter(function(selector) { return selector.getIsOutput(); });
@@ -40,10 +40,10 @@ JoinSelectorVisitor.prototype = {
var context = this.contexts[this.contexts.length - 1];
mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia);
},
visitDirective: function (directiveNode, visitArgs) {
visitAtRule: function (atRuleNode, visitArgs) {
var context = this.contexts[this.contexts.length - 1];
if (directiveNode.rules && directiveNode.rules.length) {
directiveNode.rules[0].root = (directiveNode.isRooted || context.length === 0 || null);
if (atRuleNode.rules && atRuleNode.rules.length) {
atRuleNode.rules[0].root = (atRuleNode.isRooted || context.length === 0 || null);
}
}
};

View File

@@ -9,14 +9,14 @@ var CSSVisitorUtils = function(context) {
CSSVisitorUtils.prototype = {
containsSilentNonBlockedChild: function(bodyRules) {
var rule;
if (bodyRules == null) {
if (!bodyRules) {
return false;
}
for (var r = 0; r < bodyRules.length; r++) {
rule = bodyRules[r];
if (rule.isSilent && rule.isSilent(this._context) && !rule.blocksVisibility()) {
//the directive contains something that was referenced (likely by extend)
//therefore it needs to be shown in output too
// the atrule contains something that was referenced (likely by extend)
// therefore it needs to be shown in output too
return true;
}
}
@@ -24,28 +24,21 @@ CSSVisitorUtils.prototype = {
},
keepOnlyVisibleChilds: function(owner) {
if (owner == null || owner.rules == null) {
return ;
}
owner.rules = owner.rules.filter(function(thing) {
if (owner && owner.rules) {
owner.rules = owner.rules.filter(function(thing) {
return thing.isVisible();
}
);
});
}
},
isEmpty: function(owner) {
if (owner == null || owner.rules == null) {
return true;
}
return owner.rules.length === 0;
return (owner && owner.rules)
? (owner.rules.length === 0) : true;
},
hasVisibleSelector: function(rulesetNode) {
if (rulesetNode == null || rulesetNode.paths == null) {
return false;
}
return rulesetNode.paths.length > 0;
return (rulesetNode && rulesetNode.paths)
? (rulesetNode.paths.length > 0) : false;
},
resolveVisibility: function (node, originalRules) {
@@ -100,11 +93,11 @@ ToCSSVisitor.prototype = {
return this._visitor.visit(root);
},
visitRule: function (ruleNode, visitArgs) {
if (ruleNode.blocksVisibility() || ruleNode.variable) {
visitDeclaration: function (declNode, visitArgs) {
if (declNode.blocksVisibility() || declNode.variable) {
return;
}
return ruleNode;
return declNode;
},
visitMixinDefinition: function (mixinNode, visitArgs) {
@@ -138,56 +131,63 @@ ToCSSVisitor.prototype = {
return importNode;
},
visitDirective: function(directiveNode, visitArgs) {
if (directiveNode.rules && directiveNode.rules.length) {
return this.visitDirectiveWithBody(directiveNode, visitArgs);
visitAtRule: function(atRuleNode, visitArgs) {
if (atRuleNode.rules && atRuleNode.rules.length) {
return this.visitAtRuleWithBody(atRuleNode, visitArgs);
} else {
return this.visitDirectiveWithoutBody(directiveNode, visitArgs);
return this.visitAtRuleWithoutBody(atRuleNode, visitArgs);
}
},
visitDirectiveWithBody: function(directiveNode, visitArgs) {
//if there is only one nested ruleset and that one has no path, then it is
//just fake ruleset
function hasFakeRuleset(directiveNode) {
var bodyRules = directiveNode.rules;
visitAnonymous: function(anonymousNode, visitArgs) {
if (!anonymousNode.blocksVisibility()) {
anonymousNode.accept(this._visitor);
return anonymousNode;
}
},
visitAtRuleWithBody: function(atRuleNode, visitArgs) {
// if there is only one nested ruleset and that one has no path, then it is
// just fake ruleset
function hasFakeRuleset(atRuleNode) {
var bodyRules = atRuleNode.rules;
return bodyRules.length === 1 && (!bodyRules[0].paths || bodyRules[0].paths.length === 0);
}
function getBodyRules(directiveNode) {
var nodeRules = directiveNode.rules;
if (hasFakeRuleset(directiveNode)) {
function getBodyRules(atRuleNode) {
var nodeRules = atRuleNode.rules;
if (hasFakeRuleset(atRuleNode)) {
return nodeRules[0].rules;
}
return nodeRules;
}
//it is still true that it is only one ruleset in array
//this is last such moment
//process childs
var originalRules = getBodyRules(directiveNode);
directiveNode.accept(this._visitor);
// it is still true that it is only one ruleset in array
// this is last such moment
// process childs
var originalRules = getBodyRules(atRuleNode);
atRuleNode.accept(this._visitor);
visitArgs.visitDeeper = false;
if (!this.utils.isEmpty(directiveNode)) {
this._mergeRules(directiveNode.rules[0].rules);
if (!this.utils.isEmpty(atRuleNode)) {
this._mergeRules(atRuleNode.rules[0].rules);
}
return this.utils.resolveVisibility(directiveNode, originalRules);
return this.utils.resolveVisibility(atRuleNode, originalRules);
},
visitDirectiveWithoutBody: function(directiveNode, visitArgs) {
if (directiveNode.blocksVisibility()) {
visitAtRuleWithoutBody: function(atRuleNode, visitArgs) {
if (atRuleNode.blocksVisibility()) {
return;
}
if (directiveNode.name === "@charset") {
if (atRuleNode.name === "@charset") {
// Only output the debug info together with subsequent @charset definitions
// a comment (or @media statement) before the actual @charset directive would
// a comment (or @media statement) before the actual @charset atrule would
// be considered illegal css as it has to be on the first line
if (this.charset) {
if (directiveNode.debugInfo) {
var comment = new tree.Comment("/* " + directiveNode.toCSS(this._context).replace(/\n/g, "") + " */\n");
comment.debugInfo = directiveNode.debugInfo;
if (atRuleNode.debugInfo) {
var comment = new tree.Comment("/* " + atRuleNode.toCSS(this._context).replace(/\n/g, "") + " */\n");
comment.debugInfo = atRuleNode.debugInfo;
return this._visitor.visit(comment);
}
return;
@@ -195,7 +195,7 @@ ToCSSVisitor.prototype = {
this.charset = true;
}
return directiveNode;
return atRuleNode;
},
checkValidNodes: function(rules, isRoot) {
@@ -205,29 +205,29 @@ ToCSSVisitor.prototype = {
for (var i = 0; i < rules.length; i++) {
var ruleNode = rules[i];
if (isRoot && ruleNode instanceof tree.Rule && !ruleNode.variable) {
if (isRoot && ruleNode instanceof tree.Declaration && !ruleNode.variable) {
throw { message: "Properties must be inside selector blocks. They cannot be in the root",
index: ruleNode.index, filename: ruleNode.currentFileInfo && ruleNode.currentFileInfo.filename};
index: ruleNode.getIndex(), filename: ruleNode.fileInfo() && ruleNode.fileInfo().filename};
}
if (ruleNode instanceof tree.Call) {
throw { message: "Function '" + ruleNode.name + "' is undefined",
index: ruleNode.index, filename: ruleNode.currentFileInfo && ruleNode.currentFileInfo.filename};
index: ruleNode.getIndex(), filename: ruleNode.fileInfo() && ruleNode.fileInfo().filename};
}
if (ruleNode.type && !ruleNode.allowRoot) {
throw { message: ruleNode.type + " node returned by a function is not valid here",
index: ruleNode.index, filename: ruleNode.currentFileInfo && ruleNode.currentFileInfo.filename};
index: ruleNode.getIndex(), filename: ruleNode.fileInfo() && ruleNode.fileInfo().filename};
}
}
},
visitRuleset: function (rulesetNode, visitArgs) {
//at this point rulesets are nested into each other
// at this point rulesets are nested into each other
var rule, rulesets = [];
this.checkValidNodes(rulesetNode.rules, rulesetNode.firstRoot);
if (! rulesetNode.root) {
//remove invisible paths
if (!rulesetNode.root) {
// remove invisible paths
this._compileRulesetPaths(rulesetNode);
// remove rulesets from this ruleset body and compile them separately
@@ -253,7 +253,7 @@ ToCSSVisitor.prototype = {
}
visitArgs.visitDeeper = false;
} else { //if (! rulesetNode.root) {
} else { // if (! rulesetNode.root) {
rulesetNode.accept(this._visitor);
visitArgs.visitDeeper = false;
}
@@ -263,7 +263,7 @@ ToCSSVisitor.prototype = {
this._removeDuplicateRules(rulesetNode.rules);
}
//now decide whether we keep the ruleset
// now decide whether we keep the ruleset
if (this.utils.isVisibleRuleset(rulesetNode)) {
rulesetNode.ensureVisibility();
rulesets.splice(0, 0, rulesetNode);
@@ -302,12 +302,12 @@ ToCSSVisitor.prototype = {
for (i = rules.length - 1; i >= 0 ; i--) {
rule = rules[i];
if (rule instanceof tree.Rule) {
if (rule instanceof tree.Declaration) {
if (!ruleCache[rule.name]) {
ruleCache[rule.name] = rule;
} else {
ruleList = ruleCache[rule.name];
if (ruleList instanceof tree.Rule) {
if (ruleList instanceof tree.Declaration) {
ruleList = ruleCache[rule.name] = [ruleCache[rule.name].toCSS(this._context)];
}
var ruleCSS = rule.toCSS(this._context);
@@ -321,72 +321,39 @@ ToCSSVisitor.prototype = {
}
},
_mergeRules: function (rules) {
if (!rules) { return; }
var groups = {},
parts,
rule,
key;
_mergeRules: function(rules) {
if (!rules) {
return;
}
var groups = {},
groupsArr = [];
for (var i = 0; i < rules.length; i++) {
rule = rules[i];
if ((rule instanceof tree.Rule) && rule.merge) {
key = [rule.name,
rule.important ? "!" : ""].join(",");
if (!groups[key]) {
groups[key] = [];
} else {
rules.splice(i--, 1);
}
var rule = rules[i];
if (rule.merge) {
var key = rule.name;
groups[key] ? rules.splice(i--, 1) :
groupsArr.push(groups[key] = []);
groups[key].push(rule);
}
}
Object.keys(groups).map(function (k) {
function toExpression(values) {
return new (tree.Expression)(values.map(function (p) {
return p.value;
}));
}
function toValue(values) {
return new (tree.Value)(values.map(function (p) {
return p;
}));
}
parts = groups[k];
if (parts.length > 1) {
rule = parts[0];
var spacedGroups = [];
var lastSpacedGroup = [];
parts.map(function (p) {
if (p.merge === "+") {
if (lastSpacedGroup.length > 0) {
spacedGroups.push(toExpression(lastSpacedGroup));
}
lastSpacedGroup = [];
groupsArr.forEach(function(group) {
if (group.length > 0) {
var result = group[0],
space = [],
comma = [new tree.Expression(space)];
group.forEach(function(rule) {
if ((rule.merge === '+') && (space.length > 0)) {
comma.push(new tree.Expression(space = []));
}
lastSpacedGroup.push(p);
space.push(rule.value);
result.important = result.important || rule.important;
});
spacedGroups.push(toExpression(lastSpacedGroup));
rule.value = toValue(spacedGroups);
result.value = new tree.Value(comma);
}
});
},
visitAnonymous: function(anonymousNode, visitArgs) {
if (anonymousNode.blocksVisibility()) {
return ;
}
anonymousNode.accept(this._visitor);
return anonymousNode;
}
};

View File

@@ -10,21 +10,21 @@ 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) {
/* eslint guard-for-in: 0 */
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;

5865
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "less",
"version": "2.7.3",
"version": "3.0.0",
"description": "Leaner CSS",
"homepage": "http://lesscss.org",
"author": {
@@ -34,7 +34,7 @@
},
"browser": "./dist/less.js",
"engines": {
"node": ">=0.12"
"node": ">=4"
},
"scripts": {
"test": "grunt test"
@@ -43,27 +43,32 @@
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"mime": "^1.2.11",
"mime": "^1.4.1",
"mkdirp": "^0.5.0",
"promise": "^7.1.1",
"source-map": "^0.5.3",
"request": "2.81.0"
},
"devDependencies": {
"diff": "^2.2.2",
"diff": "^3.2.0",
"git-rev": "^0.2.1",
"grunt": "~0.4.5",
"grunt-browserify": "^5.0.0",
"grunt-contrib-clean": "^1.0.0",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-connect": "^1.0.2",
"grunt-contrib-jasmine": "^1.0.3",
"grunt-contrib-jshint": "^1.0.0",
"grunt-contrib-uglify": "^1.0.1",
"grunt-jscs": "^2.8.0",
"grunt-saucelabs": "^8.6.2",
"grunt-eslint": "^19.0.0",
"grunt-saucelabs": "^9.0.0",
"grunt-shell": "^1.3.0",
"import-module": "file:test/import-module",
"jit-grunt": "^0.10.0",
"less-plugin-clean-css": "^1.5.1",
"performance-now": "^0.2.0",
"phantomjs-prebuilt": "^2.1.7",
"phin": "^2.2.3",
"promise": "^7.1.1",
"time-grunt": "^1.3.0"
},
"keywords": [
@@ -92,5 +97,6 @@
"css less"
],
"rawcurrent": "https://raw.github.com/less/less.js/v",
"sourcearchive": "https://github.com/less/less.js/archive/v"
"sourcearchive": "https://github.com/less/less.js/archive/v",
"dependencies": {}
}

View File

@@ -1,7 +1,42 @@
/* Add js reporter for sauce */
jasmine.getEnv().addReporter(new jasmine.JSReporter2());
jasmine.getEnv().defaultTimeoutInterval = 3000;
// From https://github.com/axemclion/grunt-saucelabs/issues/109#issuecomment-166767282
// (function () {
// var oldJSReport = window.jasmine.getJSReport;
// window.jasmine.getJSReport = function () {
// var results = oldJSReport();
// if (results) {
// return {
// durationSec: results.durationSec,
// suites: removePassingTests(results.suites),
// passed: results.passed
// };
// } else {
// return null;
// }
// };
// function removePassingTests (suites) {
// return suites.filter(specFailed)
// .map(mapSuite);
// }
// function mapSuite (suite) {
// var result = {};
// for (var s in suite) {
// result[s] = suite[s];
// }
// result.specs = suite.specs.filter(specFailed);
// result.suites = removePassingTests(suite.suites);
// return result;
// }
// function specFailed (item) {
// return !item.passed;
// }
// })();
/* record log messages for testing */
var logMessages = [];
@@ -44,15 +79,15 @@ less.loggers = [
}
];
var testLessEqualsInDocument = function () {
testLessEqualsInDocument = function () {
testLessInDocument(testSheet);
};
var testLessErrorsInDocument = function (isConsole) {
testLessErrorsInDocument = function (isConsole) {
testLessInDocument(isConsole ? testErrorSheetConsole : testErrorSheet);
};
var testLessInDocument = function (testFunc) {
testLessInDocument = function (testFunc) {
var links = document.getElementsByTagName('link'),
typePattern = /^text\/(x-)?less$/;
@@ -64,7 +99,7 @@ var testLessInDocument = function (testFunc) {
}
};
var ieFormat = function(text) {
ieFormat = function(text) {
var styleNode = document.createElement('style');
styleNode.setAttribute('type', 'text/css');
var headNode = document.getElementsByTagName('head')[0];
@@ -83,7 +118,7 @@ var ieFormat = function(text) {
return transformedText;
};
var testSheet = function (sheet) {
testSheet = function (sheet) {
it(sheet.id + " should match the expected output", function (done) {
var lessOutputId = sheet.id.replace("original-", ""),
expectedOutputId = "expected-" + lessOutputId,
@@ -112,7 +147,7 @@ var testSheet = function (sheet) {
});
};
//TODO: do it cleaner - the same way as in css
// TODO: do it cleaner - the same way as in css
function extractId(href) {
return href.replace(/^[a-z-]+:\/+?[^\/]+/i, '') // Remove protocol & domain
@@ -122,7 +157,7 @@ function extractId(href) {
.replace(/\./g, ':'); // Replace dots with colons(for valid id)
}
var waitFor = function (waitFunc) {
waitFor = function (waitFunc) {
return new Promise(function (resolve) {
var timeoutId = setInterval(function () {
if (waitFunc()) {
@@ -133,7 +168,7 @@ var waitFor = function (waitFunc) {
});
};
var testErrorSheet = function (sheet) {
testErrorSheet = function (sheet) {
it(sheet.id + " should match an error", function (done) {
var lessHref = sheet.href,
id = "less-error-message:" + extractId(lessHref),
@@ -147,7 +182,7 @@ var testErrorSheet = function (sheet) {
actualErrorElement = document.getElementById(id);
return actualErrorElement !== null;
}).then(function () {
var innerText = (actualErrorElement.innerHTML
var innerText = (actualErrorElement.innerHTML
.replace(/<h3>|<\/?p>|<a href="[^"]*">|<\/a>|<ul>|<\/?pre( class="?[^">]*"?)?>|<\/li>|<\/?label>/ig, "")
.replace(/<\/h3>/ig, " ")
.replace(/<li>|<\/ul>|<br>/ig, "\n"))
@@ -155,34 +190,36 @@ var testErrorSheet = function (sheet) {
// for IE8
.replace(/\r\n/g, "\n")
.replace(/\. \nin/, ". in");
actualErrorMsg = innerText
actualErrorMsg = innerText
.replace(/\n\d+/g, function (lineNo) {
return lineNo + " ";
})
.replace(/\n\s*in /g, " in ")
.replace(/\n{2,}/g, "\n")
.replace(/\nStack Trace\n[\s\S]*/i, "")
.replace(/\n$/, "");
errorFile
.replace(/\n$/, "")
.trim();
errorFile
.then(function (errorTxt) {
errorTxt = errorTxt
.replace(/\{path\}/g, "")
.replace(/\{pathrel\}/g, "")
.replace(/\{pathhref\}/g, "http://localhost:8081/test/less/errors/")
.replace(/\{404status\}/g, " (404)")
.replace(/\{node\}.*\{\/node\}/g, "")
.replace(/\n$/, "");
.replace(/\{node\}[\s\S]*\{\/node\}/g, "")
.replace(/\n$/, "")
.trim();
expect(actualErrorMsg).toEqual(errorTxt);
if (errorTxt == actualErrorMsg) {
actualErrorElement.style.display = "none";
}
done();
});
});
});
});
};
var testErrorSheetConsole = function (sheet) {
testErrorSheetConsole = function (sheet) {
it(sheet.id + " should match an error", function (done) {
var lessHref = sheet.href,
id = sheet.id.replace(/^original-less:/, "less-error-message:"),
@@ -211,7 +248,7 @@ var testErrorSheetConsole = function (sheet) {
});
};
var loadFile = function (href) {
loadFile = function (href) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', href, true);

View File

@@ -0,0 +1,3 @@
.test {
val: http://localhost:8081/tmp/browser/test-runner-browser.html;
}

View File

@@ -42,7 +42,7 @@
* @param startMs Start time in Milliseconds
* @param finishMs Finish time in Milliseconds
* @return Elapsed time in Seconds */
function elapsedSec (startMs, finishMs) {
function elapsedSec(startMs, finishMs) {
return (finishMs - startMs) / 1000;
}
@@ -52,7 +52,7 @@
* @param amount Amount to round
* @param numOfDecDigits Number of Digits to round to. Default value is '2'.
* @return Rounded amount */
function round (amount, numOfDecDigits) {
function round(amount, numOfDecDigits) {
numOfDecDigits = numOfDecDigits || 2;
return Math.round(amount * Math.pow(10, numOfDecDigits)) / Math.pow(10, numOfDecDigits);
}
@@ -61,7 +61,7 @@
* Create a new array which contains only the failed items.
* @param items Items which will be filtered
* @returns {Array} of failed items */
function failures (items) {
function failures(items) {
var fs = [], i, v;
for (i = 0; i < items.length; i += 1) {
v = items[i];
@@ -76,7 +76,7 @@
* Collect information about a Suite, recursively, and return a JSON result.
* @param suite The Jasmine Suite to get data from
*/
function getSuiteData (suite) {
function getSuiteData(suite) {
var suiteData = {
description : suite.description,
durationSec : 0,

View File

@@ -0,0 +1,7 @@
registerPlugin({
install: function(less, pluginManager, functions) {
functions.add('func', function() {
return less.anonymous(location.href);
});
}
});

View File

@@ -0,0 +1,4 @@
@plugin "plugin";
.test {
val: func();
}

View File

@@ -1,4 +1,9 @@
var less = {logLevel: 4, errorReporting: "console"};
var less = {
logLevel: 4,
errorReporting: "console",
javascriptEnabled: true,
strictMath: false
};
// There originally run inside describe method. However, since they have not
// been inside it, they run at jasmine compile time (not runtime). It all

View File

@@ -1,4 +1,6 @@
var less = {
strictUnits: true,
strictMath: true,
logLevel: 4 };
logLevel: 4,
javascriptEnabled: true
};

View File

@@ -1,4 +1,5 @@
var less = {logLevel: 4,
var less = {
logLevel: 4,
errorReporting: "console",
plugins: [AddFilePlugin]
};
};

View File

@@ -2,4 +2,5 @@ var less = {
logLevel: 4,
errorReporting: "console",
strictMath: false,
strictUnits: false };
strictUnits: false
};

Some files were not shown because too many files have changed in this diff Show More