This commit is contained in:
Justineo
2015-02-02 16:15:40 +08:00
156 changed files with 5243 additions and 2588 deletions

1
.gitattributes vendored
View File

@@ -1,4 +1,5 @@
*.js text eol=lf
*.svg text eol=lf
lessc text eol=lf
*.less text eol=lf
*.css text eol=lf

1
.gitignore vendored
View File

@@ -16,6 +16,7 @@ test/browser/less.js
test/sourcemaps/**/*.map
test/sourcemaps/*.map
test/sourcemaps/*.css
test/less-bom
# grunt
.grunt

73
.jscsrc Normal file
View File

@@ -0,0 +1,73 @@
{
"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 +1,9 @@
{
"evil": true,
"laxbreak": true,
"latedef": true,
"node": true,
"undef": true,
"unused": "vars",
"trailing": true,
"noarg": true,
"eqnull": true,
"forin": true,

View File

@@ -5,3 +5,7 @@ node_js:
install:
- npm install -g grunt-cli
- npm install
env:
global:
- secure: TrNVruWYaUK5ALga1y7wRY+MLjWJECUSCsBmKW5EUmIevOUxqHWu7M89FANKxstEeFRRAGH3QJbloRxnzIgh0U0ah5npE9XA1bYXGO5khoXeIyk7pNRfjIo8aEnJH1Vp8vWA6J6ovxdJ7lCFKEGvGKxGde50knVl7KFVVULlX2U=
- secure: Rzh+CEI7YRvvVkOruPE8Z0dkU0s13V6b6cpqbN72vxbJl/Jm5PUZkjTFJdkWJrW3ErhCKX6EC7XdGvrclqEA9WAqKzrecqCJYqTnw4MwqiAj6F9wqE/BqhoWg4xPxm0MK/7eJMvLCgjNpe+gc1CaeFJZkLSNWn6nOFke+vVlf9Q=

View File

@@ -1,12 +1,120 @@
# 2.3.1
2015-01-28
- Fix depends option (regression in 2.3.0)
- Support parent selector (`&`) used in sub element expression (e.g. `:not(.c_&)`)
# 2.3.0
2015-01-27
- add isruleset function
- add optional import option, causing less to not fail if file not found
- Fix browsers-side cache.
- Many fixes to import reference - support `@support` and keyframe
- Selectors now interpolate pseudo selectors (e.g. `:@{hover}`)
- Fix comments missed off if they were at the end of the file
- Fix !important used with parametric mixins
- Emits warnings for extends when the target is not found
- include-path now works on data-uri
- variables and function calls work for path in data-uri
- Fix absolute paths not working on imports sometimes.
- Unicode BOM removed again
- Misc. bug fixes
# 2.2.0
2015-01-04
- do not apply relative paths to svg-gradient and data-uri functions data-uri output
- using import filename interpolation and import inline together now works
- deprecate the compression option (still works, but outputs a warning unless silent)
- The node version of less now has image-size, image-width, image-height which return the image dimensions of a file
- Fixed an issue that could cause the parse to occur more than once and the callback be called multiple times
- if you are outputting to the console, lessc defaults to silent so warnings do not end up in output
- isunit function supports '' to test if a dimension has no unit
- data-uri function now counts characters after base64 encoding instead of bytes before encoding to determine ie8 support
- fix bug effecting guards on pseudo class selectors
- do not cache on the browser when used with modifyVars
- detection if less does not parse last character in file
- detection of whether a file is css now requires /css, .css, ?css, &css instead of just css. You can still tell less the type of file using import options.
- remove extra new line added to sourcemap entry inline file
- support safari extension
- less.parse now exposes a way to get the AST. We do not recommend you use this unless you need to.
# 2.1.2
2014-12-20
- Fix for use with requirejs
- Fixes for data-uri function
# 2.1.1
2014-11-27
- Improved keyword and anonymous usage with the replace function
- Added getCSSAppendage to sourcemap builder to avoid duplication in plugins
- Fix problem with plugins when used with the promises version of render
- If the render callback throws an exception it now propogates instead of calling the callback again with an error
# 2.1.0
2014-11-23
- Fixed isSync option, it was using sync file operations but promises are guaranteed to call back async. We now support promises as a feature rather than the 1st class way of doing things.
- Browser code is now synchronous again, like in v1, meaning it blocks the site until less is compiled
- Some fixes for variable imports which affected filemanagers when synchronous
- Fixed lessc makefile dependencies option
- output now reports back a imports field with an array of imported files
- relative path test for drive names (so windows only) is now case insensitive
- Fix for IE7 - use getChar instead of indexing array
- variables using !important now output !important, which bubbles up to affect the rule
- livereload cache buster is now treated specially
- upgrade dependencies
# 2.0.0
2014-11-09
- Fixed multiplication in non strict units mode to take the left operand unit, in the case that the unit cannot be resolved
- Some fixes for browser cross-compatibility
- browser tests now pass in IE 8-11 and FF
- added index.js and browser.js in root as shortcuts
- fixed some local variable spellings
- support for @counter-style directive
# 2.0.0-b3
2014-11-01
- some refactoring of browser structure to allow use of api vs normal browser bundle
- browser bundle no longer leaks require
- browser can now be scoped with just window
- browser useFileCache defaults to true, but file cache is now cleared when refreshing or in watch mode
# 2.0.0-b2
2014-10-26
- Imports are now sequenced and so are consistent (previously some complex projects might end up with occasional different orderings)
- Imports with variables are better supported - variables can be specified in sub imports
- Support for rebeccapurple
- Browser can now accept options as attributes on the script tag and the link tags e.g. `<script data-verbose="false" src="less.js"...`
- adding a .less file extension is done in the abstract file manager so it the behaviour can be overridden by certain file managers
- Fixed a bug where unquoted urls beginning `//` e.g. `url(//file/file.less)` would be incorrectly interpreted (bug introduced in b-1)
- lessc plugins can be a function, used as a constructor as well as an object - this to allow the plugin more flexibility to be used programattically
# 2.0.0-b1
2014-10-19
- Public Beta / Release Candidate - Feedback welcome
For a guide to breaking changes see [the v2 upgrade guide](lesscss.org/usage/#v2-upgrade-guide)
For a guide to breaking changes see [the v2 upgrade guide](http://lesscss.org/usage/#v2-upgrade-guide)
- no longer including old versions of less in the repo or npm
- not including test less and gradle files in npm
- colours now output in the format they are added unless compressing, so yellow will output yellow, not its hex counterpart
- colours now output in the format they are added, so yellow will output yellow, not its hex counterpart
- better parsing - better comment support and comments in brackets can now contain comments including quotes.
- Removal of dependency on clean-css - install less-plugin-clean-css and use --clean-css to reference plugin
- Environment Support - less is now separate from its node and browser environment implementations and adding support for another javascript environment should be straight forward.
@@ -175,7 +283,7 @@
- support for guards on selectors (currently only if you have a single selector)
- allow property merging through the +: syntax
- Added min/max functions
- Added length function and improved extract to work with comma seperated values
- Added length function and improved extract to work with comma separated values
- when using import multiple, sub imports are imported multiple times into final output
- fix bad spaces between namespace operators
- do not compress comment if it begins with an exclamation mark
@@ -272,7 +380,7 @@
- browser and server url re-writing is now aligned to not re-write (previous lessc behaviour)
- url-rewriting can be made to re-write to be relative to the entry file using the relative-urls option (less.relativeUrls option)
- rootpath option can be used to add a base path to every url
- Support mixin argument seperator of ';' so you can pass comma seperated values. e.g. `.mixin(23px, 12px;);`
- Support mixin argument separator of ';' so you can pass comma separated values. e.g. `.mixin(23px, 12px;);`
- Fix lots of problems with named arguments in corner cases, not behaving as expected
- hsv, hsva, unit functions
- fixed lots more bad error messages

View File

@@ -6,6 +6,8 @@ module.exports = function (grunt) {
// Report the elapsed execution time of tasks.
require('time-grunt')(grunt);
var COMPRESS_FOR_TESTS = true;
// Project configuration.
grunt.initConfig({
@@ -46,9 +48,13 @@ module.exports = function (grunt) {
browserify: {
browser: {
src: ['./lib/less-browser/index.js'],
src: ['./lib/less-browser/bootstrap.js'],
options: {
alias: ["promise/polyfill.js:promise"]
exclude: ["promise"],
require: ["promise/polyfill.js"],
browserifyOptions: {
standalone: 'less'
}
},
dest: 'tmp/less.js'
}
@@ -59,7 +65,7 @@ module.exports = function (grunt) {
banner: '<%= meta.banner %>'
},
browsertest: {
src: '<%= browserify.browser.dest %>',
src: COMPRESS_FOR_TESTS ? '<%= uglify.test.dest %>' : '<%= browserify.browser.dest %>',
dest: 'test/browser/less.js'
},
dist: {
@@ -89,11 +95,18 @@ module.exports = function (grunt) {
uglify: {
options: {
banner: '<%= meta.banner %>',
mangle: true
mangle: true,
compress: {
pure_getters: true
}
},
dist: {
src: ['<%= concat.dist.dest %>'],
dest: 'dist/less.min.js'
},
test: {
src: '<%= browserify.browser.dest %>',
dest: 'tmp/less.min.js'
}
},
@@ -111,6 +124,13 @@ module.exports = function (grunt) {
}
},
jscs: {
src: ["test/**/*.js", "lib/less*/**/*.js", "bin/lessc"],
options: {
config: ".jscsrc"
}
},
connect: {
server: {
options: {
@@ -121,10 +141,9 @@ module.exports = function (grunt) {
jasmine: {
options: {
// version: '2.0.0-rc2',
keepRunner: true,
host: 'http://localhost:8081/',
vendor: ['test/browser/common.js', 'test/browser/less.js'],
vendor: ['test/browser/jasmine-jsreporter.js', 'test/browser/common.js', 'test/browser/less.js'],
template: 'test/browser/test-runner-template.tmpl'
},
main: {
@@ -144,6 +163,14 @@ module.exports = function (grunt) {
outfile: 'tmp/browser/test-runner-legacy.html'
}
},
strictUnits: {
src: ['test/less/strict-units/*.less'],
options: {
helpers: 'test/browser/runner-strict-units-options.js',
specs: 'test/browser/runner-strict-units-spec.js',
outfile: 'tmp/browser/test-runner-strict-units.html'
}
},
errors: {
src: ['test/less/errors/*.less', '!test/less/errors/javascript-error.less'],
options: {
@@ -222,15 +249,73 @@ module.exports = function (grunt) {
options: {
helpers: 'test/browser/runner-postProcessor-options.js',
specs: 'test/browser/runner-postProcessor.js',
outfile: 'tmp/browser/test-postProcessor.html'
outfile: 'tmp/browser/test-runner-post-processor.html'
}
}
},
'saucelabs-jasmine': {
all: {
options: {
urls: ["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
}
}
},
// Clean the version of less built for the tests
clean: {
test: ['test/browser/less.js', 'tmp'],
"sourcemap-test": ['test/sourcemaps/*.css', 'test/sourcemaps/*.map']
test: ['test/browser/less.js', 'tmp', 'test/less-bom'],
"sourcemap-test": ['test/sourcemaps/*.css', 'test/sourcemaps/*.map'],
sauce_log: ["sc_*.log"]
}
});
@@ -269,6 +354,7 @@ module.exports = function (grunt) {
// Create the browser version of less.js
grunt.registerTask('browsertest-lessjs', [
'browserify:browser',
'uglify:test',
'concat:browsertest'
]);
@@ -282,16 +368,55 @@ module.exports = function (grunt) {
// setup a web server to run the browser tests in a browser rather than phantom
grunt.registerTask('browsertest-server', [
'browsertest-lessjs',
'jasmine::build',
'connect::keepalive'
]);
// Run all tests
grunt.registerTask('test', [
var previous_force_state = grunt.option("force");
grunt.registerTask("force",function(set) {
if (set === "on") {
grunt.option("force",true);
}
else if (set === "off") {
grunt.option("force",false);
}
else if (set === "restore") {
grunt.option("force",previous_force_state);
}
});
grunt.registerTask('sauce', [
'browsertest-lessjs',
'jasmine::build',
'connect',
'sauce-after-setup'
]);
// setup a web server to run the browser tests in a browser rather than phantom
grunt.registerTask('sauce-after-setup', [
'saucelabs-jasmine',
'clean:sauce_log'
]);
var testTasks = [
'clean',
'jshint',
'jscs',
'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")) {
testTasks.push("force:on");
testTasks.push("sauce-after-setup");
testTasks.push("force:off");
}
// Run all tests
grunt.registerTask('test', testTasks);
// generate a good test environment for testing sourcemaps
grunt.registerTask('sourcemap-test', [

View File

@@ -1,5 +1,6 @@
[![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)
[![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)
# [Less.js](http://lesscss.org)
@@ -12,9 +13,9 @@ This is the JavaScript, official, stable version of Less.
Options for adding Less.js to your project:
* Install with [NPM](https://npmjs.org/): `npm install less`
* Install with [npm](https://npmjs.org): `npm install less`
* [Download the latest release][download]
* Clone the repo: `git clone git://github.com/less/less.js.git`
* Clone the repo: `git clone https://github.com/less/less.js.git`
## More information
@@ -27,11 +28,11 @@ Here are other resources for using Less.js:
## Contributing
Please read [CONTRIBUTING.md](./CONTRIBUTING.md). Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
Please read [CONTRIBUTING.md](CONTRIBUTING.md). Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com).
### Reporting Issues
Before opening any issue, please search for existing issues and read the [Issue Guidelines](https://github.com/necolas/issue-guidelines), written by [Nicolas Gallagher](https://github.com/necolas/). After that if you find a bug or would like to make feature request, [please open a new issue][issues].
Before opening any issue, please search for existing issues and read the [Issue Guidelines](https://github.com/necolas/issue-guidelines), written by [Nicolas Gallagher](https://github.com/necolas). After that if you find a bug or would like to make feature request, [please open a new issue][issues].
Please report documentation issues in [the documentation project](https://github.com/less/less-docs).
@@ -44,7 +45,7 @@ See the [changelog](CHANGELOG.md)
## [License](LICENSE)
Copyright (c) 2009-2014 [Alexis Sellier](http://cloudhead.io/) & The Core Less Team
Copyright (c) 2009-2014 [Alexis Sellier](http://cloudhead.io) & The Core Less Team
Licensed under the [Apache License](LICENSE).

33
appveyor.yml Normal file
View File

@@ -0,0 +1,33 @@
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.10"
- nodejs_version: "0.11"
# Allow failing jobs for bleeding-edge Node.js versions.
matrix:
allow_failures:
- nodejs_version: "0.11"
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node 0.STABLE.latest
- ps: Install-Product node $env:nodejs_version
# Typical npm stuff.
- npm install
# Grunt-specific stuff.
- npm install -g grunt-cli
# Post-install test scripts.
test_script:
# Output useful info for debugging.
- node --version
- npm --version
# Run test
- grunt test
# Don't actually build.
build: off
# Set build version format here instead of in the admin panel.
version: "{build}"

726
bin/lessc
View File

@@ -3,8 +3,15 @@
var path = require('path'),
fs = require('../lib/less-node/fs'),
os = require('os'),
errno,
mkdirp;
try {
errno = require('errno');
} catch (err) {
errno = null;
}
var less = require('../lib/less-node'),
pluginLoader = new less.PluginLoader(less),
plugin,
@@ -27,8 +34,8 @@ var silent = false,
ieCompat: true,
strictMath: false,
strictUnits: false,
globalVariables: '',
modifyVariables: '',
globalVars: null,
modifyVars: null,
urlArgs: '',
plugins: plugins
};
@@ -52,16 +59,16 @@ var checkArgFunc = function(arg, option) {
var checkBooleanArg = function(arg) {
var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg);
if (!onOff) {
console.log(" unable to parse "+arg+" as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
console.log(" unable to parse " + arg + " as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
continueProcessing = false;
return false;
}
return Boolean(onOff[2]);
};
var parseVariableOption = function(option) {
var parseVariableOption = function(option, variables) {
var parts = option.split('=', 2);
return '@' + parts[0] + ': ' + parts[1] + ';\n';
variables[parts[0]] = parts[1];
};
var warningMessages = "";
@@ -73,362 +80,411 @@ function printUsage() {
continueProcessing = false;
}
args = args.filter(function (arg) {
var match;
// 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(/^-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; }
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;
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])) {
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])) {
options.globalVariables += parseVariableOption(match[2]);
}
break;
case "modify-var":
if (checkArgFunc(arg, match[2])) {
options.modifyVariables += parseVariableOption(match[2]);
}
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];
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])) {
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 = {};
}
plugin = pluginLoader.tryLoadPlugin(name, pluginOptions);
if (plugin) {
plugins.push(plugin);
} else {
console.log("Unable to load plugin " + name + " please make sure that it is installed under or at the same level as less");
console.log();
printUsage();
currentErrorcode = 1;
}
break;
default:
plugin = pluginLoader.tryLoadPlugin("less-plugin-" + arg, match[2]);
if (plugin) {
plugins.push(plugin);
} else {
console.log("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");
console.log();
printUsage();
currentErrorcode = 1;
}
break;
}
});
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];
if (!continueProcessing) {
return;
}
var input = args[1];
if (input && input != '-') {
input = path.resolve(process.cwd(), input);
}
var output = args[2];
var outputbase = args[2];
if (output) {
output = path.resolve(process.cwd(), output);
if (warningMessages) {
console.log(warningMessages);
}
}
if (options.sourceMap) {
sourceMapOptions.sourceMapInputFilename = input;
if (!sourceMapOptions.sourceMapFullFilename) {
if (!output && !sourceMapFileInline) {
console.log("the sourcemap option only has an optional filename if the css filename is given");
console.log("consider adding --source-map-map-inline which embeds the sourcemap into the css");
return;
plugin = pluginLoader.tryLoadPlugin(name, pluginOptions);
if (plugin) {
plugins.push(plugin);
} else {
console.log("Unable to load plugin " + name +
" please make sure that it is installed under or at the same level as less");
console.log();
printUsage();
currentErrorcode = 1;
}
break;
default:
plugin = pluginLoader.tryLoadPlugin("less-plugin-" + arg, match[2]);
if (plugin) {
plugins.push(plugin);
} else {
console.log("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");
console.log();
printUsage();
currentErrorcode = 1;
}
break;
}
// its in the same directory, so always just the basename
sourceMapOptions.sourceMapOutputFilename = path.basename(output);
sourceMapOptions.sourceMapFullFilename = output + ".map";
// its in the same directory, so always just the basename
sourceMapOptions.sourceMapFilename = path.basename(sourceMapOptions.sourceMapFullFilename);
} else if (options.sourceMap && !sourceMapFileInline) {
var mapFilename = path.resolve(process.cwd(), sourceMapOptions.sourceMapFullFilename),
mapDir = path.dirname(mapFilename),
outputDir = path.dirname(output);
// find the path from the map to the output file
sourceMapOptions.sourceMapOutputFilename = path.join(
path.relative(mapDir, outputDir), path.basename(output));
});
// make the sourcemap filename point to the sourcemap relative to the css file output directory
sourceMapOptions.sourceMapFilename = path.join(
path.relative(outputDir, mapDir), path.basename(sourceMapOptions.sourceMapFullFilename));
}
}
if (sourceMapOptions.sourceMapBasepath === undefined) {
sourceMapOptions.sourceMapBasepath = input ? path.dirname(input) : process.cwd();
}
if (sourceMapOptions.sourceMapRootpath === undefined) {
var pathToMap = path.dirname(sourceMapFileInline ? output : sourceMapOptions.sourceMapFullFilename),
pathToInput = path.dirname(sourceMapOptions.sourceMapInputFilename);
sourceMapOptions.sourceMapRootpath = path.relative(pathToMap, pathToInput);
}
if (! input) {
console.log("lessc: no input files");
console.log("");
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);
}
};
if (options.depends) {
if (!outputbase) {
console.log("option --depends requires an output path to be specified");
if (!continueProcessing) {
return;
}
process.stdout.write(outputbase + ": ");
}
if (!sourceMapFileInline) {
var writeSourceMap = function(output) {
var filename = sourceMapOptions.sourceMapFullFilename;
ensureDirectory(filename);
fs.writeFileSync(filename, output, 'utf8');
};
}
var input = args[1];
if (input && input != '-') {
input = path.resolve(process.cwd(), input);
}
var output = args[2];
var outputbase = args[2];
if (output) {
output = path.resolve(process.cwd(), output);
if (warningMessages) {
console.log(warningMessages);
}
}
var parseLessFile = function (e, data) {
if (e) {
console.log("lessc: " + e.message);
if (options.sourceMap) {
sourceMapOptions.sourceMapInputFilename = input;
if (!sourceMapOptions.sourceMapFullFilename) {
if (!output && !sourceMapFileInline) {
console.log("the sourcemap option only has an optional filename if the css filename is given");
console.log("consider adding --source-map-map-inline which embeds the sourcemap into the css");
return;
}
// its in the same directory, so always just the basename
sourceMapOptions.sourceMapOutputFilename = path.basename(output);
sourceMapOptions.sourceMapFullFilename = output + ".map";
// its in the same directory, so always just the basename
sourceMapOptions.sourceMapFilename = path.basename(sourceMapOptions.sourceMapFullFilename);
} else if (options.sourceMap && !sourceMapFileInline) {
var mapFilename = path.resolve(process.cwd(), sourceMapOptions.sourceMapFullFilename),
mapDir = path.dirname(mapFilename),
outputDir = path.dirname(output);
// find the path from the map to the output file
sourceMapOptions.sourceMapOutputFilename = path.join(
path.relative(mapDir, outputDir), path.basename(output));
// make the sourcemap filename point to the sourcemap relative to the css file output directory
sourceMapOptions.sourceMapFilename = path.join(
path.relative(outputDir, mapDir), path.basename(sourceMapOptions.sourceMapFullFilename));
}
}
if (sourceMapOptions.sourceMapBasepath === undefined) {
sourceMapOptions.sourceMapBasepath = input ? path.dirname(input) : process.cwd();
}
if (sourceMapOptions.sourceMapRootpath === undefined) {
var pathToMap = path.dirname(sourceMapFileInline ? output : sourceMapOptions.sourceMapFullFilename),
pathToInput = path.dirname(sourceMapOptions.sourceMapInputFilename);
sourceMapOptions.sourceMapRootpath = path.relative(pathToMap, pathToInput);
}
if (! input) {
console.log("lessc: no input files");
console.log("");
printUsage();
currentErrorcode = 1;
return;
}
data = options.globalVariables + data + '\n' + options.modifyVariables;
options.paths = [path.dirname(input)].concat(options.paths);
options.filename = input;
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) {
var parser = new(less.Parser)(options);
parser.parse(data, function (err, tree) {
if (err) {
less.writeError(err, options);
currentErrorcode = 1;
return;
}
for(var file in parser.imports.files) {
if (parser.imports.files.hasOwnProperty(file)) {
process.stdout.write(file + " ");
if (!outputbase) {
console.log("option --depends requires an output path to be specified");
return;
}
process.stdout.write(outputbase + ": ");
}
if (!sourceMapFileInline) {
var writeSourceMap = function(output, onDone) {
var filename = sourceMapOptions.sourceMapFullFilename;
ensureDirectory(filename);
fs.writeFile(filename, output, 'utf8', function (err) {
if (err) {
var description = "Error: ";
if (errno && errno.errno[err.errno]) {
description += errno.errno[err.errno].description;
} else {
description += err.code + " " + err.message;
}
less.logger.error('lessc: failed to create file ' + filename);
less.logger.error(description);
} else {
less.logger.info('lessc: wrote ' + filename);
}
onDone();
});
};
}
var writeSourceMapIfNeeded = function(output, onDone) {
if (options.sourceMap && !sourceMapFileInline) {
writeSourceMap(output, onDone);
} else {
onDone();
}
};
var writeOutput = function(output, result, onSuccess) {
if (output) {
ensureDirectory(output);
fs.writeFile(output, result.css, {encoding: 'utf8'}, function (err) {
if (err) {
var description = "Error: ";
if (errno && errno.errno[err.errno]) {
description += errno.errno[err.errno].description;
} else {
description += err.code + " " + err.message;
}
less.logger.error('lessc: failed to create file ' + output);
less.logger.error(description);
} else {
less.logger.info('lessc: wrote ' + output);
onSuccess();
}
});
} else if (!options.depends) {
process.stdout.write(result.css);
onSuccess();
}
};
var logDependencies = function(options, result) {
if (options.depends) {
var depends = "";
for (var i = 0; i < result.imports.length; i++) {
depends += result.imports[i] + " ";
}
console.log("");
});
return;
}
console.log(depends);
}
};
if (options.lint) {
options.sourceMap = false;
}
sourceMapOptions.sourceMapFileInline = sourceMapFileInline;
var parseLessFile = function (e, data) {
if (e) {
console.log("lessc: " + e.message);
currentErrorcode = 1;
return;
}
if (options.sourceMap) {
options.sourceMap = sourceMapOptions;
}
data = data.replace(/^\uFEFF/, '');
less.logger.addListener({
info: function(msg) {
if (verbose) {
options.paths = [path.dirname(input)].concat(options.paths);
options.filename = input;
if (options.lint) {
options.sourceMap = false;
}
sourceMapOptions.sourceMapFileInline = sourceMapFileInline;
if (options.sourceMap) {
options.sourceMap = sourceMapOptions;
}
less.logger.addListener({
info: function(msg) {
if (verbose) {
console.log(msg);
}
},
warn: function(msg) {
// do not show warning if outputting css to the console or the silent option is used
if (!silent && output) {
console.warn(msg);
}
},
error: function(msg) {
console.log(msg);
}
},
warn: function(msg) {
if (!silent) {
console.warn(msg);
}
},
error: function(msg) {
console.log(msg);
}
});
less.render(data, options)
.then(function(result) {
if(!options.lint) {
if (output) {
ensureDirectory(output);
fs.writeFileSync(output, result.css, 'utf8');
less.logger.info('lessc: wrote ' + output);
} else {
process.stdout.write(result.css);
}
if (options.sourceMap && !sourceMapFileInline) {
writeSourceMap(result.map);
}
}
},
function(err) {
less.writeError(err, options);
currentErrorcode = 1;
});
};
if (input != '-') {
fs.readFile(input, 'utf8', parseLessFile);
} else {
process.stdin.resume();
process.stdin.setEncoding('utf8');
less.render(data, options)
.then(function(result) {
if (!options.lint) {
writeOutput(output, result, function() {
writeSourceMapIfNeeded(result.map, function() {
logDependencies(options, result);
});
});
}
},
function(err) {
less.writeError(err, options);
currentErrorcode = 1;
});
};
var buffer = '';
process.stdin.on('data', function(data) {
buffer += data;
});
if (input != '-') {
fs.readFile(input, 'utf8', parseLessFile);
} else {
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('end', function() {
parseLessFile(false, buffer);
});
}
var buffer = '';
process.stdin.on('data', function(data) {
buffer += data;
});
process.stdin.on('end', function() {
parseLessFile(false, buffer);
});
}
})();

View File

@@ -1,7 +1,7 @@
{
"name": "less",
"version": "2.0.0-b1",
"main": "./dist/less.js",
"version": "2.3.1",
"main": "dist/less.js",
"ignore": [
"**/.*",
"benchmark",
@@ -15,6 +15,7 @@
"Gruntfile.js",
"*.json",
"*.yml",
"build.gradle",
"gradlew",
"gradlew.bat",
".gitattributes",

1
browser.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./lib/less-browser');

View File

@@ -1,4 +1,4 @@
if (typeof(window) === 'undefined') { less = {} }
if (typeof window === 'undefined') { less = {} }
else { less = window.less = {} }
tree = less.tree = {};
less.mode = 'rhino';

2204
dist/less.js vendored

File diff suppressed because it is too large Load Diff

13
dist/less.min.js vendored

File diff suppressed because one or more lines are too long

1
index.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./lib/less-node');

View File

@@ -0,0 +1,46 @@
var addDataAttr = require("./utils").addDataAttr,
browser = require("./browser");
module.exports = function(window, options) {
// use options from the current script tag data attribues
addDataAttr(options, browser.currentScript(window));
if (options.isFileProtocol === undefined) {
options.isFileProtocol = /^(file|(chrome|safari)(-extension)?|resource|qrc|app):/.test(window.location.protocol);
}
// Load styles asynchronously (default: false)
//
// This is set to `false` by default, so that the body
// doesn't start loading before the stylesheets are parsed.
// Setting this to `true` can result in flickering.
//
options.async = options.async || false;
options.fileAsync = options.fileAsync || false;
// Interval between watch polls
options.poll = options.poll || (options.isFileProtocol ? 1000 : 1500);
options.env = options.env || (window.location.hostname == '127.0.0.1' ||
window.location.hostname == '0.0.0.0' ||
window.location.hostname == 'localhost' ||
(window.location.port &&
window.location.port.length > 0) ||
options.isFileProtocol ? 'development'
: 'production');
var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(window.location.hash);
if (dumpLineNumbers) {
options.dumpLineNumbers = dumpLineNumbers[1];
}
if (options.useFileCache === undefined) {
options.useFileCache = true;
}
if (options.onReady === undefined) {
options.onReady = true;
}
};

26
lib/less-browser/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,26 @@
/**
* Kicks off less and compiles any stylesheets
* used in the browser distributed version of less
* to kick-start less using the browser api
*/
/*global window */
// shim Promise if required
require('promise/polyfill.js');
var options = window.less || {};
require("./add-default-options")(window, options);
var less = module.exports = require("./index")(window, options);
if (options.onReady) {
if (/!watch/.test(window.location.hash)) {
less.watch();
}
less.pageLoadFinished = less.registerStylesheets().then(
function () {
return less.refresh(less.env === 'development');
}
);
}

View File

@@ -8,50 +8,57 @@ module.exports = {
var id = 'less:' + (sheet.title || utils.extractId(href));
// If this has already been inserted into the DOM, we may need to replace it
var oldCss = document.getElementById(id);
var keepOldCss = false;
var oldStyleNode = document.getElementById(id);
var keepOldStyleNode = false;
// Create a new stylesheet node for insertion or (if necessary) replacement
var css = document.createElement('style');
css.setAttribute('type', 'text/css');
var styleNode = document.createElement('style');
styleNode.setAttribute('type', 'text/css');
if (sheet.media) {
css.setAttribute('media', sheet.media);
styleNode.setAttribute('media', sheet.media);
}
css.id = id;
styleNode.id = id;
if (!css.styleSheet) {
css.appendChild(document.createTextNode(styles));
if (!styleNode.styleSheet) {
styleNode.appendChild(document.createTextNode(styles));
// If new contents match contents of oldCss, don't replace oldCss
keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 &&
oldCss.firstChild.nodeValue === css.firstChild.nodeValue);
// If new contents match contents of oldStyleNode, don't replace oldStyleNode
keepOldStyleNode = (oldStyleNode !== null && oldStyleNode.childNodes.length > 0 && styleNode.childNodes.length > 0 &&
oldStyleNode.firstChild.nodeValue === styleNode.firstChild.nodeValue);
}
var head = document.getElementsByTagName('head')[0];
// If there is no oldCss, just append; otherwise, only append if we need
// to replace oldCss with an updated stylesheet
if (oldCss === null || keepOldCss === false) {
// If there is no oldStyleNode, just append; otherwise, only append if we need
// to replace oldStyleNode with an updated stylesheet
if (oldStyleNode === null || keepOldStyleNode === false) {
var nextEl = sheet && sheet.nextSibling || null;
if (nextEl) {
nextEl.parentNode.insertBefore(css, nextEl);
nextEl.parentNode.insertBefore(styleNode, nextEl);
} else {
head.appendChild(css);
head.appendChild(styleNode);
}
}
if (oldCss && keepOldCss === false) {
oldCss.parentNode.removeChild(oldCss);
if (oldStyleNode && keepOldStyleNode === false) {
oldStyleNode.parentNode.removeChild(oldStyleNode);
}
// For IE.
// This needs to happen *after* the style element is added to the DOM, otherwise IE 7 and 8 may crash.
// See http://social.msdn.microsoft.com/Forums/en-US/7e081b65-878a-4c22-8e68-c10d39c2ed32/internet-explorer-crashes-appending-style-element-to-head
if (css.styleSheet) {
if (styleNode.styleSheet) {
try {
css.styleSheet.cssText = styles;
styleNode.styleSheet.cssText = styles;
} catch (e) {
throw new Error("Couldn't reassign styleSheet.cssText.");
}
}
},
currentScript: function(window) {
var document = window.document;
return document.currentScript || (function() {
var scripts = document.getElementsByTagName("script");
return scripts[scripts.length - 1];
})();
}
};

View File

@@ -4,19 +4,19 @@ module.exports = function(window, options, logger) {
var cache = null;
if (options.env !== 'development') {
try {
cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
cache = (typeof window.localStorage === 'undefined') ? null : window.localStorage;
} catch (_) {}
}
return {
setCSS: function(path, lastModified, styles) {
if (cache) {
logger.info('saving ' + path+ ' to cache.');
logger.info('saving ' + path + ' to cache.');
try {
cache.setItem(path, styles);
cache.setItem(path + ':timestamp', lastModified);
} catch(e) {
//TODO - could do with adding more robust error handling
logger.error('failed to save');
logger.error('failed to save "' + path + '" to local storage for caching.');
}
}
},

View File

@@ -1,87 +1,88 @@
/*global window, XMLHttpRequest */
module.exports = function(options, isFileProtocol, logger) {
module.exports = function(options, logger) {
var PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
AbstractFileManager = require("../less/environment/abstract-file-manager.js");
var AbstractFileManager = require("../less/environment/abstract-file-manager.js");
var fileCache = {};
var fileCache = {};
//TODOS - move log somewhere. pathDiff and doing something similar in node. use pathDiff in the other browser file for the initial load
// isFileProtocol is global
//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;
}
}
}
var FileManager = function() {
};
FileManager.prototype = new AbstractFileManager();
FileManager.prototype.alwaysMakePathsAbsolute = function alwaysMakePathsAbsolute() {
return true;
};
FileManager.prototype.join = function join(basePath, laterPath) {
if (!basePath) {
return laterPath;
}
return this.extractUrlParts(laterPath, basePath).path;
};
FileManager.prototype.doXHR = function doXHR(url, type, callback, errback) {
var xhr = getXMLHttpRequest();
var async = isFileProtocol ? options.fileAsync : options.async;
if (typeof(xhr.overrideMimeType) === 'function') {
xhr.overrideMimeType('text/css');
}
logger.debug("XHR: Getting '" + url + "'");
xhr.open('GET', url, async);
xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
xhr.send(null);
function handleResponse(xhr, callback, errback) {
if (xhr.status >= 200 && xhr.status < 300) {
callback(xhr.responseText,
xhr.getResponseHeader("Last-Modified"));
} else if (typeof(errback) === 'function') {
errback(xhr.status, url);
}
}
if (isFileProtocol && !options.fileAsync) {
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
callback(xhr.responseText);
function getXMLHttpRequest() {
if (window.XMLHttpRequest && (window.location.protocol !== "file:" || !("ActiveXObject" in window))) {
return new XMLHttpRequest();
} else {
errback(xhr.status, url);
}
} else if (async) {
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
handleResponse(xhr, callback, errback);
try {
/*global ActiveXObject */
return new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
logger.error("browser doesn't support AJAX.");
return null;
}
};
} else {
handleResponse(xhr, callback, errback);
}
}
};
FileManager.prototype.supports = function(filename, currentDirectory, options, environment) {
return true;
};
FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, options, environment) {
return new PromiseConstructor(function(fullfill, reject) {
var FileManager = function() {
};
FileManager.prototype = new AbstractFileManager();
FileManager.prototype.alwaysMakePathsAbsolute = function alwaysMakePathsAbsolute() {
return true;
};
FileManager.prototype.join = function join(basePath, laterPath) {
if (!basePath) {
return laterPath;
}
return this.extractUrlParts(laterPath, basePath).path;
};
FileManager.prototype.doXHR = function doXHR(url, type, callback, errback) {
var xhr = getXMLHttpRequest();
var async = options.isFileProtocol ? options.fileAsync : options.async;
if (typeof xhr.overrideMimeType === 'function') {
xhr.overrideMimeType('text/css');
}
logger.debug("XHR: Getting '" + url + "'");
xhr.open('GET', url, async);
xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
xhr.send(null);
function handleResponse(xhr, callback, errback) {
if (xhr.status >= 200 && xhr.status < 300) {
callback(xhr.responseText,
xhr.getResponseHeader("Last-Modified"));
} else if (typeof errback === 'function') {
errback(xhr.status, url);
}
}
if (options.isFileProtocol && !options.fileAsync) {
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
callback(xhr.responseText);
} else {
errback(xhr.status, url);
}
} else if (async) {
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
handleResponse(xhr, callback, errback);
}
};
} else {
handleResponse(xhr, callback, errback);
}
};
FileManager.prototype.supports = function(filename, currentDirectory, options, environment) {
return true;
};
FileManager.prototype.clearFileCache = function() {
fileCache = {};
};
FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, options, environment, callback) {
if (currentDirectory && !this.isPathAbsolute(filename)) {
filename = currentDirectory + filename;
}
@@ -96,9 +97,9 @@ FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, o
if (options.useFileCache && fileCache[href]) {
try {
var lessText = fileCache[href];
fullfill({ contents: lessText, filename: href, webInfo: { lastModified: new Date() }});
callback(null, { contents: lessText, filename: href, webInfo: { lastModified: new Date() }});
} catch (e) {
reject({filename: href, message: "Error loading file " + href + " error was " + e.message});
callback({filename: href, message: "Error loading file " + href + " error was " + e.message});
}
return;
}
@@ -108,12 +109,11 @@ FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, o
fileCache[href] = data;
// Use remote copy (re-parse)
fullfill({ contents: data, filename: href, webInfo: { lastModified: lastModified }});
callback(null, { contents: data, filename: href, webInfo: { lastModified: lastModified }});
}, function doXHRError(status, url) {
reject({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")", href: href });
callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")", href: href });
});
}.bind(this));
};
};
return FileManager;
return FileManager;
};

View File

@@ -1,293 +1,256 @@
//
// browser.js - client-side engine
// index.js
// Should expose the additional browser functions on to the less object
//
/*global window, document, location */
var addDataAttr = require("./utils").addDataAttr,
browser = require("./browser");
var less;
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;
/*
TODO - options is now hidden - we should expose it on the less object, but not have it "as" the less object
less = { fileAsync: true }
then access as less.environment.options.fileAsync ?
*/
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);
var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(window.location.protocol),
options = window.less || {};
// shim Promise if required
require('promise/polyfill.js');
window.less = less = require('../less')();
var environment = less.environment,
FileManager = require("./file-manager")(options, isFileProtocol, less.logger),
fileManager = new FileManager();
environment.addFileManager(fileManager);
less.FileManager = FileManager;
require("./log-listener")(less, options);
var errors = require("./error-reporting")(window, less, options);
var browser = require("./browser");
var cache = less.cache = options.cache || require("./cache")(window, options, less.logger);
options.env = options.env || (window.location.hostname == '127.0.0.1' ||
window.location.hostname == '0.0.0.0' ||
window.location.hostname == 'localhost' ||
(window.location.port &&
window.location.port.length > 0) ||
isFileProtocol ? 'development'
: 'production');
// Load styles asynchronously (default: false)
//
// This is set to `false` by default, so that the body
// doesn't start loading before the stylesheets are parsed.
// Setting this to `true` can result in flickering.
//
options.async = options.async || false;
options.fileAsync = options.fileAsync || false;
// Interval between watch polls
options.poll = options.poll || (isFileProtocol ? 1000 : 1500);
//Setup user functions
if (options.functions) {
less.functions.functionRegistry.addMultiple(options.functions);
}
var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);
if (dumpLineNumbers) {
options.dumpLineNumbers = dumpLineNumbers[1];
}
var typePattern = /^text\/(x-)?less$/;
function postProcessCSS(styles) {
if (options.postProcessor && typeof options.postProcessor === 'function') {
styles = options.postProcessor.call(styles, styles) || styles;
//Setup user functions
if (options.functions) {
less.functions.functionRegistry.addMultiple(options.functions);
}
return styles;
}
function clone(obj) {
var cloned = {};
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
cloned[prop] = obj[prop];
var typePattern = /^text\/(x-)?less$/;
function postProcessCSS(styles) {
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;
}
// only really needed for phantom
function bind(func, thisArg) {
var curryArgs = Array.prototype.slice.call(arguments, 2);
return function() {
var args = curryArgs.concat(Array.prototype.slice.call(arguments, 0));
return func.apply(thisArg, args);
};
}
function loadStyles(modifyVars) {
var styles = document.getElementsByTagName('style'),
style;
for (var i = 0; i < styles.length; i++) {
style = styles[i];
if (style.type.match(typePattern)) {
var instanceOptions = clone(options);
instanceOptions.modifyVars = modifyVars;
var lessText = style.innerHTML || '';
instanceOptions.filename = document.location.href.replace(/#.*$/, '');
/*jshint loopfunc:true */
// use closure to store current style
less.render(lessText, instanceOptions,
bind(function(style, e, result) {
if (e) {
errors.add(e, "inline");
} else {
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = result.css;
} else {
style.innerHTML = result.css;
}
}
}, null, style));
}
}
}
return cloned;
}
// only really needed for phantom
function bind(func, thisArg) {
var curryArgs = Array.prototype.slice.call(arguments, 2);
return function() {
var args = curryArgs.concat(Array.prototype.slice.call(arguments, 0));
return func.apply(thisArg, args);
};
}
function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
function loadStyles(modifyVars) {
var styles = document.getElementsByTagName('style'),
style;
var instanceOptions = clone(options);
addDataAttr(instanceOptions, sheet);
instanceOptions.mime = sheet.type;
for (var i = 0; i < styles.length; i++) {
style = styles[i];
if (style.type.match(typePattern)) {
var instanceOptions = clone(options);
if (modifyVars) {
instanceOptions.modifyVars = modifyVars;
var lessText = style.innerHTML || '';
instanceOptions.filename = document.location.href.replace(/#.*$/, '');
}
if (modifyVars || instanceOptions.globalVars) {
instanceOptions.useFileCache = true;
function loadInitialFileCallback(loadedFile) {
var data = loadedFile.contents,
path = loadedFile.filename,
webInfo = loadedFile.webInfo;
var newFileInfo = {
currentDirectory: fileManager.getPath(path),
filename: path,
rootFilename: path,
relativeUrls: instanceOptions.relativeUrls};
newFileInfo.entryPath = newFileInfo.currentDirectory;
newFileInfo.rootpath = instanceOptions.rootpath || newFileInfo.currentDirectory;
if (webInfo) {
webInfo.remaining = remaining;
if (!instanceOptions.modifyVars) {
var css = cache.getCSS(path, webInfo);
if (!reload && css) {
webInfo.local = true;
callback(null, css, data, sheet, webInfo, path);
return;
}
}
}
/*jshint loopfunc:true */
// use closure to store current style
less.render(lessText, instanceOptions)
.then(bind(function(style, result) {
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = result.css;
} else {
style.innerHTML = result.css;
//TODO add tests around how this behaves when reloading
errors.remove(path);
instanceOptions.rootFileInfo = newFileInfo;
less.render(data, instanceOptions, function(e, result) {
if (e) {
e.href = path;
callback(e);
} else {
result.css = postProcessCSS(result.css);
if (!instanceOptions.modifyVars) {
cache.setCSS(sheet.href, webInfo.lastModified, result.css);
}
}, null, style),
function(e) {
errors.add(e, "inline");
});
callback(null, result.css, data, sheet, webInfo, path);
}
});
}
}
}
function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
var instanceOptions = clone(options);
instanceOptions.mime = sheet.type;
if (modifyVars) {
instanceOptions.modifyVars = modifyVars;
}
if (modifyVars || options.globalVars) {
instanceOptions.useFileCache = true;
}
fileManager.loadFile(sheet.href, null, instanceOptions, environment)
.then(function loadInitialFileCallback(loadedFile) {
var data = loadedFile.contents,
path = loadedFile.filename,
webInfo = loadedFile.webInfo;
var newFileInfo = {
currentDirectory: fileManager.getPath(path),
filename: path,
rootFilename: path,
relativeUrls: instanceOptions.relativeUrls};
newFileInfo.entryPath = newFileInfo.currentDirectory;
newFileInfo.rootpath = instanceOptions.rootpath || newFileInfo.currentDirectory;
if (webInfo) {
webInfo.remaining = remaining;
var css = cache.getCSS(path, webInfo);
if (!reload && css) {
browser.createCSS(window.document, css, sheet);
webInfo.local = true;
callback(null, null, data, sheet, webInfo, path);
fileManager.loadFile(sheet.href, null, instanceOptions, environment, function(e, loadedFile) {
if (e) {
callback(e);
return;
}
loadInitialFileCallback(loadedFile);
});
}
function loadStyleSheets(callback, reload, modifyVars) {
for (var i = 0; i < less.sheets.length; i++) {
loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars);
}
//TODO add tests around how this behaves when reloading
errors.remove(path);
instanceOptions.rootFileInfo = newFileInfo;
less.render(data, instanceOptions)
.then(function(result) {
callback(null, result.css, data, sheet, webInfo, path);
},
function(e) {
e.href = path;
callback(e);
});
},
function(e) {
callback(e);
});
}
function loadStyleSheets(callback, reload, modifyVars) {
for (var i = 0; i < less.sheets.length; i++) {
loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars);
}
}
function initRunningMode(){
if (less.env === 'development') {
less.watchTimer = setInterval(function () {
if (less.watchMode) {
loadStyleSheets(function (e, css, _, sheet, context) {
if (e) {
errors.add(e, e.href || sheet.href);
} else if (css) {
css = postProcessCSS(css);
browser.createCSS(window.document, css, sheet);
cache.setCSS(sheet.href, context.lastModified, css);
}
});
}
}, options.poll);
}
}
//
// Watch mode
//
less.watch = function () {
if (!less.watchMode ){
less.env = 'development';
initRunningMode();
}
this.watchMode = true;
return true;
};
less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; };
if (/!watch/.test(location.hash)) {
less.watch();
}
//
// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
//
less.registerStylesheets = function() {
return new Promise(function(resolve, reject) {
var links = document.getElementsByTagName('link');
less.sheets = [];
for (var i = 0; i < links.length; i++) {
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
(links[i].type.match(typePattern)))) {
less.sheets.push(links[i]);
}
function initRunningMode() {
if (less.env === 'development') {
less.watchTimer = setInterval(function () {
if (less.watchMode) {
fileManager.clearFileCache();
loadStyleSheets(function (e, css, _, sheet, webInfo) {
if (e) {
errors.add(e, e.href || sheet.href);
} else if (css) {
browser.createCSS(window.document, css, sheet);
}
});
}
}, options.poll);
}
}
resolve();
});
};
//
// Watch mode
//
less.watch = function () {
if (!less.watchMode ) {
less.env = 'development';
initRunningMode();
}
this.watchMode = true;
return true;
};
//
// With this function, it's possible to alter variables and re-render
// CSS without reloading less-files
//
less.modifyVars = function(record) {
return less.refresh(false, record);
};
less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; };
less.refresh = function (reload, modifyVars) {
return new Promise(function (resolve, reject) {
var startTime, endTime, totalMilliseconds;
startTime = endTime = new Date();
//
// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
//
less.registerStylesheets = function() {
return new Promise(function(resolve, reject) {
var links = document.getElementsByTagName('link');
less.sheets = [];
loadStyleSheets(function (e, css, _, sheet, webInfo) {
if (e) {
errors.add(e, e.href || sheet.href);
reject(e);
for (var i = 0; i < links.length; i++) {
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
(links[i].type.match(typePattern)))) {
less.sheets.push(links[i]);
}
}
if (webInfo.local) {
less.logger.info("loading " + sheet.href + " from cache.");
} else {
less.logger.info("rendered " + sheet.href + " successfully.");
css = postProcessCSS(css);
resolve();
});
};
//
// With this function, it's possible to alter variables and re-render
// CSS without reloading less-files
//
less.modifyVars = function(record) {
return less.refresh(true, record, false);
};
less.refresh = function (reload, modifyVars, clearFileCache) {
if ((reload || clearFileCache) && clearFileCache !== false) {
fileManager.clearFileCache();
}
return new Promise(function (resolve, reject) {
var startTime, endTime, totalMilliseconds;
startTime = endTime = new Date();
loadStyleSheets(function (e, css, _, sheet, webInfo) {
if (e) {
errors.add(e, e.href || sheet.href);
reject(e);
return;
}
if (webInfo.local) {
less.logger.info("loading " + sheet.href + " from cache.");
} else {
less.logger.info("rendered " + sheet.href + " successfully.");
}
browser.createCSS(window.document, css, sheet);
cache.setCSS(sheet.href, webInfo.lastModified, css);
}
less.logger.info("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms');
if (webInfo.remaining === 0) {
totalMilliseconds = new Date() - startTime;
less.logger.info("less has finished. css generated in " + totalMilliseconds + 'ms');
resolve({
startTime: startTime,
endTime: endTime,
totalMilliseconds: totalMilliseconds,
sheets: less.sheets.length
});
}
endTime = new Date();
}, reload, modifyVars);
less.logger.info("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms');
if (webInfo.remaining === 0) {
totalMilliseconds = new Date() - startTime;
less.logger.info("less has finished. css generated in " + totalMilliseconds + 'ms');
resolve({
startTime: startTime,
endTime: endTime,
totalMilliseconds: totalMilliseconds,
sheets: less.sheets.length
});
}
endTime = new Date();
}, reload, modifyVars);
loadStyles(modifyVars);
});
loadStyles(modifyVars);
});
};
less.refreshStyles = loadStyles;
return less;
};
less.refreshStyles = loadStyles;
less.pageLoadFinished = less.registerStylesheets().then(
function () {
return less.refresh(less.env === 'development');
}
);

View File

@@ -11,7 +11,7 @@ module.exports = function(less, options) {
// 1 - Errors
// 0 - None
// Defaults to 2
options.logLevel = typeof(options.logLevel) !== 'undefined' ? options.logLevel : (options.env === 'development' ? logLevel_info : logLevel_error);
options.logLevel = typeof options.logLevel !== 'undefined' ? options.logLevel : (options.env === 'development' ? logLevel_info : logLevel_error);
if (!options.loggers) {
options.loggers = [{
@@ -37,7 +37,7 @@ module.exports = function(less, options) {
}
}];
}
for(var i = 0; i < options.loggers.length; i++) {
for (var i = 0; i < options.loggers.length; i++) {
less.logger.addListener(options.loggers[i]);
}
};

View File

@@ -1,9 +1,24 @@
module.exports = {
extractId: function(href) {
return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' ) // Remove protocol & domain
.replace(/^\//, '' ) // Remove root /
.replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension
.replace(/[^\.\w-]+/g, '-') // Replace illegal characters
.replace(/\./g, ':'); // Replace dots with colons(for valid id)
.replace(/[\?\&]livereload=\w+/, '' ) // Remove LiveReload cachebuster
.replace(/^\//, '' ) // Remove root /
.replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension
.replace(/[^\.\w-]+/g, '-') // Replace illegal characters
.replace(/\./g, ':'); // Replace dots with colons(for valid id)
},
addDataAttr: function(options, tag) {
for (var opt in tag.dataset) {
if (tag.dataset.hasOwnProperty(opt)) {
if (opt === "env" || opt === "dumpLineNumbers" || opt === "rootpath" || opt === "errorReporting") {
options[opt] = tag.dataset[opt];
} else {
try {
options[opt] = JSON.parse(tag.dataset[opt]);
}
catch(_) {}
}
}
}
}
};

View File

@@ -1,8 +1,13 @@
var path = require('path'),
fs = require('./fs'),
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
PromiseConstructor,
AbstractFileManager = require("../less/environment/abstract-file-manager.js");
try {
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
} catch(e) {
}
var FileManager = function() {
};
@@ -15,65 +20,89 @@ FileManager.prototype.supportsSync = function(filename, currentDirectory, option
return true;
};
FileManager.prototype.loadFile = function(filename, currentDirectory, options, environment) {
return new PromiseConstructor(function(fullfill, reject) {
var fullFilename,
data;
FileManager.prototype.loadFile = function(filename, currentDirectory, options, environment, callback) {
var fullFilename,
data,
isAbsoluteFilename = this.isPathAbsolute(filename),
filenamesTried = [];
options = options || {};
options = options || {};
var paths = [currentDirectory];
if (options.paths) paths.push.apply(paths, options.paths);
if (paths.indexOf('.') === -1) paths.push('.');
if (options.syncImport || !PromiseConstructor) {
data = this.loadFileSync(filename, currentDirectory, options, environment, 'utf-8');
callback(data.error, data);
return;
}
if (options.syncImport) {
for (var i = 0; i < paths.length; i++) {
try {
fullFilename = path.join(paths[i], filename);
fs.statSync(fullFilename);
break;
} catch (e) {
fullFilename = null;
var paths = isAbsoluteFilename ? [""] : [currentDirectory];
if (options.paths) { paths.push.apply(paths, options.paths); }
if (!isAbsoluteFilename && paths.indexOf('.') === -1) { paths.push('.'); }
// promise is guarenteed 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) {
(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; }
fulfill({ contents: data, filename: fullFilename});
});
}
});
} else {
reject({ type: 'File', message: "'" + filename + "' wasn't found. Tried - " + filenamesTried.join(",") });
}
if (!fullFilename) {
reject({ type: 'File', message: "'" + filename + "' wasn't found" });
return;
}
data = fs.readFileSync(fullFilename, 'utf-8');
fullfill({ contents: data, filename: fullFilename});
} else {
(function tryPathIndex(i) {
if (i < paths.length) {
fullFilename = path.join(paths[i], filename);
fs.stat(fullFilename, function (err) {
if (err) {
tryPathIndex(i + 1);
} else {
fs.readFile(fullFilename, 'utf-8', function(e, data) {
if (e) { reject(e); return; }
// do processing in the next tick to allow
// file handling to dispose
process.nextTick(function() {
fullfill({ contents: data, filename: fullFilename});
});
});
}
});
} else {
reject({ type: 'File', message: "'" + filename + "' wasn't found" });
}
}(0));
}
}(0));
});
};
FileManager.prototype.loadFileSync = function(filename, currentDirectory, options, environment) {
filename = path.join(currentDirectory, filename);
return { contents: fs.readFileSync(filename), filename: filename };
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;
};
module.exports = FileManager;

View File

@@ -0,0 +1,34 @@
var Dimension = require("../less/tree/dimension"),
Expression = require("../less/tree/expression"),
functionRegistry = require("./../less/functions/function-registry"),
path = require("path");
function imageSize(filePathNode) {
var filePath = filePathNode.value;
var currentDirectory = filePathNode.currentFileInfo.relativeUrls ?
filePathNode.currentFileInfo.currentDirectory : filePathNode.currentFileInfo.entryPath;
var sizeOf = require('image-size');
filePath = path.join(currentDirectory, filePath);
return sizeOf(filePath);
}
var imageFunctions = {
"image-size": function(filePathNode) {
var size = imageSize(filePathNode);
return new Expression([
new Dimension(size.width, "px"),
new Dimension(size.height, "px")
]);
},
"image-width": function(filePathNode) {
var size = imageSize(filePathNode);
return new Dimension(size.width, "px");
},
"image-height": function(filePathNode) {
var size = imageSize(filePathNode);
return new Dimension(size.height, "px");
}
};
functionRegistry.addMultiple(imageFunctions);

View File

@@ -27,11 +27,11 @@ less.formatError = function(ctx, options) {
return ctx.stack || ctx.message;
}
if (typeof(extract[0]) === 'string') {
if (typeof extract[0] === 'string') {
error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
}
if (typeof(extract[1]) === 'string') {
if (typeof extract[1] === 'string') {
var errorTxt = ctx.line + ' ';
if (extract[1]) {
errorTxt += extract[1].slice(0, ctx.column) +
@@ -41,7 +41,7 @@ less.formatError = function(ctx, options) {
error.push(errorTxt);
}
if (typeof(extract[2]) === 'string') {
if (typeof extract[2] === 'string') {
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
}
error = error.join('\n') + stylize('', 'reset') + '\n';
@@ -68,4 +68,7 @@ less.writeError = function (ctx, options) {
console.error(less.formatError(ctx, options));
};
// provide image-size functionality
require('./image-size');
module.exports = less;

View File

@@ -26,31 +26,31 @@ var lessc_helper = {
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 Print help (this message) and exit.");
console.log(" --include-path=PATHS Set include paths. Separated by `:'. Use `;' on Windows.");
console.log(" -M, --depends Output a makefile import dependency list to stdout");
console.log(" --no-color Disable colorized output.");
console.log(" --no-ie-compat Disable IE compatibility checks.");
console.log(" --no-js Disable JavaScript in less files");
console.log(" -h, --help Prints help (this message) and exit.");
console.log(" --include-path=PATHS Sets include paths. Separated by `:'. Use `;' 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 Suppress output of error messages.");
console.log(" --strict-imports Force evaluation of imports.");
console.log(" --insecure Allow imports from insecure https hosts.");
console.log(" -v, --version Print version number and exit.");
console.log(" -x, --compress Compress output by removing some whitespaces.");
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(" -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(" --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) into the output css file");
console.log(" --source-map-url=URL the complete url and filename put in the less file");
console.log(" -rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls.");
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-write relative urls to the base less file.");
console.log(" -sm=on|off Turn 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(" -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 Allow mixed units, e.g. 1px+1em or 1px*1px which have units");
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.");
@@ -70,6 +70,8 @@ var lessc_helper = {
console.log(" media query which is compatible with the SASS");
console.log(" format, and 'all' which will do both.");
console.log(" --verbose Be verbose.");
console.log(" -x, --compress Compresses output by removing some whitespaces.");
console.log(" We recommend you use a dedicated minifer like less-plugin-clean-css");
console.log("");
console.log("Report bugs to: http://github.com/less/less.js/issues");
console.log("Home page: <http://lesscss.org/>");

View File

@@ -8,6 +8,11 @@ var PluginLoader = function(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));
@@ -33,7 +38,7 @@ PluginLoader.prototype.tryLoadPlugin = function(name, argument) {
return null;
};
PluginLoader.prototype.compareVersion = function(aVersion, bVersion) {
for(var i = 0; i < aVersion.length; i++) {
for (var i = 0; i < aVersion.length; i++) {
if (aVersion[i] !== bVersion[i]) {
return parseInt(aVersion[i]) > parseInt(bVersion[i]) ? -1 : 1;
}
@@ -42,7 +47,7 @@ PluginLoader.prototype.compareVersion = function(aVersion, bVersion) {
};
PluginLoader.prototype.versionToString = function(version) {
var versionString = "";
for(var i = 0; i < version.length; i++) {
for (var i = 0; i < version.length; i++) {
versionString += (versionString ? "." : "") + version[i];
}
return versionString;
@@ -76,7 +81,7 @@ PluginLoader.prototype.tryRequirePlugin = function(name) {
}
};
PluginLoader.prototype.printUsage = function(plugins) {
for(var i = 0; i < plugins.length; i++) {
for (var i = 0; i < plugins.length; i++) {
var plugin = plugins[i];
if (plugin.printUsage) {
plugin.printUsage();

View File

@@ -1,7 +1,7 @@
var isUrlRe = /^(?:https?:)?\/\//i,
url = require('url'),
request,
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
PromiseConstructor,
AbstractFileManager = require("../less/environment/abstract-file-manager.js"),
logger = require("../less/logger");
@@ -15,7 +15,10 @@ UrlFileManager.prototype.supports = function(filename, currentDirectory, options
};
UrlFileManager.prototype.loadFile = function(filename, currentDirectory, options, environment) {
return new PromiseConstructor(function(fullfill, reject) {
if (!PromiseConstructor) {
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
}
return new PromiseConstructor(function(fulfill, reject) {
if (request === undefined) {
try { request = require('request'); }
catch(e) { request = null; }
@@ -35,7 +38,7 @@ UrlFileManager.prototype.loadFile = function(filename, currentDirectory, options
request.get({uri: urlStr, strictSSL: !options.insecure }, function (error, res, body) {
if (error) {
reject({ type: 'File', message: "resource '" + urlStr + "' gave this Error:\n "+ error +"\n" });
reject({ type: 'File', message: "resource '" + urlStr + "' gave this Error:\n " + error + "\n" });
return;
}
if (res && res.statusCode === 404) {
@@ -43,9 +46,9 @@ UrlFileManager.prototype.loadFile = function(filename, currentDirectory, options
return;
}
if (!body) {
logger.warn('Warning: Empty body (HTTP '+ res.statusCode + ') returned by "' + urlStr +'"');
logger.warn('Warning: Empty body (HTTP ' + res.statusCode + ') returned by "' + urlStr + '"');
}
fullfill({ contents: body, filename: urlStr });
fulfill({ contents: body, filename: urlStr });
});
});
};

View File

@@ -18,11 +18,11 @@ function formatError(ctx, options) {
return ctx.stack || ctx.message;
}
if (typeof(extract[0]) === 'string') {
if (typeof extract[0] === 'string') {
error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
}
if (typeof(extract[1]) === 'string') {
if (typeof extract[1] === 'string') {
var errorTxt = ctx.line + ' ';
if (extract[1]) {
errorTxt += extract[1].slice(0, ctx.column) +
@@ -32,14 +32,14 @@ function formatError(ctx, options) {
error.push(errorTxt);
}
if (typeof(extract[2]) === 'string') {
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 +
message += stylize(' in ', 'red') + ctx.filename +
stylize(' on line ' + ctx.line + ', column ' + (ctx.column + 1) + ':', 'grey');
}
@@ -113,8 +113,8 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) {
}
var j = file.lastIndexOf('/');
if(newFileInfo.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) {
var relativeSubDirectory = file.slice(0, j+1);
if (newFileInfo.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) {
var relativeSubDirectory = file.slice(0, j + 1);
newFileInfo.rootpath = newFileInfo.rootpath + relativeSubDirectory; // append (sub|sup) directory path of imported file
}
newFileInfo.currentDirectory = path;
@@ -135,7 +135,6 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) {
}
};
function writeFile(filename, content) {
var fstream = new java.io.FileWriter(filename);
var out = new java.io.BufferedWriter(fstream);
@@ -178,7 +177,7 @@ function writeFile(filename, content) {
var checkBooleanArg = function(arg) {
var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg);
if (!onOff) {
print(" unable to parse "+arg+" as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
print(" unable to parse " + arg + " as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
continueProcessing = false;
return false;
}
@@ -197,8 +196,12 @@ function writeFile(filename, content) {
}
match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
if (match) { arg = match[1]; } // was (?:=([^\s]*)), check!
else { return arg; }
if (match) {
arg = match[1];
}
else {
return arg;
}
switch (arg) {
case 'v':
@@ -306,10 +309,10 @@ function writeFile(filename, content) {
case 'source-map-output-map-file':
if (checkArgFunc(arg, match[2])) {
options.writeSourceMap = function(sourceMapContent) {
writeFile(match[2], sourceMapContent);
writeFile(match[2], sourceMapContent);
};
}
break;
break;
case 'rp':
case 'rootpath':
if (checkArgFunc(arg, match[2])) {
@@ -371,7 +374,6 @@ function writeFile(filename, content) {
options.sourceMapOutputFilename = options.sourceMap;
}
if (!name) {
console.log("lessc: no inout files");
console.log("");

View File

@@ -4,7 +4,7 @@ module.exports = contexts;
var copyFromOriginal = function copyFromOriginal(original, destination, propertiesToCopy) {
if (!original) { return; }
for(var i = 0; i < propertiesToCopy.length; i++) {
for (var i = 0; i < propertiesToCopy.length; i++) {
if (original.hasOwnProperty(propertiesToCopy[i])) {
destination[propertiesToCopy[i]] = original[propertiesToCopy[i]];
}
@@ -41,6 +41,7 @@ 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
@@ -49,13 +50,15 @@ var evalCopyProperties = [
'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
'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);
this.frames = frames || [];
this.importantScope = this.importantScope || [];
};
contexts.Eval.prototype.inParenthesis = function () {
@@ -74,7 +77,7 @@ contexts.Eval.prototype.isMathOn = function () {
};
contexts.Eval.prototype.isPathRelative = function (path) {
return !/^(?:[a-z-]+:|\/)/.test(path);
return !/^(?:[a-z-]+:|\/)/i.test(path);
};
contexts.Eval.prototype.normalizePath = function( path ) {
@@ -105,4 +108,3 @@ contexts.Eval.prototype.normalizePath = function( path ) {
};
//todo - do the same for the toCSS ?

View File

@@ -118,6 +118,7 @@ module.exports = {
'plum':'#dda0dd',
'powderblue':'#b0e0e6',
'purple':'#800080',
'rebeccapurple':'#663399',
'red':'#ff0000',
'rosybrown':'#bc8f8f',
'royalblue':'#4169e1',

View File

@@ -13,9 +13,9 @@ module.exports = {
'ms': 0.001
},
angle: {
'rad': 1/(2*Math.PI),
'deg': 1/360,
'grad': 1/400,
'rad': 1 / (2 * Math.PI),
'deg': 1 / 360,
'grad': 1 / 400,
'turn': 1
}
};

View File

@@ -2,7 +2,11 @@ var abstractFileManager = function() {
};
abstractFileManager.prototype.getPath = function (filename) {
var j = filename.lastIndexOf('/');
var j = filename.lastIndexOf('?');
if (j > 0) {
filename = filename.slice(0, j);
}
j = filename.lastIndexOf('/');
if (j < 0) {
j = filename.lastIndexOf('\\');
}
@@ -44,15 +48,15 @@ abstractFileManager.prototype.pathDiff = function pathDiff(url, baseUrl) {
return "";
}
max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
for(i = 0; i < max; i++) {
for (i = 0; i < max; i++) {
if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
}
baseUrlDirectories = baseUrlParts.directories.slice(i);
urlDirectories = urlParts.directories.slice(i);
for(i = 0; i < baseUrlDirectories.length-1; i++) {
for (i = 0; i < baseUrlDirectories.length - 1; i++) {
diff += "../";
}
for(i = 0; i < urlDirectories.length-1; i++) {
for (i = 0; i < urlDirectories.length - 1; i++) {
diff += urlDirectories[i] + "/";
}
return diff;
@@ -77,7 +81,7 @@ abstractFileManager.prototype.extractUrlParts = function extractUrlParts(url, ba
if (baseUrl && (!urlParts[1] || urlParts[2])) {
baseUrlParts = baseUrl.match(urlPartsRegex);
if (!baseUrlParts) {
throw new Error("Could not parse page url - '"+baseUrl+"'");
throw new Error("Could not parse page url - '" + baseUrl + "'");
}
urlParts[1] = urlParts[1] || baseUrlParts[1] || "";
if (!urlParts[2]) {
@@ -89,16 +93,16 @@ abstractFileManager.prototype.extractUrlParts = function extractUrlParts(url, ba
directories = urlParts[3].replace(/\\/g, "/").split("/");
// extract out . before .. so .. doesn't absorb a non-directory
for(i = 0; i < directories.length; i++) {
for (i = 0; i < directories.length; i++) {
if (directories[i] === ".") {
directories.splice(i, 1);
i -= 1;
}
}
for(i = 0; i < directories.length; i++) {
for (i = 0; i < directories.length; i++) {
if (directories[i] === ".." && i > 0) {
directories.splice(i-1, 2);
directories.splice(i - 1, 2);
i -= 2;
}
}

View File

@@ -1,3 +1,4 @@
var logger = require("../logger");
var environment = function(externalEnvironment, fileManagers) {
this.fileManagers = fileManagers || [];
externalEnvironment = externalEnvironment || {};
@@ -6,7 +7,7 @@ var environment = function(externalEnvironment, fileManagers) {
requiredFunctions = [],
functions = requiredFunctions.concat(optionalFunctions);
for(var i = 0; i < functions.length; i++) {
for (var i = 0; i < functions.length; i++) {
var propName = functions[i],
environmentFunc = externalEnvironment[propName];
if (environmentFunc) {
@@ -18,11 +19,19 @@ var environment = function(externalEnvironment, fileManagers) {
};
environment.prototype.getFileManager = function (filename, currentDirectory, options, environment, isSync) {
if (!filename) {
logger.warn("getFileManager called with no filename.. Please report this issue. continuing.");
}
if (currentDirectory == null) {
logger.warn("getFileManager called with null directory.. Please report this issue. continuing.");
}
var fileManagers = this.fileManagers;
if (options.pluginManager) {
fileManagers = [].concat(fileManagers).concat(options.pluginManager.getFileManagers());
}
for(var i = fileManagers.length - 1; i >= 0 ; i--) {
for (var i = fileManagers.length - 1; i >= 0 ; i--) {
var fileManager = fileManagers[i];
if (fileManager[isSync ? "supportsSync" : "supports"](filename, currentDirectory, options, environment)) {
return fileManager;

View File

@@ -73,7 +73,7 @@ module.exports = {
supports: function(filename, currentDirectory, options, environment) {
},
/**
* Loads a file asynchronously. Expects a promise that either rejects with an error or fullfills with an
* Loads a file asynchronously. Expects a promise that either rejects with an error or fulfills with an
* object containing
* { filename: - full resolved path to file
* contents: - the contents of the file, as a string }

View File

@@ -33,9 +33,9 @@ var colorBlendModeFunctions = {
},
overlay: function(cb, cs) {
cb *= 2;
return (cb <= 1)
? colorBlendModeFunctions.multiply(cb, cs)
: colorBlendModeFunctions.screen(cb - 1, cs);
return (cb <= 1) ?
colorBlendModeFunctions.multiply(cb, cs) :
colorBlendModeFunctions.screen(cb - 1, cs);
},
softlight: function(cb, cs) {
var d = 1, e = cb;

View File

@@ -14,7 +14,7 @@ function hsla(color) {
function number(n) {
if (n instanceof Dimension) {
return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
} else if (typeof(n) === 'number') {
} else if (typeof n === 'number') {
return n;
} else {
throw {
@@ -45,10 +45,18 @@ colorFunctions = {
hsla: function (h, s, l, a) {
function hue(h) {
h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; }
else if (h * 2 < 1) { return m2; }
else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; }
else { return m1; }
if (h * 6 < 1) {
return m1 + (m2 - m1) * h * 6;
}
else if (h * 2 < 1) {
return m2;
}
else if (h * 3 < 2) {
return m1 + (m2 - m1) * (2 / 3 - h) * 6;
}
else {
return m1;
}
}
h = (number(h) % 360) / 360;
@@ -57,9 +65,9 @@ colorFunctions = {
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
var m1 = l * 2 - m2;
return colorFunctions.rgba(hue(h + 1/3) * 255,
return colorFunctions.rgba(hue(h + 1 / 3) * 255,
hue(h) * 255,
hue(h - 1/3) * 255,
hue(h - 1 / 3) * 255,
a);
},
@@ -127,9 +135,9 @@ colorFunctions = {
},
luminance: function (color) {
var luminance =
(0.2126 * color.rgb[0] / 255)
+ (0.7152 * color.rgb[1] / 255)
+ (0.0722 * color.rgb[2] / 255);
(0.2126 * color.rgb[0] / 255) +
(0.7152 * color.rgb[1] / 255) +
(0.0722 * color.rgb[2] / 255);
return new Dimension(luminance * color.alpha * 100, '%');
},
@@ -259,7 +267,7 @@ colorFunctions = {
return new Color(c.value.slice(1));
}
if ((c instanceof Color) || (c = Color.fromKeyword(c.value))) {
c.keyword = undefined;
c.value = undefined;
return c;
}
throw {
@@ -268,7 +276,7 @@ colorFunctions = {
};
},
tint: function(color, amount) {
return colorFunctions.mix(colorFunctions.rgb(255,255,255), color, amount);
return colorFunctions.mix(colorFunctions.rgb(255, 255, 255), color, amount);
},
shade: function(color, amount) {
return colorFunctions.mix(colorFunctions.rgb(0, 0, 0), color, amount);

View File

@@ -1,5 +1,5 @@
module.exports = function(environment) {
var Anonymous = require("../tree/anonymous"),
var Quoted = require("../tree/quoted"),
URL = require("../tree/url"),
functionRegistry = require("./function-registry"),
fallback = function(functionThis, node) {
@@ -9,39 +9,44 @@ module.exports = function(environment) {
functionRegistry.add("data-uri", function(mimetypeNode, filePathNode) {
var mimetype = mimetypeNode.value;
var filePath = (filePathNode && filePathNode.value);
var fileManager = environment.getFileManager(filePath, this.context.currentFileInfo, this.context, environment, true);
if (!fileManager) {
return fallback(this, filePathNode || mimetypeNode);
if (!filePathNode) {
filePathNode = mimetypeNode;
mimetypeNode = null;
}
var useBase64 = false;
if (arguments.length < 2) {
filePath = mimetype;
}
var mimetype = mimetypeNode && mimetypeNode.value;
var filePath = filePathNode.value;
var currentFileInfo = this.currentFileInfo;
var currentDirectory = currentFileInfo.relativeUrls ?
currentFileInfo.currentDirectory : currentFileInfo.entryPath;
var fragmentStart = filePath.indexOf('#');
var fragment = '';
if (fragmentStart!==-1) {
if (fragmentStart !== -1) {
fragment = filePath.slice(fragmentStart);
filePath = filePath.slice(0, fragmentStart);
}
var currentDirectory = this.currentFileInfo.relativeUrls ?
this.currentFileInfo.currentDirectory : this.currentFileInfo.entryPath;
var fileManager = environment.getFileManager(filePath, currentDirectory, this.context, environment, true);
if (!fileManager) {
return fallback(this, filePathNode);
}
var useBase64 = false;
// detect the mimetype if not given
if (arguments.length < 2) {
if (!mimetypeNode) {
mimetype = environment.mimeLookup(filePath);
// use base 64 unless it's an ASCII or UTF-8 format
var charset = environment.charsetLookup(mimetype);
useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
if (mimetype === "image/svg+xml") {
useBase64 = false;
} else {
// use base 64 unless it's an ASCII or UTF-8 format
var charset = environment.charsetLookup(mimetype);
useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
}
if (useBase64) { mimetype += ';base64'; }
}
else {
@@ -50,28 +55,31 @@ module.exports = function(environment) {
var fileSync = fileManager.loadFileSync(filePath, currentDirectory, this.context, environment);
if (!fileSync.contents) {
logger.warn("Skipped data-uri embedding because file not found");
logger.warn("Skipped data-uri embedding of " + filePath + " because file not found");
return fallback(this, filePathNode || mimetypeNode);
}
var buf = fileSync.contents;
if (useBase64 && !environment.encodeBase64) {
return fallback(this, filePathNode);
}
// IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
buf = useBase64 ? environment.encodeBase64(buf) : encodeURIComponent(buf);
var uri = "data:" + mimetype + ',' + buf + fragment;
// IE8 cannot handle a data-uri larger than 32,768 characters. If this is exceeded
// and the --ieCompat flag is enabled, return a normal url() instead.
var DATA_URI_MAX_KB = 32,
fileSizeInKB = parseInt((buf.length / 1024), 10);
if (fileSizeInKB >= DATA_URI_MAX_KB) {
var DATA_URI_MAX = 32768;
if (uri.length >= DATA_URI_MAX) {
if (this.context.ieCompat !== false) {
logger.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
logger.warn("Skipped data-uri embedding of " + filePath + " because its size (" + uri.length +
" characters) exceeds IE8-safe " + DATA_URI_MAX + " characters!");
return fallback(this, filePathNode || mimetypeNode);
}
}
buf = useBase64 ? buf.toString('base64')
: encodeURIComponent(buf);
var uri = "\"data:" + mimetype + ',' + buf + fragment + "\"";
return new URL(new Anonymous(uri), this.index, this.currentFileInfo);
return new URL(new Quoted('"' + uri + '"', uri, false, this.index, this.currentFileInfo), this.index, this.currentFileInfo);
});
};

View File

@@ -2,16 +2,16 @@ var functionRegistry = require("./function-registry");
var functionCaller = function(name, context, index, currentFileInfo) {
this.name = name.toLowerCase();
this.function = functionRegistry.get(this.name);
this.func = functionRegistry.get(this.name);
this.index = index;
this.context = context;
this.currentFileInfo = currentFileInfo;
};
functionCaller.prototype.isValid = function() {
return Boolean(this.function);
return Boolean(this.func);
};
functionCaller.prototype.call = function(args) {
return this.function.apply(this, args);
return this.func.apply(this, args);
};
module.exports = functionCaller;

View File

@@ -34,7 +34,7 @@ for (var f in mathFunctions) {
}
mathFunctions.round = function (n, f) {
var fraction = typeof(f) === "undefined" ? 0 : f.value;
var fraction = typeof f === "undefined" ? 0 : f.value;
return _math(function(num) { return num.toFixed(fraction); }, null, n);
};

View File

@@ -14,7 +14,7 @@ var minMax = function (isMin, args) {
for (i = 0; i < args.length; i++) {
current = args[i];
if (!(current instanceof Dimension)) {
if(Array.isArray(args[i].value)) {
if (Array.isArray(args[i].value)) {
Array.prototype.push.apply(args, Array.prototype.slice.call(args[i].value));
}
continue;
@@ -25,7 +25,7 @@ var minMax = function (isMin, args) {
unitClone = unit !== "" && unitClone === undefined ? current.unit.toString() : unitClone;
j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit];
if (j === undefined) {
if(unitStatic !== undefined && unit !== unitStatic) {
if (unitStatic !== undefined && unit !== unitStatic) {
throw{ type: "Argument", message: "incompatible types" };
}
values[unit] = order.length;

View File

@@ -8,7 +8,9 @@ functionRegistry.addMultiple({
return new Anonymous(str instanceof JavaScript ? str.evaluated : str.value);
},
escape: function (str) {
return new Anonymous(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
return new Anonymous(
encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B")
.replace(/\(/g, "%28").replace(/\)/g, "%29"));
},
replace: function (string, pattern, replacement, flags) {
var result = string.value;

View File

@@ -1,14 +1,17 @@
module.exports = function(environment) {
var Dimension = require("../tree/dimension"),
Color = require("../tree/color"),
Anonymous = require("../tree/anonymous"),
Expression = require("../tree/expression"),
Quoted = require("../tree/quoted"),
URL = require("../tree/url"),
functionRegistry = require("./function-registry");
functionRegistry.add("svg-gradient", function(direction) {
function throwArgumentDescriptor() {
throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" };
throw { type: "Argument",
message: "svg-gradient expects direction, start_color [start_position], [color position,]...," +
" end_color [end_position]" };
}
if (arguments.length < 3) {
@@ -18,7 +21,6 @@ module.exports = function(environment) {
gradientDirectionSvg,
gradientType = "linear",
rectangleDimension = 'x="0" y="0" width="1" height="1"',
useBase64 = true,
renderEnv = {compress: false},
returner,
directionValue = direction.toCSS(renderEnv),
@@ -44,14 +46,15 @@ module.exports = function(environment) {
rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
break;
default:
throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" };
throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right'," +
" 'to bottom right', 'to top right' or 'ellipse at center'" };
}
returner = '<?xml version="1.0" ?>' +
'<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) {
if (stops[i].value) {
if (stops[i] instanceof Expression) {
color = stops[i].value[0];
position = stops[i].value[1];
} else {
@@ -59,7 +62,7 @@ module.exports = function(environment) {
position = undefined;
}
if (!(color instanceof Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof Dimension))) {
if (!(color instanceof Color) || (!((i === 0 || i + 1 === stops.length) && position === undefined) && !(position instanceof Dimension))) {
throwArgumentDescriptor();
}
positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%";
@@ -69,15 +72,9 @@ module.exports = function(environment) {
returner += '</' + gradientType + 'Gradient>' +
'<rect ' + rectangleDimension + ' fill="url(#gradient)" /></svg>';
if (useBase64) {
try {
returner = environment.encodeBase64(returner);
} catch(e) {
useBase64 = false;
}
}
returner = encodeURIComponent(returner);
returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'";
return new URL(new Anonymous(returner), this.index, this.currentFileInfo);
returner = "data:image/svg+xml," + returner;
return new URL(new Quoted("'" + returner + "'", returner, false, this.index, this.currentFileInfo), this.index, this.currentFileInfo);
});
};

View File

@@ -1,4 +1,5 @@
var Keyword = require("../tree/keyword"),
DetachedRuleset = require("../tree/detached-ruleset"),
Dimension = require("../tree/dimension"),
Color = require("../tree/color"),
Quoted = require("../tree/quoted"),
@@ -8,12 +9,22 @@ var Keyword = require("../tree/keyword"),
functionRegistry = require("./function-registry");
var isa = function (n, Type) {
return (n instanceof Type) ? Keyword.True : Keyword.False;
return (n instanceof Type) ? Keyword.True : Keyword.False;
},
isunit = function (n, unit) {
return (n instanceof Dimension) && n.unit.is(unit.value || unit) ? Keyword.True : Keyword.False;
if (unit === undefined) {
throw { type: "Argument", message: "missing the required second argument to isunit." };
}
unit = typeof unit.value === "string" ? unit.value : unit;
if (typeof unit !== "string") {
throw { type: "Argument", message: "Second argument to isunit should be a unit or a string." };
}
return (n instanceof Dimension) && n.unit.is(unit) ? Keyword.True : Keyword.False;
};
functionRegistry.addMultiple({
isruleset: function (n) {
return isa(n, DetachedRuleset);
},
iscolor: function (n) {
return isa(n, Color);
},
@@ -40,8 +51,10 @@ functionRegistry.addMultiple({
},
isunit: isunit,
unit: function (val, unit) {
if(!(val instanceof Dimension)) {
throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof Operation ? ". Have you forgotten parenthesis?" : "") };
if (!(val instanceof Dimension)) {
throw { type: "Argument",
message: "the first argument to unit must be a number" +
(val instanceof Operation ? ". Have you forgotten parenthesis?" : "") };
}
if (unit) {
if (unit instanceof Keyword) {

View File

@@ -16,13 +16,13 @@ module.exports = function(environment) {
this.rootFilename = rootFileInfo.filename;
this.paths = context.paths || []; // Search paths, when importing
this.contents = {}; // map - filename to contents of all the files
this.contentsIgnoredChars = {}; // map - filename to lines at the begining of each file to ignore
this.contentsIgnoredChars = {}; // map - filename to lines at the beginning of each file to ignore
this.mime = context.mime;
this.error = null;
this.context = context;
// Deprecated? Unused outside of here, could be useful.
this.queue = []; // Files which haven't been imported yet
this.files = []; // Holds the imported parse trees.
this.files = {}; // Holds the imported parse trees.
};
/**
* Add an import to be imported
@@ -40,12 +40,14 @@ module.exports = function(environment) {
importManager.queue.splice(importManager.queue.indexOf(path), 1); // Remove the path from the queue
var importedEqualsRoot = fullPath === importManager.rootFilename;
importManager.files[fullPath] = root;
if (e && !importManager.error) { importManager.error = e; }
callback(e, root, importedEqualsRoot, fullPath);
if (importOptions.optional && e) {
callback(null, {rules:[]}, false, null);
}
else {
importManager.files[fullPath] = root;
if (e && !importManager.error) { importManager.error = e; }
callback(e, root, importedEqualsRoot, fullPath);
}
};
var newFileInfo = {
@@ -66,48 +68,59 @@ module.exports = function(environment) {
path = fileManager.tryAppendLessExtension(path);
}
fileManager.loadFile(path, currentFileInfo.currentDirectory, this.context, environment)
.then(function loadFileCallback(loadedFile) {
var resolvedFilename = loadedFile.filename,
contents = loadedFile.contents;
var loadFileCallback = function(loadedFile) {
var resolvedFilename = loadedFile.filename,
contents = loadedFile.contents.replace(/^\uFEFF/, '');
// Pass on an updated rootpath if path of imported file is relative and file
// is in a (sub|sup) directory
//
// Examples:
// - If path of imported file is 'module/nav/nav.less' and rootpath is 'less/',
// then rootpath should become 'less/module/nav/'
// - If path of imported file is '../mixins.less' and rootpath is 'less/',
// then rootpath should become 'less/../'
newFileInfo.currentDirectory = fileManager.getPath(resolvedFilename);
if(newFileInfo.relativeUrls) {
newFileInfo.rootpath = fileManager.join((importManager.context.rootpath || ""), fileManager.pathDiff(newFileInfo.currentDirectory, newFileInfo.entryPath));
if (!fileManager.isPathAbsolute(newFileInfo.rootpath) && fileManager.alwaysMakePathsAbsolute()) {
newFileInfo.rootpath = fileManager.join(newFileInfo.entryPath, newFileInfo.rootpath);
}
// Pass on an updated rootpath if path of imported file is relative and file
// is in a (sub|sup) directory
//
// Examples:
// - If path of imported file is 'module/nav/nav.less' and rootpath is 'less/',
// then rootpath should become 'less/module/nav/'
// - If path of imported file is '../mixins.less' and rootpath is 'less/',
// then rootpath should become 'less/../'
newFileInfo.currentDirectory = fileManager.getPath(resolvedFilename);
if (newFileInfo.relativeUrls) {
newFileInfo.rootpath = fileManager.join(
(importManager.context.rootpath || ""),
fileManager.pathDiff(newFileInfo.currentDirectory, newFileInfo.entryPath));
if (!fileManager.isPathAbsolute(newFileInfo.rootpath) && fileManager.alwaysMakePathsAbsolute()) {
newFileInfo.rootpath = fileManager.join(newFileInfo.entryPath, newFileInfo.rootpath);
}
newFileInfo.filename = resolvedFilename;
}
newFileInfo.filename = resolvedFilename;
var newEnv = new contexts.Parse(importManager.context);
var newEnv = new contexts.Parse(importManager.context);
newEnv.processImports = false;
importManager.contents[resolvedFilename] = contents;
newEnv.processImports = false;
importManager.contents[resolvedFilename] = contents;
if (currentFileInfo.reference || importOptions.reference) {
newFileInfo.reference = true;
}
if (currentFileInfo.reference || importOptions.reference) {
newFileInfo.reference = true;
}
if (importOptions.inline) {
fileParsedFunc(null, contents, resolvedFilename);
} else {
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (e, root) {
fileParsedFunc(e, root, resolvedFilename);
});
}
},
function(error) {
fileParsedFunc(error);
});
if (importOptions.inline) {
fileParsedFunc(null, contents, resolvedFilename);
} else {
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (e, root) {
fileParsedFunc(e, root, resolvedFilename);
});
}
};
var promise = fileManager.loadFile(path, currentFileInfo.currentDirectory, this.context, environment,
function(err, loadedFile) {
if (err) {
fileParsedFunc(err);
} else {
loadFileCallback(loadedFile);
}
});
if (promise) {
promise.then(loadFileCallback, fileParsedFunc);
}
};
return ImportManager;
};

View File

@@ -2,7 +2,7 @@ module.exports = function(environment, fileManagers) {
var SourceMapOutput, SourceMapBuilder, ParseTree, ImportManager, Environment;
var less = {
version: [2, 0, "0-b1"],
version: [2, 3, 1],
data: require('./data'),
tree: require('./tree'),
Environment: (Environment = require("./environment/environment")),
@@ -13,10 +13,11 @@ module.exports = function(environment, fileManagers) {
functions: require('./functions')(environment),
contexts: require("./contexts"),
SourceMapOutput: (SourceMapOutput = require('./source-map-output')(environment)),
SourceMapBuilder: (SourceMapBuilder = require('./source-map-builder')(SourceMapOutput)),
SourceMapBuilder: (SourceMapBuilder = require('./source-map-builder')(SourceMapOutput, environment)),
ParseTree: (ParseTree = require('./parse-tree')(SourceMapBuilder)),
ImportManager: (ImportManager = require('./import-manager')(environment)),
render: require("./render")(environment, ParseTree, ImportManager),
parse: require("./parse")(environment, ParseTree, ImportManager),
LessError: require('./less-error'),
transformTree: require('./transform-tree'),
utils: require('./utils'),

View File

@@ -17,7 +17,7 @@ var LessError = module.exports = function LessError(e, importManager, currentFil
this.type = e.type || 'Syntax';
this.filename = filename;
this.index = e.index;
this.line = typeof(line) === 'number' ? line + 1 : null;
this.line = typeof line === 'number' ? line + 1 : null;
this.callLine = callLine + 1;
this.callExtract = lines[callLine];
this.column = col;
@@ -31,5 +31,12 @@ var LessError = module.exports = function LessError(e, importManager, currentFil
this.stack = e.stack;
};
LessError.prototype = Object.create(Error.prototype);
if (typeof Object.create === 'undefined') {
var F = function () {};
F.prototype = Error.prototype;
LessError.prototype = new F();
} else {
LessError.prototype = Object.create(Error.prototype);
}
LessError.prototype.constructor = LessError;

View File

@@ -15,7 +15,7 @@ module.exports = {
this._listeners.push(listener);
},
removeListener: function(listener) {
for(var i = 0; i < this._listeners.length; i++) {
for (var i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
@@ -23,7 +23,7 @@ module.exports = {
}
},
_fireEvent: function(type, msg) {
for(var i = 0; i < this._listeners.length; i++) {
for (var i = 0; i < this._listeners.length; i++) {
var logFunction = this._listeners[i][type];
if (logFunction) {
logFunction(msg);

View File

@@ -1,47 +1,60 @@
var LessError = require('./less-error'),
transformTree = require("./transform-tree");
transformTree = require("./transform-tree"),
logger = require("./logger");
module.exports = function(SourceMapBuilder) {
var ParseTree = function(root, imports) {
this.root = root;
this.imports = imports;
};
var ParseTree = function(root, imports) {
this.root = root;
this.imports = imports;
};
ParseTree.prototype.toCSS = function(options) {
var evaldRoot, result = {}, sourceMapBuilder;
try {
evaldRoot = transformTree(this.root, options);
} catch (e) {
throw new LessError(e, this.imports);
}
ParseTree.prototype.toCSS = function(options) {
var evaldRoot, result = {}, sourceMapBuilder;
try {
evaldRoot = transformTree(this.root, options);
} catch (e) {
throw new LessError(e, this.imports);
}
try {
var toCSSOptions = {
compress: Boolean(options.compress),
dumpLineNumbers: options.dumpLineNumbers,
strictUnits: Boolean(options.strictUnits),
numPrecision: 8};
try {
var compress = Boolean(options.compress);
if (compress) {
logger.warn("The compress option has been deprecated. We recommend you use a dedicated css minifier, for instance see less-plugin-clean-css.");
}
var toCSSOptions = {
compress: compress,
dumpLineNumbers: options.dumpLineNumbers,
strictUnits: Boolean(options.strictUnits),
numPrecision: 8};
if (options.sourceMap) {
sourceMapBuilder = new SourceMapBuilder(options.sourceMap);
result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);
} else {
result.css = evaldRoot.toCSS(toCSSOptions);
}
} catch (e) {
throw new LessError(e, this.imports);
}
if (options.pluginManager) {
var postProcessors = options.pluginManager.getPostProcessors();
for (var i = 0; i < postProcessors.length; i++) {
result.css = postProcessors[i].process(result.css, { sourceMap: sourceMapBuilder, options: options, imports: this.imports });
}
}
if (options.sourceMap) {
sourceMapBuilder = new SourceMapBuilder(options.sourceMap);
result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);
} else {
result.css = evaldRoot.toCSS(toCSSOptions);
result.map = sourceMapBuilder.getExternalSourceMap();
}
} catch (e) {
throw new LessError(e, this.imports);
}
if (options.pluginManager) {
var postProcessors = options.pluginManager.getPostProcessors();
for(var i = 0; i < postProcessors.length; i++) {
result.css = postProcessors[i].process(result.css, { sourceMap: sourceMapBuilder, options: options, imports: this.imports });
result.imports = [];
for (var file in this.imports.files) {
if (this.imports.files.hasOwnProperty(file) && file !== this.imports.rootFilename) {
result.imports.push(file);
}
}
}
if (options.sourceMap) {
result.map = sourceMapBuilder.getExternalSourceMap();
}
return result;
};
return ParseTree;
return result;
};
return ParseTree;
};

68
lib/less/parse.js Normal file
View File

@@ -0,0 +1,68 @@
var PromiseConstructor,
contexts = require("./contexts"),
Parser = require('./parser/parser'),
PluginManager = require('./plugin-manager');
module.exports = function(environment, ParseTree, ImportManager) {
var parse = function (input, options, callback) {
options = options || {};
if (typeof options === 'function') {
callback = options;
options = {};
}
if (!callback) {
if (!PromiseConstructor) {
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
}
var self = this;
return new PromiseConstructor(function (resolve, reject) {
parse.call(self, input, options, function(err, output) {
if (err) {
reject(err);
} else {
resolve(output);
}
});
});
} else {
var context,
rootFileInfo,
pluginManager = new PluginManager(this);
pluginManager.addPlugins(options.plugins);
options.pluginManager = pluginManager;
context = new contexts.Parse(options);
if (options.rootFileInfo) {
rootFileInfo = options.rootFileInfo;
} else {
var filename = options.filename || "input";
var entryPath = filename.replace(/[^\/\\]*$/, "");
rootFileInfo = {
filename: filename,
relativeUrls: context.relativeUrls,
rootpath: context.rootpath || "",
currentDirectory: entryPath,
entryPath: entryPath,
rootFilename: filename
};
// add in a missing trailing slash
if (rootFileInfo.rootpath && rootFileInfo.rootpath.slice(-1) !== "/") {
rootFileInfo.rootpath += "/";
}
}
var imports = new ImportManager(context, rootFileInfo);
new Parser(context, imports, rootFileInfo)
.parse(input, function (e, root) {
if (e) { return callback(e); }
callback(null, root, imports, options);
}, options);
}
};
return parse;
};

View File

@@ -16,7 +16,8 @@ module.exports = function() {
saveStack.push( { current: current, i: parserInput.i, j: j });
};
parserInput.restore = function(possibleErrorMessage) {
if (parserInput.i > furthest) {
if (parserInput.i > furthest || (parserInput.i === furthest && possibleErrorMessage && !furthestPossibleErrorMessage)) {
furthest = parserInput.i;
furthestPossibleErrorMessage = possibleErrorMessage;
}
@@ -72,7 +73,7 @@ module.exports = function() {
//
skipWhitespace(length);
if(typeof(match) === 'string') {
if (typeof match === 'string') {
return match;
} else {
return match.length === 1 ? match[0] : match;
@@ -91,7 +92,7 @@ module.exports = function() {
}
skipWhitespace(m[0].length);
if(typeof m === "string") {
if (typeof m === "string") {
return m;
}
@@ -132,7 +133,7 @@ module.exports = function() {
c = inp.charCodeAt(parserInput.i);
if (parserInput.autoCommentAbsorb && c === CHARCODE_FORWARD_SLASH) {
nextChar = inp[parserInput.i + 1];
nextChar = inp.charAt(parserInput.i + 1);
if (nextChar === '/') {
comment = {index: parserInput.i, isLineComment: true};
var nextNewLine = inp.indexOf("\n", parserInput.i + 1);
@@ -184,7 +185,7 @@ module.exports = function() {
// Same as $(), but don't change the state of the parser,
// just return the match.
parserInput.peek = function(tok) {
if (typeof(tok) === 'string') {
if (typeof tok === 'string') {
return input.charAt(parserInput.i) === tok;
} else {
return tok.test(current);
@@ -238,7 +239,7 @@ module.exports = function() {
parserInput.end = function() {
var message,
isFinished = parserInput.i >= input.length - 1;
isFinished = parserInput.i >= input.length;
if (parserInput.i < furthest) {
message = furthestPossibleErrorMessage;

View File

@@ -47,7 +47,7 @@ var Parser = function Parser(context, imports, fileInfo) {
if (result) {
return result;
}
error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + parserInput.currentChar() + "'"
error(msg || (typeof arg === 'string' ? "expected '" + arg + "' got '" + parserInput.currentChar() + "'"
: "unexpected token"));
}
@@ -111,7 +111,7 @@ var Parser = function Parser(context, imports, fileInfo) {
ignored[fileInfo.filename] += preText.length;
}
str = str.replace(/\r\n/g, '\n');
str = str.replace(/\r\n?/g, '\n');
// Remove potential UTF Byte Order Mark
str = preText + str.replace(/^\uFEFF/, '') + modifyVars;
imports.contents[fileInfo.filename] = str;
@@ -213,7 +213,7 @@ var Parser = function Parser(context, imports, fileInfo) {
// 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]]]))
// Rule ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
// Ruleset (Selector [Element '>', '.child'], [...])
// ])
//
@@ -240,17 +240,28 @@ var Parser = function Parser(context, imports, fileInfo) {
primary: function () {
var mixin = this.mixin, root = [], node;
while (!parserInput.finished)
while (true)
{
while(true) {
while (true) {
node = this.comment();
if (!node) { break; }
root.push(node);
}
// always process comments before deciding if finished
if (parserInput.finished) {
break;
}
if (parserInput.peek('}')) {
break;
}
node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() ||
node = this.extendRule();
if (node) {
root = root.concat(node);
continue;
}
node = mixin.definition() || this.rule() || this.ruleset() ||
mixin.call() || this.rulesetCall() || this.directive();
if (node) {
root.push(node);
@@ -330,7 +341,7 @@ var Parser = function Parser(context, imports, fileInfo) {
if (nameLC === 'alpha') {
alpha = parsers.alpha();
if(alpha) {
if (alpha) {
return alpha;
}
}
@@ -452,7 +463,9 @@ var Parser = function Parser(context, imports, fileInfo) {
var rgb;
if (parserInput.currentChar() === '#' && (rgb = parserInput.$re(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) {
var colorCandidateString = rgb.input.match(/^#([\w]+).*/); // strip colons, brackets, whitespaces and other characters that should not definitely be part of color string
// strip colons, brackets, whitespaces and other characters that should not
// definitely be part of color string
var colorCandidateString = rgb.input.match(/^#([\w]+).*/);
colorCandidateString = colorCandidateString[1];
if (!colorCandidateString.match(/^[A-Fa-f0-9]+$/)) { // verify if candidate consists only of allowed HEX characters
error("Invalid HEX color code");
@@ -543,17 +556,27 @@ var Parser = function Parser(context, imports, fileInfo) {
elements = null;
while (! (option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
e = this.element();
if (!e) { break; }
if (elements) { elements.push(e); } else { elements = [ e ]; }
if (!e) {
break;
}
if (elements) {
elements.push(e);
} else {
elements = [ e ];
}
}
option = option && option[1];
if (!elements)
if (!elements) {
error("Missing target selector for :extend().");
}
extend = new(tree.Extend)(new(tree.Selector)(elements), option, index);
if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
} while(parserInput.$char(","));
if (extendList) {
extendList.push(extend);
} else {
extendList = [ extend ];
}
} while (parserInput.$char(","));
expect(/^\)/);
@@ -601,7 +624,11 @@ var Parser = function Parser(context, imports, fileInfo) {
break;
}
elem = new(tree.Element)(c, e, elemIndex, fileInfo);
if (elements) { elements.push(elem); } else { elements = [ elem ]; }
if (elements) {
elements.push(elem);
} else {
elements = [ elem ];
}
c = parserInput.$char('>');
}
@@ -627,7 +654,7 @@ var Parser = function Parser(context, imports, fileInfo) {
var entities = parsers.entities,
returner = { args:null, variadic: false },
expressions = [], argsSemiColon = [], argsComma = [],
isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg;
isSemiColonSeparated, expressionContainsNamed, name, nameLoop, value, arg;
parserInput.save();
@@ -638,10 +665,10 @@ var Parser = function Parser(context, imports, fileInfo) {
parserInput.commentStore.length = 0;
if (parserInput.currentChar() === '.' && parserInput.$re(/^\.{3}/)) {
returner.variadic = true;
if (parserInput.$char(";") && !isSemiColonSeperated) {
isSemiColonSeperated = true;
if (parserInput.$char(";") && !isSemiColonSeparated) {
isSemiColonSeparated = true;
}
(isSemiColonSeperated ? argsSemiColon : argsComma)
(isSemiColonSeparated ? argsSemiColon : argsComma)
.push({ variadic: true });
break;
}
@@ -671,7 +698,7 @@ var Parser = function Parser(context, imports, fileInfo) {
if (val && val instanceof tree.Variable) {
if (parserInput.$char(':')) {
if (expressions.length > 0) {
if (isSemiColonSeperated) {
if (isSemiColonSeparated) {
error("Cannot mix ; and , as delimiter types");
}
expressionContainsNamed = true;
@@ -694,10 +721,10 @@ var Parser = function Parser(context, imports, fileInfo) {
nameLoop = (name = val.name);
} else if (!isCall && parserInput.$re(/^\.{3}/)) {
returner.variadic = true;
if (parserInput.$char(";") && !isSemiColonSeperated) {
isSemiColonSeperated = true;
if (parserInput.$char(";") && !isSemiColonSeparated) {
isSemiColonSeparated = true;
}
(isSemiColonSeperated ? argsSemiColon : argsComma)
(isSemiColonSeparated ? argsSemiColon : argsComma)
.push({ name: arg.name, variadic: true });
break;
} else if (!isCall) {
@@ -716,13 +743,13 @@ var Parser = function Parser(context, imports, fileInfo) {
continue;
}
if (parserInput.$char(';') || isSemiColonSeperated) {
if (parserInput.$char(';') || isSemiColonSeparated) {
if (expressionContainsNamed) {
error("Cannot mix ; and , as delimiter types");
}
isSemiColonSeperated = true;
isSemiColonSeparated = true;
if (expressions.length > 1) {
value = new(tree.Value)(expressions);
@@ -736,7 +763,7 @@ var Parser = function Parser(context, imports, fileInfo) {
}
parserInput.forget();
returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
returner.args = isSemiColonSeparated ? argsSemiColon : argsComma;
return returner;
},
//
@@ -859,8 +886,10 @@ var Parser = function Parser(context, imports, fileInfo) {
c = this.combinator();
e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) || parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
parserInput.$char('*') || parserInput.$char('&') || this.attribute() || parserInput.$re(/^\([^()@]+\)/) || parserInput.$re(/^[\.#](?=@)/) ||
e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) ||
parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
parserInput.$char('*') || parserInput.$char('&') || this.attribute() ||
parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[\.#:](?=@)/) ||
this.entities.variableCurly();
if (! e) {
@@ -932,19 +961,27 @@ var Parser = function Parser(context, imports, fileInfo) {
// Selectors are made out of one or more Elements, see above.
//
selector: function (isLess) {
var index = parserInput.i, elements, extendList, c, e, extend, when, condition;
var index = parserInput.i, elements, extendList, c, e, allExtends, when, condition;
while ((isLess && (extend = this.extend())) || (isLess && (when = parserInput.$re(/^when/))) || (e = this.element())) {
while ((isLess && (extendList = this.extend())) || (isLess && (when = parserInput.$re(/^when/))) || (e = this.element())) {
if (when) {
condition = expect(this.conditions, 'expected condition');
} else if (condition) {
error("CSS guard can only be used at the end of selector");
} else if (extend) {
if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
} else if (extendList) {
if (allExtends) {
allExtends = allExtends.concat(extendList);
} else {
allExtends = extendList;
}
} else {
if (extendList) { error("Extend can only be used at the end of selector"); }
if (allExtends) { error("Extend can only be used at the end of selector"); }
c = parserInput.currentChar();
if (elements) { elements.push(e); } else { elements = [ e ]; }
if (elements) {
elements.push(e);
} else {
elements = [ e ];
}
e = null;
}
if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
@@ -952,8 +989,8 @@ var Parser = function Parser(context, imports, fileInfo) {
}
}
if (elements) { return new(tree.Selector)(elements, extendList, condition, index, fileInfo); }
if (extendList) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
if (elements) { return new(tree.Selector)(elements, allExtends, condition, index, 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; }
@@ -1019,7 +1056,11 @@ var Parser = function Parser(context, imports, fileInfo) {
if (!s) {
break;
}
if (selectors) { selectors.push(s); } else { selectors = [ s ]; }
if (selectors) {
selectors.push(s);
} else {
selectors = [ s ];
}
parserInput.commentStore.length = 0;
if (s.condition && selectors.length > 1) {
error("Guards are only currently allowed on a single selector.");
@@ -1156,11 +1197,11 @@ var Parser = function Parser(context, imports, fileInfo) {
case "css":
optionName = "less";
value = false;
break;
break;
case "once":
optionName = "multiple";
value = false;
break;
break;
}
options[optionName] = value;
if (! parserInput.$char(',')) { break; }
@@ -1171,7 +1212,7 @@ var Parser = function Parser(context, imports, fileInfo) {
},
importOption: function() {
var opt = parserInput.$re(/^(less|css|multiple|once|inline|reference)/);
var opt = parserInput.$re(/^(less|css|multiple|once|inline|reference|optional)/);
if (opt) {
return opt[1];
}
@@ -1299,6 +1340,10 @@ var Parser = function Parser(context, imports, fileInfo) {
hasBlock = true;
break;
*/
case "@counter-style":
hasIdentifier = true;
hasBlock = true;
break;
case "@charset":
hasIdentifier = true;
hasBlock = false;
@@ -1367,7 +1412,7 @@ var Parser = function Parser(context, imports, fileInfo) {
expressions.push(e);
if (! parserInput.$char(',')) { break; }
}
} while(e);
} while (e);
if (expressions.length > 0) {
return new(tree.Value)(expressions);
@@ -1381,15 +1426,19 @@ var Parser = function Parser(context, imports, fileInfo) {
sub: function () {
var a, e;
parserInput.save();
if (parserInput.$char('(')) {
a = this.addition();
if (a) {
if (a && parserInput.$char(')')) {
parserInput.forget();
e = new(tree.Expression)([a]);
expectChar(')');
e.parens = true;
return e;
}
parserInput.restore("Expected ')'");
return;
}
parserInput.restore();
},
multiplication: function () {
var m, a, op, operation, isSpaced;
@@ -1596,8 +1645,8 @@ Parser.serializeVars = function(vars) {
for (var name in vars) {
if (Object.hasOwnProperty.call(vars, name)) {
var value = vars[name];
s += ((name[0] === '@') ? '' : '@') + name +': '+ value +
((('' + value).slice(-1) === ';') ? '' : ';');
s += ((name[0] === '@') ? '' : '@') + name + ': ' + value +
((String(value).slice(-1) === ';') ? '' : ';');
}
}

View File

@@ -15,7 +15,7 @@ var PluginManager = function(less) {
*/
PluginManager.prototype.addPlugins = function(plugins) {
if (plugins) {
for(var i = 0;i < plugins.length; i++) {
for (var i = 0; i < plugins.length; i++) {
this.addPlugin(plugins[i]);
}
}
@@ -57,7 +57,7 @@ PluginManager.prototype.addPreProcessor = function(preProcessor, priority) {
*/
PluginManager.prototype.addPostProcessor = function(postProcessor, priority) {
var indexToInsertAt;
for(indexToInsertAt = 0; indexToInsertAt < this.postProcessors.length; indexToInsertAt++) {
for (indexToInsertAt = 0; indexToInsertAt < this.postProcessors.length; indexToInsertAt++) {
if (this.postProcessors[indexToInsertAt].priority >= priority) {
break;
}
@@ -90,7 +90,7 @@ PluginManager.prototype.getPreProcessors = function() {
*/
PluginManager.prototype.getPostProcessors = function() {
var postProcessors = [];
for(var i = 0; i < this.postProcessors.length; i++) {
for (var i = 0; i < this.postProcessors.length; i++) {
postProcessors.push(this.postProcessors[i].postProcessor);
}
return postProcessors;

View File

@@ -1,65 +1,41 @@
var PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
contexts = require("./contexts"),
Parser = require('./parser/parser'),
PluginManager = require('./plugin-manager');
var PromiseConstructor;
module.exports = function(environment, ParseTree, ImportManager) {
var render = function (input, options, callback) {
options = options || {};
if (typeof(options) === 'function') {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (callback) {
render.call(this, input, options)
.then(function(css) {
callback(null, css);
},
function(error) {
callback(error);
});
} else {
var context,
rootFileInfo,
pluginManager = new PluginManager(this);
pluginManager.addPlugins(options.plugins);
options.pluginManager = pluginManager;
context = new contexts.Parse(options);
if (options.rootFileInfo) {
rootFileInfo = options.rootFileInfo;
} else {
var filename = options.filename || "input";
var entryPath = filename.replace(/[^\/\\]*$/, "");
rootFileInfo = {
filename: filename,
relativeUrls: context.relativeUrls,
rootpath: context.rootpath || "",
currentDirectory: entryPath,
entryPath: entryPath,
rootFilename: filename
};
if (!callback) {
if (!PromiseConstructor) {
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
}
var imports = new ImportManager(context, rootFileInfo);
var parser = new Parser(context, imports, rootFileInfo);
var self = this;
return new PromiseConstructor(function (resolve, reject) {
parser.parse(input, function (e, root) {
if (e) { return reject(e); }
try {
var parseTree = new ParseTree(root, imports);
var result = parseTree.toCSS(options);
resolve(result);
render.call(self, input, options, function(err, output) {
if (err) {
reject(err);
} else {
resolve(output);
}
catch (err) { reject( err); }
}, options);
});
});
} else {
this.parse(input, options, function(err, root, imports, options) {
if (err) { return callback(err); }
var result;
try {
var parseTree = new ParseTree(root, imports);
result = parseTree.toCSS(options);
}
catch (err) { return callback(err); }
callback(null, result);
});
}
};
return render;
};

View File

@@ -1,4 +1,4 @@
module.exports = function (SourceMapOutput) {
module.exports = function (SourceMapOutput, environment) {
var SourceMapBuilder = function (options) {
this.options = options;
@@ -26,7 +26,19 @@ module.exports = function (SourceMapOutput) {
if (this.options.sourceMapInputFilename) {
this.sourceMapInputFilename = sourceMapOutput.normalizeFilename(this.options.sourceMapInputFilename);
}
return css;
return css + this.getCSSAppendage();
};
SourceMapBuilder.prototype.getCSSAppendage = function() {
var sourceMapURL = this.sourceMapURL;
if (this.options.sourceMapFileInline) {
sourceMapURL = "data:application/json;base64," + environment.encodeBase64(this.sourceMap);
}
if (sourceMapURL) {
return "/*# sourceMappingURL=" + sourceMapURL + " */";
}
return "";
};
SourceMapBuilder.prototype.getExternalSourceMap = function() {

View File

@@ -15,7 +15,7 @@ module.exports = function (environment) {
}
if (options.sourceMapRootpath) {
this._sourceMapRootpath = options.sourceMapRootpath.replace(/\\/g, '/');
if (this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') {
if (this._sourceMapRootpath.charAt(this._sourceMapRootpath.length - 1) !== '/') {
this._sourceMapRootpath += '/';
}
} else {
@@ -23,7 +23,6 @@ module.exports = function (environment) {
}
this._outputSourceFiles = options.outputSourceFiles;
this._sourceMapGeneratorConstructor = environment.getSourceMapGenerator();
this._sourceMapFileInline = options.sourceMapFileInline;
this._lineNumber = 0;
this._column = 0;
@@ -35,7 +34,7 @@ module.exports = function (environment) {
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);
filename = filename.substring(1);
}
}
return (this._sourceMapRootpath || "") + filename;
@@ -67,11 +66,11 @@ module.exports = function (environment) {
}
inputSource = inputSource.substring(0, index);
sourceLines = inputSource.split("\n");
sourceColumns = sourceLines[sourceLines.length-1];
sourceColumns = sourceLines[sourceLines.length - 1];
}
lines = chunk.split("\n");
columns = lines[lines.length-1];
columns = lines[lines.length - 1];
if (fileInfo) {
if (!mapLines) {
@@ -79,7 +78,7 @@ module.exports = function (environment) {
original: { line: sourceLines.length, column: sourceColumns.length},
source: this.normalizeFilename(fileInfo.filename)});
} else {
for(i = 0; i < lines.length; i++) {
for (i = 0; i < lines.length; i++) {
this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + i + 1, column: i === 0 ? this._column : 0},
original: { line: sourceLines.length + i, column: i === 0 ? sourceColumns.length : 0},
source: this.normalizeFilename(fileInfo.filename)});
@@ -105,7 +104,7 @@ module.exports = function (environment) {
this._sourceMapGenerator = new this._sourceMapGeneratorConstructor({ file: this._outputFilename, sourceRoot: null });
if (this._outputSourceFiles) {
for(var filename in this._contentsMap) {
for (var filename in this._contentsMap) {
if (this._contentsMap.hasOwnProperty(filename))
{
var source = this._contentsMap[filename];
@@ -130,15 +129,7 @@ module.exports = function (environment) {
}
this.sourceMapURL = sourceMapURL;
if (!this._sourceMapFileInline) {
this.sourceMap = sourceMapContent;
} else {
sourceMapURL = "data:application/json;base64," + environment.encodeBase64(sourceMapContent);
}
if (sourceMapURL) {
this._css.push("/*# sourceMappingURL=" + sourceMapURL + " */");
}
this.sourceMap = sourceMapContent;
}
return this._css.join('');

View File

@@ -21,7 +21,7 @@ module.exports = function(root, options) {
// ])
// )
//
if (typeof(variables) === 'object' && !Array.isArray(variables)) {
if (typeof variables === 'object' && !Array.isArray(variables)) {
variables = Object.keys(variables).map(function (k) {
var value = variables[k];
@@ -45,7 +45,7 @@ module.exports = function(root, options) {
if (options.pluginManager) {
var pluginVisitors = options.pluginManager.getVisitors();
for(i =0; i < pluginVisitors.length; i++) {
for (i = 0; i < pluginVisitors.length; i++) {
var pluginVisitor = pluginVisitors[i];
if (pluginVisitor.isPreEvalVisitor) {
preEvalVisitors.push(pluginVisitor);
@@ -59,13 +59,13 @@ module.exports = function(root, options) {
}
}
for(i = 0; i < preEvalVisitors.length; i++) {
for (i = 0; i < preEvalVisitors.length; i++) {
preEvalVisitors[i].run(root);
}
evaldRoot = root.eval(evalEnv);
for(i = 0; i < visitors.length; i++) {
for (i = 0; i < visitors.length; i++) {
visitors[i].run(evaldRoot);
}

View File

@@ -5,7 +5,7 @@ var Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike)
this.index = index;
this.mapLines = mapLines;
this.currentFileInfo = currentFileInfo;
this.rulesetLike = (typeof rulesetLike === 'undefined')? false : rulesetLike;
this.rulesetLike = (typeof rulesetLike === 'undefined') ? false : rulesetLike;
};
Anonymous.prototype = new Node();
Anonymous.prototype.type = "Anonymous";

View File

@@ -25,4 +25,3 @@ Assignment.prototype.genCSS = function (context, output) {
}
};
module.exports = Assignment;

View File

@@ -18,13 +18,11 @@ Call.prototype.accept = function (visitor) {
};
//
// When evaluating a function call,
// we either find the function in `less.functions` [1],
// we either find the function in the functionRegistry,
// in which case we call it, passing the evaluated arguments,
// if this returns null or we cannot find the function, we
// simply print it out as it appeared originally [2].
//
// The *functions.js* file contains the built-in functions.
//
// The reason why we evaluate the arguments, is in the case where
// we try to pass a variable to a function, like: `saturate(@color)`.
// The function should receive the value, not the variable.
@@ -52,7 +50,7 @@ Call.prototype.eval = function (context) {
Call.prototype.genCSS = function (context, output) {
output.add(this.name + "(", this.currentFileInfo, this.index);
for(var i = 0; i < this.args.length; i++) {
for (var i = 0; i < this.args.length; i++) {
this.args[i].genCSS(context, output);
if (i + 1 < this.args.length) {
output.add(", ");

View File

@@ -22,7 +22,7 @@ var Color = function (rgb, a) {
return parseInt(c + c, 16);
});
}
this.alpha = typeof(a) === 'number' ? a : 1;
this.alpha = typeof a === 'number' ? a : 1;
};
Color.prototype = new Node();
@@ -56,11 +56,11 @@ Color.prototype.genCSS = function (context, output) {
Color.prototype.toCSS = function (context, doNotCompress) {
var compress = context && context.compress && !doNotCompress, color, alpha;
// `keyword` is set if this color was originally
// `value` is set if this color was originally
// converted from a named color string so we need
// to respect this and try to output named color too.
if (this.keyword) {
return this.keyword;
if (this.value) {
return this.value;
}
// If we have some transparency, the only way to represent it
@@ -149,7 +149,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;
@@ -179,7 +179,7 @@ Color.fromKeyword = function(keyword) {
}
if (c) {
c.keyword = keyword;
c.value = keyword;
return c;
}
};

View File

@@ -20,13 +20,17 @@ Condition.prototype.eval = function (context) {
case 'or': return a || b;
default:
switch (Node.compare(a, b)) {
case -1: return op === '<' || op === '=<' || op === '<=';
case 0: return op === '=' || op === '>=' || op === '=<' || op === '<=';
case 1: return op === '>' || op === '>=';
default: return false;
case -1:
return op === '<' || op === '=<' || op === '<=';
case 0:
return op === '=' || op === '>=' || op === '=<' || op === '<=';
case 1:
return op === '>' || op === '>=';
default:
return false;
}
}
}) (this.op, this.lvalue.eval(context), this.rvalue.eval(context));
})(this.op, this.lvalue.eval(context), this.rvalue.eval(context));
return this.negate ? !result : result;
};

View File

@@ -1,5 +1,5 @@
var debugInfo = function(context, ctx, lineSeperator) {
var result="";
var debugInfo = function(context, ctx, lineSeparator) {
var result = "";
if (context.dumpLineNumbers && !context.compress) {
switch(context.dumpLineNumbers) {
case 'comments':
@@ -9,7 +9,7 @@ var debugInfo = function(context, ctx, lineSeperator) {
result = debugInfo.asMediaQuery(ctx);
break;
case 'all':
result = debugInfo.asComment(ctx) + (lineSeperator || "") + debugInfo.asMediaQuery(ctx);
result = debugInfo.asComment(ctx) + (lineSeparator || "") + debugInfo.asMediaQuery(ctx);
break;
}
}
@@ -21,8 +21,12 @@ debugInfo.asComment = function(ctx) {
};
debugInfo.asMediaQuery = function(ctx) {
var filenameWithProtocol = ctx.debugInfo.fileName;
if (!/^[a-z]+:\/\//i.test(filenameWithProtocol)) {
filenameWithProtocol = 'file://' + filenameWithProtocol;
}
return '@media -sass-debug-info{filename{font-family:' +
('file://' + ctx.debugInfo.fileName).replace(/([.:\/\\])/g, function (a) {
filenameWithProtocol.replace(/([.:\/\\])/g, function (a) {
if (a == '\\') {
a = '\/';
}

View File

@@ -25,7 +25,7 @@ Dimension.prototype.toColor = function () {
};
Dimension.prototype.genCSS = function (context, output) {
if ((context && context.strictUnits) && !this.unit.isSingular()) {
throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());
throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: " + this.unit.toString());
}
var value = this.fround(context, this.value),
@@ -70,9 +70,9 @@ Dimension.prototype.operate = function (context, op, other) {
} else {
other = other.convertTo(this.unit.usedUnits());
if(context.strictUnits && other.unit.toString() !== unit.toString()) {
throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() +
"' and '" + other.unit.toString() + "'.");
if (context.strictUnits && other.unit.toString() !== unit.toString()) {
throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() +
"' and '" + other.unit.toString() + "'.");
}
value = this._operate(context, op, this.value, other.value);
@@ -116,7 +116,7 @@ Dimension.prototype.convertTo = function (conversions) {
i, groupName, group, targetUnit, derivedConversions = {}, applyUnit;
if (typeof conversions === 'string') {
for(i in unitConversions) {
for (i in unitConversions) {
if (unitConversions[i].hasOwnProperty(conversions)) {
derivedConversions = {};
derivedConversions[i] = conversions;

View File

@@ -1,7 +1,7 @@
var Node = require("./node"),
Ruleset = require("./ruleset");
var Directive = function (name, value, rules, index, currentFileInfo, debugInfo) {
var Directive = function (name, value, rules, index, currentFileInfo, debugInfo, isReferenced) {
this.name = name;
this.value = value;
if (rules) {
@@ -11,6 +11,7 @@ var Directive = function (name, value, rules, index, currentFileInfo, debugInfo)
this.index = index;
this.currentFileInfo = currentFileInfo;
this.debugInfo = debugInfo;
this.isReferenced = isReferenced;
};
Directive.prototype = new Node();
@@ -38,7 +39,10 @@ Directive.prototype.genCSS = function (context, output) {
value.genCSS(context, output);
}
if (rules) {
this.outputRuleset(context, output, [rules]);
if (rules.type === "Ruleset") {
rules = [rules];
}
this.outputRuleset(context, output, rules);
} else {
output.add(';');
}
@@ -53,11 +57,23 @@ Directive.prototype.eval = function (context) {
rules.root = true;
}
return new Directive(this.name, value, rules,
this.index, this.currentFileInfo, this.debugInfo);
this.index, this.currentFileInfo, this.debugInfo, this.isReferenced);
};
Directive.prototype.variable = function (name) {
if (this.rules) {
return Ruleset.prototype.variable.call(this.rules, name);
}
};
Directive.prototype.find = function () {
if (this.rules) {
return Ruleset.prototype.find.apply(this.rules, arguments);
}
};
Directive.prototype.rulesets = function () {
if (this.rules) {
return Ruleset.prototype.rulesets.apply(this.rules);
}
};
Directive.prototype.variable = function (name) { if (this.rules) return Ruleset.prototype.variable.call(this.rules, name); };
Directive.prototype.find = function () { if (this.rules) return Ruleset.prototype.find.apply(this.rules, arguments); };
Directive.prototype.rulesets = function () { if (this.rules) return Ruleset.prototype.rulesets.apply(this.rules); };
Directive.prototype.markReferenced = function () {
var i, rules;
this.isReferenced = true;
@@ -70,6 +86,9 @@ Directive.prototype.markReferenced = function () {
}
}
};
Directive.prototype.getIsReferenced = function () {
return !this.currentFileInfo || !this.currentFileInfo.reference || this.isReferenced;
};
Directive.prototype.outputRuleset = function (context, output, rules) {
var ruleCnt = rules.length, i;
context.tabLevel = (context.tabLevel | 0) + 1;

View File

@@ -6,7 +6,7 @@ var Element = function (combinator, value, index, currentFileInfo) {
this.combinator = combinator instanceof Combinator ?
combinator : new Combinator(combinator);
if (typeof(value) === 'string') {
if (typeof value === 'string') {
this.value = value.trim();
} else if (value) {
this.value = value;

View File

@@ -5,7 +5,7 @@ var Node = require("./node"),
var Expression = function (value) {
this.value = value;
if (!value) {
throw new Error("Expression requires a array parameter");
throw new Error("Expression requires an array parameter");
}
};
Expression.prototype = new Node();
@@ -41,7 +41,7 @@ Expression.prototype.eval = function (context) {
return returnValue;
};
Expression.prototype.genCSS = function (context, output) {
for(var i = 0; i < this.value.length; i++) {
for (var i = 0; i < this.value.length; i++) {
this.value[i].genCSS(context, output);
if (i + 1 < this.value.length) {
output.add(" ");

View File

@@ -11,11 +11,11 @@ var Extend = function Extend(selector, option, index) {
case "all":
this.allowBefore = true;
this.allowAfter = true;
break;
break;
default:
this.allowBefore = false;
this.allowAfter = false;
break;
break;
}
};
Extend.next_id = 0;
@@ -36,7 +36,7 @@ Extend.prototype.findSelfSelectors = function (selectors) {
i,
selectorElements;
for(i = 0; i < selectors.length; i++) {
for (i = 0; i < selectors.length; i++) {
selectorElements = selectors[i].elements;
// duplicate the logic in genCSS function inside the selector node.
// future TODO - move both logics into the selector joiner visitor

View File

@@ -28,7 +28,7 @@ var Import = function (path, features, options, index, currentFileInfo) {
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;
}
}
@@ -73,6 +73,17 @@ Import.prototype.getPath = function () {
}
return null;
};
Import.prototype.isVariableImport = function () {
var path = this.path;
if (path instanceof URL) {
path = path.value;
}
if (path instanceof Quoted) {
return path.containsVariables();
}
return true;
};
Import.prototype.evalForImport = function (context) {
var path = this.path;
if (path instanceof URL) {
@@ -89,7 +100,7 @@ Import.prototype.evalPath = function (context) {
var pathValue = path.value;
// Add the base path if the import is relative
if (pathValue && context.isPathRelative(pathValue)) {
path.value = rootpath +pathValue;
path.value = rootpath + pathValue;
}
}
path.value = context.normalizePath(path.value);

View File

@@ -1,5 +1,6 @@
var tree = {};
tree.Node = require('./node');
tree.Alpha = require('./alpha');
tree.Color = require('./color');
tree.Directive = require('./directive');

View File

@@ -14,9 +14,9 @@ JavaScript.prototype.type = "JavaScript";
JavaScript.prototype.eval = function(context) {
var result = this.evaluateJavaScript(this.expression, context);
if (typeof(result) === 'number') {
if (typeof result === 'number') {
return new Dimension(result);
} else if (typeof(result) === 'string') {
} else if (typeof result === 'string') {
return new Quoted('"' + result + '"', result, this.escaped, this.index);
} else if (Array.isArray(result)) {
return new Anonymous(result.join(', '));

View File

@@ -39,7 +39,7 @@ Media.prototype.eval = function (context) {
}
var media = new Media(null, [], this.index, this.currentFileInfo);
if(this.debugInfo) {
if (this.debugInfo) {
this.rules[0].debugInfo = this.debugInfo;
media.debugInfo = this.debugInfo;
}
@@ -127,7 +127,7 @@ Media.prototype.evalNested = function (context) {
return fragment.toCSS ? fragment : new Anonymous(fragment);
});
for(i = path.length - 1; i > 0; i--) {
for (i = path.length - 1; i > 0; i--) {
path.splice(i, 0, new Anonymous("and"));
}
@@ -138,24 +138,25 @@ Media.prototype.evalNested = function (context) {
return new Ruleset([], []);
};
Media.prototype.permute = function (arr) {
if (arr.length === 0) {
return [];
} else if (arr.length === 1) {
return arr[0];
} else {
var result = [];
var rest = this.permute(arr.slice(1));
for (var i = 0; i < rest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push([arr[0][j]].concat(rest[i]));
}
}
return result;
}
if (arr.length === 0) {
return [];
} else if (arr.length === 1) {
return arr[0];
} else {
var result = [];
var rest = this.permute(arr.slice(1));
for (var i = 0; i < rest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push([arr[0][j]].concat(rest[i]));
}
}
return result;
}
};
Media.prototype.bubbleSelectors = function (selectors) {
if (!selectors)
return;
this.rules = [new Ruleset(selectors.slice(0), [this.rules[0]])];
if (!selectors) {
return;
}
this.rules = [new Ruleset(selectors.slice(0), [this.rules[0]])];
};
module.exports = Media;

View File

@@ -22,7 +22,7 @@ MixinCall.prototype.accept = function (visitor) {
};
MixinCall.prototype.eval = function (context) {
var mixins, mixin, mixinPath, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule,
candidates = [], candidate, conditionResult = [], defaultResult, defFalseEitherCase=-1,
candidates = [], candidate, conditionResult = [], defaultResult, defFalseEitherCase = -1,
defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset, noArgumentsFilter;
function calcDefGroup(mixin, mixinPath) {
@@ -31,7 +31,7 @@ MixinCall.prototype.eval = function (context) {
for (f = 0; f < 2; f++) {
conditionResult[f] = true;
defaultFunc.value(f);
for(p = 0; p < mixinPath.length && conditionResult[f]; p++) {
for (p = 0; p < mixinPath.length && conditionResult[f]; p++) {
namespace = mixinPath[p];
if (namespace.matchCondition) {
conditionResult[f] = conditionResult[f] && namespace.matchCondition(null, context);
@@ -71,7 +71,7 @@ MixinCall.prototype.eval = function (context) {
mixin = mixins[m].rule;
mixinPath = mixins[m].path;
isRecursive = false;
for(f = 0; f < context.frames.length; f++) {
for (f = 0; f < context.frames.length; f++) {
if ((!(mixin instanceof MixinDefinition)) && mixin === (context.frames[f].originalRuleset || context.frames[f])) {
isRecursive = true;
break;
@@ -84,7 +84,7 @@ MixinCall.prototype.eval = function (context) {
if (mixin.matchArgs(args, context)) {
candidate = {mixin: mixin, group: calcDefGroup(mixin, mixinPath)};
if (candidate.group!==defFalseEitherCase) {
if (candidate.group !== defFalseEitherCase) {
candidates.push(candidate);
}
@@ -105,8 +105,7 @@ MixinCall.prototype.eval = function (context) {
defaultResult = defTrue;
if ((count[defTrue] + count[defFalse]) > 1) {
throw { type: 'Runtime',
message: 'Ambiguous use of `default()` found when matching for `'
+ this.format(args) + '`',
message: 'Ambiguous use of `default()` found when matching for `' + this.format(args) + '`',
index: this.index, filename: this.currentFileInfo.filename };
}
}

View File

@@ -15,8 +15,12 @@ var Definition = function (name, params, rules, condition, variadic, frames) {
this.rules = rules;
this._lookups = {};
this.required = params.reduce(function (count, p) {
if (!p.name || (p.name && !p.value)) { return count + 1; }
else { return count; }
if (!p.name || (p.name && !p.value)) {
return count + 1;
}
else {
return count;
}
}, 0);
this.frames = frames;
};
@@ -45,11 +49,11 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
args = args.slice(0);
argsLength = args.length;
for(i = 0; i < argsLength; i++) {
for (i = 0; i < argsLength; i++) {
arg = args[i];
if (name = (arg && arg.name)) {
isNamedFound = false;
for(j = 0; j < params.length; j++) {
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)));
@@ -108,6 +112,17 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
return frame;
};
Definition.prototype.makeImportant = function() {
var rules = !this.rules ? this.rules : this.rules.map(function (r) {
if (r.makeImportant) {
return r.makeImportant(true);
} else {
return r;
}
});
var result = new Definition (this.name, this.params, rules, this.condition, this.variadic, this.frames);
return result;
};
Definition.prototype.eval = function (context) {
return new Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || context.frames.slice(0));
};
@@ -125,16 +140,17 @@ Definition.prototype.evalCall = function (context, args, important) {
ruleset.originalRuleset = this;
ruleset = ruleset.eval(new contexts.Eval(context, [this, frame].concat(mixinFrames)));
if (important) {
ruleset = this.makeImportant.apply(ruleset);
ruleset = ruleset.makeImportant();
}
return ruleset;
};
Definition.prototype.matchCondition = function (args, context) {
if (this.condition && !this.condition.eval(
new contexts.Eval(context,
[this.evalParams(context, new contexts.Eval(context, this.frames ? this.frames.concat(context.frames) : context.frames), args, [])] // the parameter variables
.concat(this.frames) // the parent namespace/mixin frames
.concat(context.frames)))) { // the current environment frames
[this.evalParams(context, /* the parameter variables*/
new contexts.Eval(context, this.frames ? this.frames.concat(context.frames) : context.frames), args, [])]
.concat(this.frames) // the parent namespace/mixin frames
.concat(context.frames)))) { // the current environment frames
return false;
}
return true;
@@ -143,10 +159,16 @@ Definition.prototype.matchArgs = function (args, context) {
var argsLength = (args && args.length) || 0, len;
if (! this.variadic) {
if (argsLength < this.required) { return false; }
if (argsLength > this.params.length) { return false; }
if (argsLength < this.required) {
return false;
}
if (argsLength > this.params.length) {
return false;
}
} else {
if (argsLength < (this.required - 1)) { return false; }
if (argsLength < (this.required - 1)) {
return false;
}
}
len = Math.min(argsLength, this.arity);

View File

@@ -3,7 +3,7 @@ var Node = require("./node"),
Variable = require("./variable");
var Quoted = function (str, content, escaped, index, currentFileInfo) {
this.escaped = escaped;
this.escaped = (escaped == null) ? true : escaped;
this.value = content || '';
this.quote = str.charAt(0);
this.index = index;
@@ -20,6 +20,9 @@ Quoted.prototype.genCSS = function (context, output) {
output.add(this.quote);
}
};
Quoted.prototype.containsVariables = function() {
return this.value.match(/(`([^`]+)`)|@\{([\w-]+)\}/);
};
Quoted.prototype.eval = function (context) {
var that = this, value = this.value;
var javascriptReplacement = function (_, exp) {
@@ -32,9 +35,9 @@ Quoted.prototype.eval = function (context) {
function iterativeReplace(value, regexp, replacementFnc) {
var evaluatedValue = value;
do {
value = evaluatedValue;
evaluatedValue = value.replace(regexp, replacementFnc);
} while (value!==evaluatedValue);
value = evaluatedValue;
evaluatedValue = value.replace(regexp, replacementFnc);
} while (value !== evaluatedValue);
return evaluatedValue;
}
value = iterativeReplace(value, /`([^`]+)`/g, javascriptReplacement);

View File

@@ -42,26 +42,31 @@ Rule.prototype.eval = function (context) {
if (typeof name !== "string") {
// expand 'primitive' name directly to get
// things faster (~10% for benchmark.less):
name = (name.length === 1)
&& (name[0] instanceof Keyword)
? name[0].value : evalName(context, name);
variable = false; // never treat expanded interpolation as new variable name
name = (name.length === 1) && (name[0] instanceof Keyword) ?
name[0].value : evalName(context, name);
variable = false; // never treat expanded interpolation as new variable name
}
if (name === "font" && !context.strictMath) {
strictMathBypass = true;
context.strictMath = true;
}
try {
context.importantScope.push({});
evaldValue = this.value.eval(context);
if (!this.variable && evaldValue.type === "DetachedRuleset") {
throw { message: "Rulesets cannot be evaluated on a property.",
index: this.index, filename: this.currentFileInfo.filename };
}
var important = this.important,
importantResult = context.importantScope.pop();
if (!important && importantResult.important) {
important = importantResult.important;
}
return new Rule(name,
evaldValue,
this.important,
important,
this.merge,
this.index, this.currentFileInfo, this.inline,
variable);

View File

@@ -2,6 +2,7 @@ var Node = require("./node"),
Rule = require("./rule"),
Selector = require("./selector"),
Element = require("./element"),
Paren = require("./paren"),
contexts = require("../contexts"),
defaultFunc = require("../functions/default"),
getDebugInfo = require("./debug-info");
@@ -57,7 +58,7 @@ Ruleset.prototype.eval = function (context) {
ruleset.firstRoot = this.firstRoot;
ruleset.allowImports = this.allowImports;
if(this.debugInfo) {
if (this.debugInfo) {
ruleset.debugInfo = this.debugInfo;
}
@@ -107,7 +108,7 @@ Ruleset.prototype.eval = function (context) {
});
rsRules.splice.apply(rsRules, [i, 1].concat(rules));
rsRuleCnt += rules.length - 1;
i += rules.length-1;
i += rules.length - 1;
ruleset.resetCache();
} else if (rsRules[i].type === "RulesetCall") {
/*jshint loopfunc:true */
@@ -120,7 +121,7 @@ Ruleset.prototype.eval = function (context) {
});
rsRules.splice.apply(rsRules, [i, 1].concat(rules));
rsRuleCnt += rules.length - 1;
i += rules.length-1;
i += rules.length - 1;
ruleset.resetCache();
}
}
@@ -142,7 +143,7 @@ Ruleset.prototype.eval = function (context) {
if (rule.selectors[0].isJustParentSelector()) {
rsRules.splice(i--, 1);
for(var j = 0; j < rule.rules.length; j++) {
for (var j = 0; j < rule.rules.length; j++) {
subRule = rule.rules[j];
if (!(subRule instanceof Rule) || !subRule.variable) {
rsRules.splice(++i, 0, subRule);
@@ -173,7 +174,7 @@ Ruleset.prototype.evalImports = function(context) {
importRules = rules[i].eval(context);
if (importRules && importRules.length) {
rules.splice.apply(rules, [i, 1].concat(importRules));
i+= importRules.length-1;
i+= importRules.length - 1;
} else {
rules.splice(i, 1, importRules);
}
@@ -182,20 +183,22 @@ Ruleset.prototype.evalImports = function(context) {
}
};
Ruleset.prototype.makeImportant = function() {
return new Ruleset(this.selectors, this.rules.map(function (r) {
if (r.makeImportant) {
return r.makeImportant();
} else {
return r;
}
}), this.strictImports);
var result = new Ruleset(this.selectors, this.rules.map(function (r) {
if (r.makeImportant) {
return r.makeImportant();
} else {
return r;
}
}), this.strictImports);
return result;
};
Ruleset.prototype.matchArgs = function (args) {
return !args || args.length === 0;
};
// lets you call a css selector with a guard
Ruleset.prototype.matchCondition = function (args, context) {
var lastSelector = this.selectors[this.selectors.length-1];
var lastSelector = this.selectors[this.selectors.length - 1];
if (!lastSelector.evaldCondition) {
return false;
}
@@ -218,6 +221,17 @@ Ruleset.prototype.variables = function () {
if (r instanceof Rule && r.variable === true) {
hash[r.name] = r;
}
// when evaluating variables in an import statement, imports have not been eval'd
// so we need to go inside import statements.
// guard against root being a string (in the case of inlined less)
if (r.type === "Import" && r.root && r.root.variables) {
var vars = r.root.variables();
for (var name in vars) {
if (vars.hasOwnProperty(name)) {
hash[name] = vars[name];
}
}
}
return hash;
}, {});
}
@@ -243,7 +257,11 @@ Ruleset.prototype.rulesets = function () {
};
Ruleset.prototype.prependRule = function (rule) {
var rules = this.rules;
if (rules) { rules.unshift(rule); } else { this.rules = [ rule ]; }
if (rules) {
rules.unshift(rule);
} else {
this.rules = [ rule ];
}
};
Ruleset.prototype.find = function (selector, self, filter) {
self = self || this;
@@ -258,13 +276,13 @@ Ruleset.prototype.find = function (selector, self, filter) {
match = selector.match(rule.selectors[j]);
if (match) {
if (selector.elements.length > match) {
if (!filter || filter(rule)) {
foundMixins = rule.find(new Selector(selector.elements.slice(match)), self, filter);
for (var i = 0; i < foundMixins.length; ++i) {
foundMixins[i].path.push(rule);
if (!filter || filter(rule)) {
foundMixins = rule.find(new Selector(selector.elements.slice(match)), self, filter);
for (var i = 0; i < foundMixins.length; ++i) {
foundMixins[i].path.push(rule);
}
Array.prototype.push.apply(rules, foundMixins);
}
Array.prototype.push.apply(rules, foundMixins);
}
} else {
rules.push({ rule: rule, path: []});
}
@@ -297,19 +315,17 @@ Ruleset.prototype.genCSS = function (context, output) {
sep;
function isRulesetLikeNode(rule, root) {
// 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(root);
}
// 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(root);
}
//anything else is assumed to be a rule
return false;
//anything else is assumed to be a rule
return false;
}
for (i = 0; i < this.rules.length; i++) {
@@ -403,189 +419,296 @@ Ruleset.prototype.genCSS = function (context, output) {
}
};
Ruleset.prototype.markReferenced = function () {
if (!this.selectors) {
return;
var s;
if (this.selectors) {
for (s = 0; s < this.selectors.length; s++) {
this.selectors[s].markReferenced();
}
}
for (var s = 0; s < this.selectors.length; s++) {
this.selectors[s].markReferenced();
if (this.rules) {
for (s = 0; s < this.rules.length; s++) {
if (this.rules[s].markReferenced) {
this.rules[s].markReferenced();
}
}
}
};
Ruleset.prototype.getIsReferenced = function() {
var i, j, path, selector;
if (this.paths) {
for (i = 0; i < this.paths.length; i++) {
path = this.paths[i];
for (j = 0; j < path.length; j++) {
if (path[j].getIsReferenced && path[j].getIsReferenced()) {
return true;
}
}
}
}
if (this.selectors) {
for (i = 0; i < this.selectors.length; i++) {
selector = this.selectors[i];
if (selector.getIsReferenced && selector.getIsReferenced()) {
return true;
}
}
}
return false;
};
Ruleset.prototype.joinSelectors = function (paths, context, selectors) {
for (var s = 0; s < selectors.length; s++) {
this.joinSelector(paths, context, selectors[s]);
}
};
Ruleset.prototype.joinSelector = function (paths, context, selector) {
var i, j, k,
hasParentSelector, newSelectors, el, sel, parentSel,
newSelectorPath, afterParentJoin, newJoinedSelector,
newJoinedSelectorEmpty, lastSelector, currentElements,
selectorsMultiplied;
for (i = 0; i < selector.elements.length; i++) {
el = selector.elements[i];
if (el.value === '&') {
hasParentSelector = true;
}
}
if (!hasParentSelector) {
if (context.length > 0) {
for (i = 0; i < context.length; i++) {
paths.push(context[i].concat(selector));
}
}
else {
paths.push([selector]);
}
return;
}
// The paths are [[Selector]]
// The first list is a list of comma seperated selectors
// The inner list is a list of inheritance seperated selectors
// e.g.
// .a, .b {
// .c {
// }
// }
// == [[.a] [.c]] [[.b] [.c]]
//
// the elements from the current selector so far
currentElements = [];
// the current list of new selectors to add to the path.
// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
// by the parents
newSelectors = [[]];
for (i = 0; i < selector.elements.length; i++) {
el = selector.elements[i];
// non parent reference elements just get added
if (el.value !== "&") {
currentElements.push(el);
function createParenthesis(elementsToPak, originalElement) {
var replacementParen, j;
if (elementsToPak.length === 0) {
replacementParen = new Paren(elementsToPak[0]);
} else {
// the new list of selectors to add
selectorsMultiplied = [];
var insideParent = [];
for (j = 0; j < elementsToPak.length; j++) {
insideParent.push(new Element(null, elementsToPak[j], originalElement.index, originalElement.currentFileInfo));
}
replacementParen = new Paren(new Selector(insideParent));
}
return replacementParen;
}
// merge the current list of non parent selector elements
// on to the current list of selectors to add
if (currentElements.length > 0) {
this.mergeElementsOnToSelectors(currentElements, newSelectors);
function createSelector(containedElement, originalElement) {
var element, selector;
element = new Element(null, containedElement, originalElement.index, originalElement.currentFileInfo);
selector = new Selector([element]);
return selector;
}
// replace all parent selectors inside `inSelector` by content of `context` array
// resulting selectors are returned inside `paths` array
// returns true if `inSelector` contained at least one parent selector
function replaceParentSelector(paths, context, inSelector) {
// The paths are [[Selector]]
// The first list is a list of comma separated selectors
// The inner list is a list of inheritance separated selectors
// e.g.
// .a, .b {
// .c {
// }
// }
// == [[.a] [.c]] [[.b] [.c]]
//
var i, j, k, currentElements, newSelectors, selectorsMultiplied, sel, el, hadParentSelector = false, length, lastSelector;
function findNestedSelector(element) {
var maybeSelector;
if (element.value.type !== 'Paren') {
return null;
}
// loop through our current selectors
for (j = 0; j < newSelectors.length; j++) {
sel = newSelectors[j];
// if we don't have any parent paths, the & might be in a mixin so that it can be used
// whether there are parents or not
if (context.length === 0) {
// 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 = sel[0].elements.slice(0);
sel[0].elements.push(new Element(el.combinator, '', el.index, el.currentFileInfo));
maybeSelector = element.value.value;
if (maybeSelector.type !== 'Selector') {
return null;
}
return maybeSelector;
}
// the elements from the current selector so far
currentElements = [];
// the current list of new selectors to add to the path.
// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
// by the parents
newSelectors = [
[]
];
for (i = 0; i < inSelector.elements.length; i++) {
el = inSelector.elements[i];
// non parent reference elements just get added
if (el.value !== "&") {
var nestedSelector = findNestedSelector(el);
if (nestedSelector != null) {
// merge the current list of non parent selector elements
// on to the current list of selectors to add
mergeElementsOnToSelectors(currentElements, newSelectors);
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
for (k = 0; k < nestedPaths.length; k++) {
var replacementSelector = createSelector(createParenthesis(nestedPaths[k], el), el);
addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector, replacedNewSelectors);
}
selectorsMultiplied.push(sel);
newSelectors = replacedNewSelectors;
currentElements = [];
} else {
currentElements.push(el);
}
else {
// and the parent selectors
for (k = 0; k < context.length; k++) {
parentSel = context[k];
// We need to put the current selectors
// then join the last selector's elements on to the parents selectors
// our new selector path
newSelectorPath = [];
// selectors from the parent after the join
afterParentJoin = [];
newJoinedSelectorEmpty = true;
} else {
hadParentSelector = true;
// the new list of selectors to add
selectorsMultiplied = [];
//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
// merge the current list of non parent selector elements
// on to the current list of selectors to add
mergeElementsOnToSelectors(currentElements, newSelectors);
// loop through our current selectors
for (j = 0; j < newSelectors.length; j++) {
sel = newSelectors[j];
// if we don't have any parent paths, the & might be in a mixin so that it can be used
// whether there are parents or not
if (context.length === 0) {
// the combinator used on el should now be applied to the next element instead so that
// it is not lost
if (sel.length > 0) {
newSelectorPath = sel.slice(0);
lastSelector = newSelectorPath.pop();
newJoinedSelector = selector.createDerived(lastSelector.elements.slice(0));
newJoinedSelectorEmpty = false;
sel[0].elements.push(new Element(el.combinator, '', el.index, el.currentFileInfo));
}
else {
newJoinedSelector = selector.createDerived([]);
selectorsMultiplied.push(sel);
}
else {
// and the parent selectors
for (k = 0; k < context.length; k++) {
// We need to put the current selectors
// then join the last selector's elements on to the parents selectors
var newSelectorPath = addReplacementIntoPath(sel, context[k], el, inSelector);
// add that to our new set of selectors
selectorsMultiplied.push(newSelectorPath);
}
//put together the parent selectors after the join
if (parentSel.length > 1) {
afterParentJoin = afterParentJoin.concat(parentSel.slice(1));
}
if (parentSel.length > 0) {
newJoinedSelectorEmpty = false;
// /deep/ is a combinator that is valid without anything in front of it
// so if the & does not have a combinator that is "" or " " then
// and there is a combinator on the parent, then grab that.
// this also allows + a { & .b { .a & { ... though not sure why you would want to do that
var combinator = el.combinator,
parentEl = parentSel[0].elements[0];
if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) {
combinator = parentEl.combinator;
}
// join the elements so far with the first part of the parent
newJoinedSelector.elements.push(new Element(combinator, parentEl.value, el.index, el.currentFileInfo));
newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1));
}
if (!newJoinedSelectorEmpty) {
// now add the joined selector
newSelectorPath.push(newJoinedSelector);
}
// and the rest of the parent
newSelectorPath = newSelectorPath.concat(afterParentJoin);
// add that to our new set of selectors
selectorsMultiplied.push(newSelectorPath);
}
}
// our new selectors has been multiplied, so reset the state
newSelectors = selectorsMultiplied;
currentElements = [];
}
// our new selectors has been multiplied, so reset the state
newSelectors = selectorsMultiplied;
currentElements = [];
}
}
// if we have any elements left over (e.g. .a& .b == .b)
// add them on to all the current selectors
if (currentElements.length > 0) {
this.mergeElementsOnToSelectors(currentElements, newSelectors);
}
// if we have any elements left over (e.g. .a& .b == .b)
// add them on to all the current selectors
mergeElementsOnToSelectors(currentElements, newSelectors);
for (i = 0; i < newSelectors.length; i++) {
if (newSelectors[i].length > 0) {
paths.push(newSelectors[i]);
for (i = 0; i < newSelectors.length; i++) {
length = newSelectors[i].length;
if (length > 0) {
paths.push(newSelectors[i]);
lastSelector = newSelectors[i][length - 1];
newSelectors[i][length - 1] = lastSelector.createDerived(lastSelector.elements, inSelector.extendList);
}
}
}
};
Ruleset.prototype.mergeElementsOnToSelectors = function(elements, selectors) {
var i, sel;
if (selectors.length === 0) {
selectors.push([ new Selector(elements) ]);
return;
return hadParentSelector;
}
for (i = 0; i < selectors.length; i++) {
sel = selectors[i];
// joins selector path from `beginningPath` with selector path in `addPath`
// `replacedElement` contains element that is being replaced by `addPath`
// returns concatenated path
function addReplacementIntoPath(beginningPath, addPath, replacedElement, originalSelector) {
var newSelectorPath, lastSelector, newJoinedSelector;
// our new selector path
newSelectorPath = [];
// 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));
//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);
lastSelector = newSelectorPath.pop();
newJoinedSelector = originalSelector.createDerived(lastSelector.elements.slice(0));
}
else {
sel.push(new Selector(elements));
newJoinedSelector = originalSelector.createDerived([]);
}
if (addPath.length > 0) {
// /deep/ is a combinator that is valid without anything in front of it
// so if the & does not have a combinator that is "" or " " then
// and there is a combinator on the parent, then grab that.
// this also allows + a { & .b { .a & { ... though not sure why you would want to do that
var combinator = replacedElement.combinator, parentEl = addPath[0].elements[0];
if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) {
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 = newJoinedSelector.elements.concat(addPath[0].elements.slice(1));
}
// now add the joined selector - but only if it is not empty
if (newJoinedSelector.elements.length !== 0) {
newSelectorPath.push(newJoinedSelector);
}
//put together the parent selectors after the join (e.g. the rest of the parent)
if (addPath.length > 1) {
newSelectorPath = newSelectorPath.concat(addPath.slice(1));
}
return newSelectorPath;
}
// joins selector path from `beginningPath` with every selector path in `addPaths` array
// `replacedElement` contains element that is being replaced by `addPath`
// returns array with all concatenated paths
function addAllReplacementsIntoPath( beginningPath, addPaths, replacedElement, originalSelector, result) {
var j;
for (j = 0; j < beginningPath.length; j++) {
var newSelectorPath = addReplacementIntoPath(beginningPath[j], addPaths, replacedElement, originalSelector);
result.push(newSelectorPath);
}
return result;
}
function mergeElementsOnToSelectors(elements, selectors) {
var i, sel;
if (elements.length === 0) {
return ;
}
if (selectors.length === 0) {
selectors.push([ new Selector(elements) ]);
return;
}
for (i = 0; i < selectors.length; i++) {
sel = selectors[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));
}
else {
sel.push(new Selector(elements));
}
}
}
// joinSelector code follows
var i, newPaths, hadParentSelector;
newPaths = [];
hadParentSelector = replaceParentSelector(newPaths, context, selector);
if (!hadParentSelector) {
if (context.length > 0) {
newPaths = [];
for (i = 0; i < context.length; i++) {
newPaths.push(context[i].concat(selector));
}
}
else {
newPaths = [[selector]];
}
}
for (i = 0; i < newPaths.length; i++) {
paths.push(newPaths[i]);
}
};
module.exports = Ruleset;

View File

@@ -51,8 +51,9 @@ Selector.prototype.match = function (other) {
return olen; // return number of matched elements
};
Selector.prototype.CacheElements = function() {
if (this._elements)
if (this._elements) {
return;
}
var elements = this.elements.map( function(v) {
return v.combinator.value + (v.value.value || v.value);
@@ -90,7 +91,7 @@ Selector.prototype.genCSS = function (context, output) {
}
if (!this._css) {
//TODO caching? speed comparison?
for(i = 0; i < this.elements.length; i++) {
for (i = 0; i < this.elements.length; i++) {
element = this.elements[i];
element.genCSS(context, output);
}

View File

@@ -4,7 +4,11 @@ var Node = require("./node"),
var Unit = function (numerator, denominator, backupUnit) {
this.numerator = numerator ? numerator.slice(0).sort() : [];
this.denominator = denominator ? denominator.slice(0).sort() : [];
this.backupUnit = backupUnit;
if (backupUnit) {
this.backupUnit = backupUnit;
} else if (numerator && numerator.length) {
this.backupUnit = numerator[0];
}
};
Unit.prototype = new Node();
@@ -13,14 +17,14 @@ Unit.prototype.clone = function () {
return new Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit);
};
Unit.prototype.genCSS = function (context, output) {
if (this.numerator.length >= 1) {
output.add(this.numerator[0]);
} else
if (this.denominator.length >= 1) {
output.add(this.denominator[0]);
} else
if ((!context || !context.strictUnits) && this.backupUnit) {
// Dimension checks the unit is singular and throws an error if in strict math mode.
var strictUnits = context && context.strictUnits;
if (this.numerator.length === 1) {
output.add(this.numerator[0]); // the ideal situation
} else if (!strictUnits && this.backupUnit) {
output.add(this.backupUnit);
} else if (!strictUnits && this.denominator.length) {
output.add(this.denominator[0]);
}
};
Unit.prototype.toString = function () {
@@ -79,21 +83,15 @@ Unit.prototype.usedUnits = function() {
return result;
};
Unit.prototype.cancel = function () {
var counter = {}, atomicUnit, i, backup;
var counter = {}, atomicUnit, i;
for (i = 0; i < this.numerator.length; i++) {
atomicUnit = this.numerator[i];
if (!backup) {
backup = atomicUnit;
}
counter[atomicUnit] = (counter[atomicUnit] || 0) + 1;
}
for (i = 0; i < this.denominator.length; i++) {
atomicUnit = this.denominator[i];
if (!backup) {
backup = atomicUnit;
}
counter[atomicUnit] = (counter[atomicUnit] || 0) - 1;
}
@@ -116,10 +114,6 @@ Unit.prototype.cancel = function () {
}
}
if (this.numerator.length === 0 && this.denominator.length === 0 && backup) {
this.backupUnit = backup;
}
this.numerator.sort();
this.denominator.sort();
};

View File

@@ -23,9 +23,12 @@ URL.prototype.eval = function (context) {
if (!this.isEvald) {
// Add the base path if the URL is relative
rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
if (rootpath && typeof val.value === "string" && context.isPathRelative(val.value)) {
if (rootpath &&
typeof val.value === "string" &&
context.isPathRelative(val.value)) {
if (!val.quote) {
rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; });
rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\" + match; });
}
val.value = rootpath + val.value;
}

View File

@@ -24,9 +24,9 @@ Value.prototype.eval = function (context) {
};
Value.prototype.genCSS = function (context, output) {
var i;
for(i = 0; i < this.value.length; i++) {
for (i = 0; i < this.value.length; i++) {
this.value[i].genCSS(context, output);
if (i+1 < this.value.length) {
if (i + 1 < this.value.length) {
output.add((context && context.compress) ? ',' : ', ');
}
}

View File

@@ -26,6 +26,10 @@ Variable.prototype.eval = function (context) {
variable = this.find(context.frames, function (frame) {
var v = frame.variable(name);
if (v) {
if (v.important) {
var importantScope = context.importantScope[context.importantScope.length - 1];
importantScope.important = v.important;
}
return v.value.eval(context);
}
});

View File

@@ -1,5 +1,6 @@
var tree = require("../tree"),
Visitor = require("./visitor");
Visitor = require("./visitor"),
logger = require("../logger");
/*jshint loopfunc:true */
@@ -30,7 +31,7 @@ ExtendFinderVisitor.prototype = {
// get &:extend(.a); rules which apply to all selectors in this ruleset
var rules = rulesetNode.rules, ruleCnt = rules ? rules.length : 0;
for(i = 0; i < ruleCnt; i++) {
for (i = 0; i < ruleCnt; i++) {
if (rulesetNode.rules[i] instanceof tree.Extend) {
allSelectorsExtendList.push(rules[i]);
rulesetNode.extendOnEveryPath = true;
@@ -40,7 +41,7 @@ ExtendFinderVisitor.prototype = {
// now find every selector and apply the extends that apply to all extends
// and the ones which apply to an individual extend
var paths = rulesetNode.paths;
for(i = 0; i < paths.length; i++) {
for (i = 0; i < paths.length; i++) {
var selectorPath = paths[i],
selector = selectorPath[selectorPath.length - 1],
selExtendList = selector.extendList;
@@ -54,13 +55,13 @@ ExtendFinderVisitor.prototype = {
});
}
for(j = 0; j < extendList.length; j++) {
for (j = 0; j < extendList.length; j++) {
this.foundExtends = true;
extend = extendList[j];
extend.findSelfSelectors(selectorPath);
extend.ruleset = rulesetNode;
if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
this.allExtendsStack[this.allExtendsStack.length - 1].push(extend);
}
}
@@ -94,23 +95,44 @@ var ProcessExtendsVisitor = function() {
ProcessExtendsVisitor.prototype = {
run: function(root) {
var extendFinder = new ExtendFinderVisitor();
this.extendIndicies = {};
extendFinder.run(root);
if (!extendFinder.foundExtends) { return root; }
root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
this.allExtendsStack = [root.allExtends];
return this._visitor.visit(root);
var newRoot = this._visitor.visit(root);
this.checkExtendsForNonMatched(root.allExtends);
return newRoot;
},
checkExtendsForNonMatched: function(extendList) {
var indicies = this.extendIndicies;
extendList.filter(function(extend) {
return !extend.hasFoundMatches && extend.parent_ids.length == 1;
}).forEach(function(extend) {
var selector = "_unknown_";
try {
selector = extend.selector.toCSS({});
}
catch(_) {}
if (!indicies[extend.index + ' ' + selector]) {
indicies[extend.index + ' ' + selector] = true;
logger.warn("extend '" + selector + "' has no matches");
}
});
},
doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
//
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
// the selector we would do normally, but we are also adding an extend with the same target selector
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering
// and pasting the selector we would do normally, but we are also adding an extend with the same target selector
// this means this new extend can then go and alter other extends
//
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
// we look at each selector at a time, as is done in visitRuleset
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already
// processed if we look at each selector at a time, as is done in visitRuleset
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath,
extend, targetExtend, newExtend;
iterationCount = iterationCount || 0;
@@ -120,14 +142,14 @@ ProcessExtendsVisitor.prototype = {
// and the second is the target.
// the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
// case when processing media queries
for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
for (extendIndex = 0; extendIndex < extendsList.length; extendIndex++) {
for (targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++) {
extend = extendsList[extendIndex];
targetExtend = extendsListTarget[targetExtendIndex];
// look for circular references
if( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ){ continue; }
if ( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ) { continue; }
// find a match in the target extends self selector (the bit before :extend)
selectorPath = [targetExtend.selfSelectors[0]];
@@ -135,6 +157,8 @@ ProcessExtendsVisitor.prototype = {
if (matches.length) {
extend.hasFoundMatches = true;
// we found a match, so for each self selector..
extend.selfSelectors.forEach(function(selfSelector) {
@@ -146,7 +170,7 @@ ProcessExtendsVisitor.prototype = {
newExtend.selfSelectors = newSelector;
// add the extend onto the list of extends for that selector
newSelector[newSelector.length-1].extendList = [newExtend];
newSelector[newSelector.length - 1].extendList = [newExtend];
// record that we need to add it.
extendsToAdd.push(newExtend);
@@ -180,11 +204,13 @@ ProcessExtendsVisitor.prototype = {
selectorTwo = extendsToAdd[0].selector.toCSS();
}
catch(e) {}
throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
throw { message: "extend circular reference detected. One of the circular extends is currently:" +
selectorOne + ":extend(" + selectorTwo + ")"};
}
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
// now process the new extends on the existing rules so that we can handle a extending b extending c extending
// d extending e...
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount + 1));
} else {
return extendsToAdd;
}
@@ -202,22 +228,24 @@ ProcessExtendsVisitor.prototype = {
if (rulesetNode.root) {
return;
}
var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this, selectorPath;
var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length - 1],
selectorsToAdd = [], extendVisitor = this, selectorPath;
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
for (extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
for (pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
selectorPath = rulesetNode.paths[pathIndex];
// extending extends happens initially, before the main pass
if (rulesetNode.extendOnEveryPath) { continue; }
var extendList = selectorPath[selectorPath.length-1].extendList;
var extendList = selectorPath[selectorPath.length - 1].extendList;
if (extendList && extendList.length) { continue; }
matches = this.findMatch(allExtends[extendIndex], selectorPath);
if (matches.length) {
allExtends[extendIndex].hasFoundMatches = true;
allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
@@ -239,24 +267,25 @@ ProcessExtendsVisitor.prototype = {
potentialMatches = [], potentialMatch, matches = [];
// loop through the haystack elements
for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
for (haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
for (hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
haystackElement = hackstackSelector.elements[hackstackElementIndex];
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
if (extend.allowBefore || (haystackSelectorIndex === 0 && hackstackElementIndex === 0)) {
potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0,
initialCombinator: haystackElement.combinator});
}
for(i = 0; i < potentialMatches.length; i++) {
for (i = 0; i < potentialMatches.length; i++) {
potentialMatch = potentialMatches[i];
// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
// what the resulting combinator will be
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to
// work out what the resulting combinator will be
targetCombinator = haystackElement.combinator.value;
if (targetCombinator === '' && hackstackElementIndex === 0) {
targetCombinator = ' ';
@@ -274,7 +303,8 @@ ProcessExtendsVisitor.prototype = {
if (potentialMatch) {
potentialMatch.finished = potentialMatch.matched === needleElements.length;
if (potentialMatch.finished &&
(!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
(!extend.allowAfter &&
(hackstackElementIndex + 1 < hackstackSelector.elements.length || haystackSelectorIndex + 1 < haystackSelectorPath.length))) {
potentialMatch = null;
}
}
@@ -320,7 +350,7 @@ ProcessExtendsVisitor.prototype = {
if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
return false;
}
for(var i = 0; i <elementValue1.elements.length; i++) {
for (var i = 0; i < elementValue1.elements.length; i++) {
if (elementValue1.elements[i].combinator.value !== elementValue2.elements[i].combinator.value) {
if (i !== 0 || (elementValue1.elements[i].combinator.value || ' ') !== (elementValue2.elements[i].combinator.value || ' ')) {
return false;
@@ -358,7 +388,8 @@ ProcessExtendsVisitor.prototype = {
);
if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
path[path.length - 1].elements = path[path.length - 1]
.elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
currentSelectorPathElementIndex = 0;
currentSelectorPathIndex++;
}
@@ -387,7 +418,8 @@ ProcessExtendsVisitor.prototype = {
}
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
path[path.length - 1].elements = path[path.length - 1]
.elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
currentSelectorPathIndex++;
}
@@ -398,20 +430,24 @@ ProcessExtendsVisitor.prototype = {
visitRulesetOut: function (rulesetNode) {
},
visitMedia: function (mediaNode, visitArgs) {
var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length - 1]);
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
this.allExtendsStack.push(newAllExtends);
},
visitMediaOut: function (mediaNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
var lastIndex = this.allExtendsStack.length - 1;
this.checkExtendsForNonMatched(this.allExtendsStack[lastIndex]);
this.allExtendsStack.length = lastIndex;
},
visitDirective: function (directiveNode, visitArgs) {
var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length - 1]);
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
this.allExtendsStack.push(newAllExtends);
},
visitDirectiveOut: function (directiveNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
var lastIndex = this.allExtendsStack.length - 1;
this.checkExtendsForNonMatched(this.allExtendsStack[lastIndex]);
this.allExtendsStack.length = lastIndex;
}
};

View File

@@ -0,0 +1,54 @@
function ImportSequencer(onSequencerEmpty) {
this.imports = [];
this.variableImports = [];
this._onSequencerEmpty = onSequencerEmpty;
this._currentDepth = 0;
}
ImportSequencer.prototype.addImport = function(callback) {
var importSequencer = this,
importItem = {
callback: callback,
args: null,
isReady: false
};
this.imports.push(importItem);
return function() {
importItem.args = Array.prototype.slice.call(arguments, 0);
importItem.isReady = true;
importSequencer.tryRun();
};
};
ImportSequencer.prototype.addVariableImport = function(callback) {
this.variableImports.push(callback);
};
ImportSequencer.prototype.tryRun = function() {
this._currentDepth++;
try {
while (true) {
while (this.imports.length > 0) {
var importItem = this.imports[0];
if (!importItem.isReady) {
return;
}
this.imports = this.imports.slice(1);
importItem.callback.apply(null, importItem.args);
}
if (this.variableImports.length === 0) {
break;
}
var variableImport = this.variableImports[0];
this.variableImports = this.variableImports.slice(1);
variableImport();
}
} finally {
this._currentDepth--;
}
if (this._currentDepth === 0 && this._onSequencerEmpty) {
this._onSequencerEmpty();
}
};
module.exports = ImportSequencer;

View File

@@ -1,144 +1,171 @@
var contexts = require("../contexts"),
Visitor = require("./visitor");
Visitor = require("./visitor"),
ImportSequencer = require("./import-sequencer");
var ImportVisitor = function(importer, finish) {
var ImportVisitor = function(importer, finish, evalEnv, onceFileDetectionMap, recursionDetector) {
this._visitor = new Visitor(this);
this._importer = importer;
this._finish = finish;
this.context = evalEnv || new contexts.Eval();
this.context = new contexts.Eval();
this.importCount = 0;
this.onceFileDetectionMap = onceFileDetectionMap || {};
this.onceFileDetectionMap = {};
this.recursionDetector = {};
if (recursionDetector) {
for(var fullFilename in recursionDetector) {
if (recursionDetector.hasOwnProperty(fullFilename)) {
this.recursionDetector[fullFilename] = true;
}
}
}
this._sequencer = new ImportSequencer(this._onSequencerEmpty.bind(this));
};
ImportVisitor.prototype = {
isReplacing: true,
isReplacing: false,
run: function (root) {
var error;
try {
// process the contents
this._visitor.visit(root);
}
catch(e) {
error = e;
this.error = e;
}
this.isFinished = true;
if (this.importCount === 0) {
this._finish(error);
this._sequencer.tryRun();
},
_onSequencerEmpty: function() {
if (!this.isFinished) {
return;
}
this._finish(this.error);
},
visitImport: function (importNode, visitArgs) {
var importVisitor = this,
evaldImportNode,
inlineCSS = importNode.options.inline;
var inlineCSS = importNode.options.inline;
if (!importNode.css || inlineCSS) {
try {
evaldImportNode = importNode.evalForImport(this.context);
} catch(e){
if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
// attempt to eval properly and treat as css
importNode.css = true;
// if that fails, this error will be thrown
importNode.error = e;
}
var context = new contexts.Eval(this.context, this.context.frames.slice(0));
var importParent = context.frames[0];
if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
importNode = evaldImportNode;
this.importCount++;
var context = new contexts.Eval(this.context, this.context.frames.slice(0));
if (importNode.options.multiple) {
context.importMultiple = true;
}
// try appending if we haven't determined if it is css or not
var tryAppendLessExtension = importNode.css === undefined;
this._importer.push(importNode.getPath(), tryAppendLessExtension, importNode.currentFileInfo, importNode.options, function (e, root, importedAtRoot, fullPath) {
if (e && !e.filename) {
e.index = importNode.index; e.filename = importNode.currentFileInfo.filename;
}
var duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector;
if (!context.importMultiple) {
if (duplicateImport) {
importNode.skip = true;
} else {
importNode.skip = function() {
if (fullPath in importVisitor.onceFileDetectionMap) {
return true;
}
importVisitor.onceFileDetectionMap[fullPath] = true;
return false;
};
}
}
var subFinish = function(e) {
importVisitor.importCount--;
if (importVisitor.importCount === 0 && importVisitor.isFinished) {
importVisitor._finish(e);
}
};
if (root) {
importNode.root = root;
importNode.importedFilename = fullPath;
if (!inlineCSS && (context.importMultiple || !duplicateImport)) {
importVisitor.recursionDetector[fullPath] = true;
new ImportVisitor(importVisitor._importer, subFinish, context, importVisitor.onceFileDetectionMap, importVisitor.recursionDetector)
.run(root);
return;
}
}
subFinish();
});
this.importCount++;
if (importNode.isVariableImport()) {
this._sequencer.addVariableImport(this.processImportNode.bind(this, importNode, context, importParent));
} else {
this.processImportNode(importNode, context, importParent);
}
}
visitArgs.visitDeeper = false;
return importNode;
},
processImportNode: function(importNode, context, importParent) {
var evaldImportNode,
inlineCSS = importNode.options.inline;
try {
evaldImportNode = importNode.evalForImport(context);
} catch(e) {
if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
// attempt to eval properly and treat as css
importNode.css = true;
// if that fails, this error will be thrown
importNode.error = e;
}
if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
if (evaldImportNode.options.multiple) {
context.importMultiple = true;
}
// try appending if we haven't determined if it is css or not
var tryAppendLessExtension = evaldImportNode.css === undefined;
for (var i = 0; i < importParent.rules.length; i++) {
if (importParent.rules[i] === importNode) {
importParent.rules[i] = evaldImportNode;
break;
}
}
var onImported = this.onImported.bind(this, evaldImportNode, context),
sequencedOnImported = this._sequencer.addImport(onImported);
this._importer.push(evaldImportNode.getPath(), tryAppendLessExtension, evaldImportNode.currentFileInfo,
evaldImportNode.options, sequencedOnImported);
} else {
this.importCount--;
if (this.isFinished) {
this._sequencer.tryRun();
}
}
},
onImported: function (importNode, context, e, root, importedAtRoot, fullPath) {
if (e) {
if (!e.filename) {
e.index = importNode.index; e.filename = importNode.currentFileInfo.filename;
}
this.error = e;
}
var importVisitor = this,
inlineCSS = importNode.options.inline,
duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector;
if (!context.importMultiple) {
if (duplicateImport) {
importNode.skip = true;
} else {
importNode.skip = function() {
if (fullPath in importVisitor.onceFileDetectionMap) {
return true;
}
importVisitor.onceFileDetectionMap[fullPath] = true;
return false;
};
}
}
if (root) {
importNode.root = root;
importNode.importedFilename = fullPath;
if (!inlineCSS && (context.importMultiple || !duplicateImport)) {
importVisitor.recursionDetector[fullPath] = true;
var oldContext = this.context;
this.context = context;
try {
this._visitor.visit(root);
} catch (e) {
this.error = e;
}
this.context = oldContext;
}
}
importVisitor.importCount--;
if (importVisitor.isFinished) {
importVisitor._sequencer.tryRun();
}
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
return ruleNode;
},
visitDirective: function (directiveNode, visitArgs) {
this.context.frames.unshift(directiveNode);
return directiveNode;
},
visitDirectiveOut: function (directiveNode) {
this.context.frames.shift();
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
this.context.frames.unshift(mixinDefinitionNode);
return mixinDefinitionNode;
},
visitMixinDefinitionOut: function (mixinDefinitionNode) {
this.context.frames.shift();
},
visitRuleset: function (rulesetNode, visitArgs) {
this.context.frames.unshift(rulesetNode);
return rulesetNode;
},
visitRulesetOut: function (rulesetNode) {
this.context.frames.shift();
},
visitMedia: function (mediaNode, visitArgs) {
this.context.frames.unshift(mediaNode.rules[0]);
return mediaNode;
},
visitMediaOut: function (mediaNode) {
this.context.frames.shift();

View File

@@ -14,7 +14,7 @@ ToCSSVisitor.prototype = {
visitRule: function (ruleNode, visitArgs) {
if (ruleNode.variable) {
return [];
return;
}
return ruleNode;
},
@@ -23,16 +23,14 @@ ToCSSVisitor.prototype = {
// mixin definitions do not get eval'd - this means they keep state
// so we have to clear that state here so it isn't used if toCSS is called twice
mixinNode.frames = [];
return [];
},
visitExtend: function (extendNode, visitArgs) {
return [];
},
visitComment: function (commentNode, visitArgs) {
if (commentNode.isSilent(this._context)) {
return [];
return;
}
return commentNode;
},
@@ -42,38 +40,70 @@ ToCSSVisitor.prototype = {
visitArgs.visitDeeper = false;
if (!mediaNode.rules.length) {
return [];
return;
}
return mediaNode;
},
visitDirective: function(directiveNode, visitArgs) {
if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) {
return [];
}
if (directiveNode.name === "@charset") {
if (!directiveNode.getIsReferenced()) {
return;
}
// Only output the debug info together with subsequent @charset definitions
// a comment (or @media statement) before the actual @charset directive 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");
var comment = new tree.Comment("/* " + directiveNode.toCSS(this._context).replace(/\n/g, "") + " */\n");
comment.debugInfo = directiveNode.debugInfo;
return this._visitor.visit(comment);
}
return [];
return;
}
this.charset = true;
}
if (directiveNode.rules && directiveNode.rules.rules) {
this._mergeRules(directiveNode.rules.rules);
//process childs
directiveNode.accept(this._visitor);
visitArgs.visitDeeper = false;
// the directive was directly referenced and therefore needs to be shown in the output
if (directiveNode.getIsReferenced()) {
return directiveNode;
}
if (!directiveNode.rules.rules) {
return ;
}
//the directive was not directly referenced
for (var r = 0; r < directiveNode.rules.rules.length; r++) {
var rule = directiveNode.rules.rules[r];
if (rule.getIsReferenced && rule.getIsReferenced()) {
//the directive contains something that was referenced (likely by extend)
//therefore it needs to be shown in output too
//marking as referenced in case the directive is stored inside another directive
directiveNode.markReferenced();
return directiveNode;
}
}
//The directive was not directly referenced and does not contain anything that
//was referenced. Therefore it must not be shown in output.
return ;
} else {
if (!directiveNode.getIsReferenced()) {
return;
}
}
return directiveNode;
},
checkPropertiesInRoot: function(rules) {
var ruleNode;
for(var i = 0; i < rules.length; i++) {
for (var i = 0; i < rules.length; i++) {
ruleNode = rules[i];
if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
throw { message: "properties must be inside selector blocks, they cannot be in the root.",
@@ -95,7 +125,7 @@ ToCSSVisitor.prototype = {
if (p[0].elements[0].combinator.value === ' ') {
p[0].elements[0].combinator = new(tree.Combinator)('');
}
for(i = 0; i < p.length; i++) {
for (i = 0; i < p.length; i++) {
if (p[i].getIsReferenced() && p[i].getIsOutput()) {
return true;
}
@@ -160,7 +190,7 @@ ToCSSVisitor.prototype = {
var ruleCache = {},
ruleList, rule, i;
for(i = rules.length - 1; i >= 0 ; i--) {
for (i = rules.length - 1; i >= 0 ; i--) {
rule = rules[i];
if (rule instanceof tree.Rule) {
if (!ruleCache[rule.name]) {
@@ -227,8 +257,8 @@ ToCSSVisitor.prototype = {
var spacedGroups = [];
var lastSpacedGroup = [];
parts.map(function (p) {
if (p.merge==="+") {
if (lastSpacedGroup.length > 0) {
if (p.merge === "+") {
if (lastSpacedGroup.length > 0) {
spacedGroups.push(toExpression(lastSpacedGroup));
}
lastSpacedGroup = [];

View File

@@ -106,6 +106,7 @@ Visitor.prototype = {
var out = [];
for (i = 0; i < cnt; i++) {
var evald = this.visit(nodes[i]);
if (evald === undefined) { continue; }
if (!evald.splice) {
out.push(evald);
} else if (evald.length) {
@@ -124,6 +125,9 @@ Visitor.prototype = {
for (i = 0, cnt = arr.length; i < cnt; i++) {
item = arr[i];
if (item === undefined) {
continue;
}
if (!item.splice) {
out.push(item);
continue;
@@ -131,6 +135,9 @@ Visitor.prototype = {
for (j = 0, nestedCnt = item.length; j < nestedCnt; j++) {
nestedItem = item[j];
if (nestedItem === undefined) {
continue;
}
if (!nestedItem.splice) {
out.push(nestedItem);
} else if (nestedItem.length) {

View File

@@ -1872,7 +1872,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
if (original.source !== null
&& original.line !== null
&& original.column !== null) {
if(lastOriginalSource !== original.source
if (lastOriginalSource !== original.source
|| lastOriginalLine !== original.line
|| lastOriginalColumn !== original.column
|| lastOriginalName !== original.name) {

View File

@@ -1,6 +1,6 @@
{
"name": "less",
"version": "2.0.0-b1",
"version": "2.3.1",
"description": "Leaner CSS",
"homepage": "http://lesscss.org",
"author": {
@@ -26,7 +26,7 @@
"bin": {
"lessc": "./bin/lessc"
},
"main": "./lib/less-node/index",
"main": "index",
"directories": {
"test": "./test"
},
@@ -40,26 +40,30 @@
"test": "grunt test"
},
"optionalDependencies": {
"graceful-fs": "~3.0.4",
"mime": "~1.2.11",
"request": "~2.45.0",
"mkdirp": "~0.5.0",
"source-map": "0.1.x",
"promise": "~6.0.1"
"errno": "^0.1.1",
"graceful-fs": "^3.0.5",
"image-size": "~0.3.5",
"mime": "^1.2.11",
"mkdirp": "^0.5.0",
"promise": "^6.0.1",
"request": "^2.51.0",
"source-map": "^0.2.0"
},
"devDependencies": {
"diff": "~1.0",
"grunt": "~0.4.5",
"grunt-contrib-clean": "~0.6.0",
"grunt-contrib-concat": "~0.5.0",
"grunt-contrib-connect": "~0.8.0",
"grunt-contrib-jasmine": "~0.8.0",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-uglify": "~0.6.0",
"grunt-shell": "~1.1.1",
"matchdep": "~0.3.0",
"time-grunt": "~1.0.0",
"grunt-browserify": "~3.0.1"
"diff": "^1.0",
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-concat": "^0.5.0",
"grunt-contrib-connect": "^0.9.0",
"grunt-contrib-jasmine": "^0.8.1",
"grunt-contrib-jshint": "^0.11.0",
"grunt-contrib-uglify": "^0.7.0",
"grunt-jscs": "^1.2.0",
"grunt-shell": "^1.1.1",
"grunt-browserify": "^3.2.0",
"matchdep": "^0.3.0",
"time-grunt": "^1.0.0",
"grunt-saucelabs": "^8.3.2"
},
"keywords": [
"compile less",

View File

@@ -1,3 +1,7 @@
/* Add js reporter for sauce */
jasmine.getEnv().addReporter(new jasmine.JSReporter2());
/* record log messages for testing */
var logMessages = [];
@@ -39,6 +43,25 @@ var testLessInDocument = function (testFunc) {
}
};
var ieFormat = function(text) {
var styleNode = document.createElement('style');
styleNode.setAttribute('type', 'text/css');
var headNode = document.getElementsByTagName('head')[0];
headNode.appendChild(styleNode);
try {
if (styleNode.styleSheet) {
styleNode.styleSheet.cssText = text;
} else {
styleNode.innerText = text;
}
} catch (e) {
throw new Error("Couldn't reassign styleSheet.cssText.");
}
var transformedText = styleNode.styleSheet ? styleNode.styleSheet.cssText : styleNode.innerText;
headNode.removeChild(styleNode);
return transformedText;
};
var testSheet = function (sheet) {
it(sheet.id + " should match the expected output", function (done) {
var lessOutputId = sheet.id.replace("original-", ""),
@@ -52,11 +75,16 @@ var testSheet = function (sheet) {
less.pageLoadFinished
.then(function () {
lessOutputObj = document.getElementById(lessOutputId);
lessOutput = lessOutputObj.innerText || lessOutputObj.innerHTML;
lessOutput = lessOutputObj.styleSheet ? lessOutputObj.styleSheet.cssText :
(lessOutputObj.innerText || lessOutputObj.innerHTML);
expectedOutput
.then(function (text) {
expect(text).toEqual(lessOutput);
if (window.navigator.userAgent.indexOf("MSIE") >= 0 ||
window.navigator.userAgent.indexOf("Trident/") >= 0) {
text = ieFormat(text);
}
expect(lessOutput).toEqual(text);
done();
});
});
@@ -98,21 +126,32 @@ var testErrorSheet = function (sheet) {
actualErrorElement = document.getElementById(id);
return actualErrorElement !== null;
}).then(function () {
actualErrorMsg = actualErrorElement.innerText
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"))
.replace(/&amp;/ig, "&")
// for IE8
.replace(/\r\n/g, "\n")
.replace(/\. \nin/, ". in");
actualErrorMsg = innerText
.replace(/\n\d+/g, function (lineNo) {
return lineNo + " ";
})
.replace(/\n\s*in /g, " in ")
.replace("\n\n", "\n")
.replace(/\nStack Trace\n[\s\S]*/, "");
.replace(/\n{2,}/g, "\n")
.replace(/\nStack Trace\n[\s\S]*/i, "")
.replace(/\n$/, "");
errorFile
.then(function (errorTxt) {
errorTxt = errorTxt
.replace("{path}", "")
.replace("{pathrel}", "")
.replace("{pathhref}", "http://localhost:8081/test/less/errors/")
.replace("{404status}", " (404)");
expect(errorTxt).toEqual(actualErrorMsg);
.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$/, "");
expect(actualErrorMsg).toEqual(errorTxt);
if (errorTxt == actualErrorMsg) {
actualErrorElement.style.display = "none";
}
@@ -139,12 +178,13 @@ var testErrorSheetConsole = function (sheet) {
errorFile
.then(function (errorTxt) {
errorTxt
.replace("{path}", "")
.replace("{pathrel}", "")
.replace("{pathhref}", "http://localhost:8081/browser/less/")
.replace("{404status}", " (404)")
.replace(/\{path\}/g, "")
.replace(/\{pathrel\}/g, "")
.replace(/\{pathhref\}/g, "http://localhost:8081/browser/less/")
.replace(/\{404status\}/g, " (404)")
.replace(/\{node\}.*\{\/node\}/g, "")
.trim();
expect(errorTxt).toEqual(actualErrorMsg);
expect(actualErrorMsg).toEqual(errorTxt);
done();
});
});
@@ -154,11 +194,13 @@ var loadFile = function (href) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', href, true);
request.onload = function (e) {
resolve(request.response.replace(/\r/g, ""));
request.onreadystatechange = function () {
if (request.readyState == 4) {
resolve(request.responseText.replace(/\r/g, ""));
}
};
request.send();
request.send(null);
});
};
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;

View File

@@ -6,6 +6,9 @@
.modify {
my-url: url("http://localhost:8081/test/browser/less/b.png");
}
.gray-gradient {
background: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20viewBox%3D%220%200%201%201%22%20preserveAspectRatio%3D%22none%22%3E%3ClinearGradient%20id%3D%22gradient%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%25%22%20y1%3D%220%25%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E%3Cstop%20offset%3D%220%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%2260%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.05%22%2F%3E%3Cstop%20offset%3D%2270%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.1%22%2F%3E%3Cstop%20offset%3D%2273%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.15%22%2F%3E%3Cstop%20offset%3D%2275%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.2%22%2F%3E%3Cstop%20offset%3D%2280%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.25%22%2F%3E%3Cstop%20offset%3D%2285%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.3%22%2F%3E%3Cstop%20offset%3D%2288%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.35%22%2F%3E%3Cstop%20offset%3D%2290%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.4%22%2F%3E%3Cstop%20offset%3D%2295%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.45%22%2F%3E%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.5%22%2F%3E%3C%2FlinearGradient%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22url(%23gradient)%22%20%2F%3E%3C%2Fsvg%3E');
}
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/fonts.svg#MyGeometricModern) format("svg");
@@ -48,7 +51,7 @@
uri: url('http://localhost:8081/test/data/data-uri-fail.png');
}
#svg-functions {
background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#000000"/><stop offset="100%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#000000"/><stop offset="3%" stop-color="#ffa500"/><stop offset="100%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="1%" stop-color="#c4c4c4"/><stop offset="3%" stop-color="#ffa500"/><stop offset="5%" stop-color="#008000"/><stop offset="95%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
background-image: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20viewBox%3D%220%200%201%201%22%20preserveAspectRatio%3D%22none%22%3E%3ClinearGradient%20id%3D%22gradient%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%25%22%20y1%3D%220%25%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E%3Cstop%20offset%3D%220%25%22%20stop-color%3D%22%23000000%22%2F%3E%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%23ffffff%22%2F%3E%3C%2FlinearGradient%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22url(%23gradient)%22%20%2F%3E%3C%2Fsvg%3E');
background-image: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20viewBox%3D%220%200%201%201%22%20preserveAspectRatio%3D%22none%22%3E%3ClinearGradient%20id%3D%22gradient%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%25%22%20y1%3D%220%25%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E%3Cstop%20offset%3D%220%25%22%20stop-color%3D%22%23000000%22%2F%3E%3Cstop%20offset%3D%223%25%22%20stop-color%3D%22%23ffa500%22%2F%3E%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%23ffffff%22%2F%3E%3C%2FlinearGradient%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22url(%23gradient)%22%20%2F%3E%3C%2Fsvg%3E');
background-image: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20viewBox%3D%220%200%201%201%22%20preserveAspectRatio%3D%22none%22%3E%3ClinearGradient%20id%3D%22gradient%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%25%22%20y1%3D%220%25%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E%3Cstop%20offset%3D%221%25%22%20stop-color%3D%22%23c4c4c4%22%2F%3E%3Cstop%20offset%3D%223%25%22%20stop-color%3D%22%23ffa500%22%2F%3E%3Cstop%20offset%3D%225%25%22%20stop-color%3D%22%23008000%22%2F%3E%3Cstop%20offset%3D%2295%25%22%20stop-color%3D%22%23ffffff%22%2F%3E%3C%2FlinearGradient%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22url(%23gradient)%22%20%2F%3E%3C%2Fsvg%3E');
}

View File

@@ -0,0 +1,391 @@
/*
This file is part of the Jasmine JSReporter project from Ivan De Marino.
Copyright (C) 2011-2014 Ivan De Marino <http://ivandemarino.me>
Copyright (C) 2014 Alex Treppass <http://alextreppass.co.uk>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL IVAN DE MARINO BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function (jasmine) {
if (!jasmine) {
throw new Error("[Jasmine JSReporter] 'Jasmine' library not found");
}
// ------------------------------------------------------------------------
// Jasmine JSReporter for Jasmine 1.x
// ------------------------------------------------------------------------
/**
* Calculate elapsed time, in Seconds.
* @param startMs Start time in Milliseconds
* @param finishMs Finish time in Milliseconds
* @return Elapsed time in Seconds */
function elapsedSec (startMs, finishMs) {
return (finishMs - startMs) / 1000;
}
/**
* Round an amount to the given number of Digits.
* If no number of digits is given, than '2' is assumed.
* @param amount Amount to round
* @param numOfDecDigits Number of Digits to round to. Default value is '2'.
* @return Rounded amount */
function round (amount, numOfDecDigits) {
numOfDecDigits = numOfDecDigits || 2;
return Math.round(amount * Math.pow(10, numOfDecDigits)) / Math.pow(10, numOfDecDigits);
}
/**
* 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) {
var fs = [], i, v;
for (i = 0; i < items.length; i += 1) {
v = items[i];
if (!v.passed_) {
fs.push(v);
}
}
return fs;
}
/**
* Collect information about a Suite, recursively, and return a JSON result.
* @param suite The Jasmine Suite to get data from
*/
function getSuiteData (suite) {
var suiteData = {
description : suite.description,
durationSec : 0,
specs: [],
suites: [],
passed: true
},
specs = suite.specs(),
suites = suite.suites(),
i, ilen;
// Loop over all the Suite's Specs
for (i = 0, ilen = specs.length; i < ilen; ++i) {
suiteData.specs[i] = {
description : specs[i].description,
durationSec : specs[i].durationSec,
passed : specs[i].results().passedCount === specs[i].results().totalCount,
skipped : specs[i].results().skipped,
passedCount : specs[i].results().passedCount,
failedCount : specs[i].results().failedCount,
totalCount : specs[i].results().totalCount,
failures: failures(specs[i].results().getItems())
};
suiteData.passed = !suiteData.specs[i].passed ? false : suiteData.passed;
suiteData.durationSec += suiteData.specs[i].durationSec;
}
// Loop over all the Suite's sub-Suites
for (i = 0, ilen = suites.length; i < ilen; ++i) {
suiteData.suites[i] = getSuiteData(suites[i]); //< recursive population
suiteData.passed = !suiteData.suites[i].passed ? false : suiteData.passed;
suiteData.durationSec += suiteData.suites[i].durationSec;
}
// Rounding duration numbers to 3 decimal digits
suiteData.durationSec = round(suiteData.durationSec, 4);
return suiteData;
}
var JSReporter = function () {
};
JSReporter.prototype = {
reportRunnerStarting: function (runner) {
// Nothing to do
},
reportSpecStarting: function (spec) {
// Start timing this spec
spec.startedAt = new Date();
},
reportSpecResults: function (spec) {
// Finish timing this spec and calculate duration/delta (in sec)
spec.finishedAt = new Date();
// If the spec was skipped, reportSpecStarting is never called and spec.startedAt is undefined
spec.durationSec = spec.startedAt ? elapsedSec(spec.startedAt.getTime(), spec.finishedAt.getTime()) : 0;
},
reportSuiteResults: function (suite) {
// Nothing to do
},
reportRunnerResults: function (runner) {
var suites = runner.suites(),
i, j, ilen;
// Attach results to the "jasmine" object to make those results easy to scrap/find
jasmine.runnerResults = {
suites: [],
durationSec : 0,
passed : true
};
// Loop over all the Suites
for (i = 0, ilen = suites.length, j = 0; i < ilen; ++i) {
if (suites[i].parentSuite === null) {
jasmine.runnerResults.suites[j] = getSuiteData(suites[i]);
// If 1 suite fails, the whole runner fails
jasmine.runnerResults.passed = !jasmine.runnerResults.suites[j].passed ? false : jasmine.runnerResults.passed;
// Add up all the durations
jasmine.runnerResults.durationSec += jasmine.runnerResults.suites[j].durationSec;
j++;
}
}
// Decorate the 'jasmine' object with getters
jasmine.getJSReport = function () {
if (jasmine.runnerResults) {
return jasmine.runnerResults;
}
return null;
};
jasmine.getJSReportAsString = function () {
return JSON.stringify(jasmine.getJSReport());
};
}
};
// export public
jasmine.JSReporter = JSReporter;
// ------------------------------------------------------------------------
// Jasmine JSReporter for Jasmine 2.0
// ------------------------------------------------------------------------
/*
Simple timer implementation
*/
var Timer = function () {};
Timer.prototype.start = function () {
this.startTime = new Date().getTime();
return this;
};
Timer.prototype.elapsed = function () {
if (this.startTime == null) {
return -1;
}
return new Date().getTime() - this.startTime;
};
/*
Utility methods
*/
var _extend = function (obj1, obj2) {
for (var prop in obj2) {
obj1[prop] = obj2[prop];
}
return obj1;
};
var _clone = function (obj) {
if (obj !== Object(obj)) {
return obj;
}
return _extend({}, obj);
};
jasmine.JSReporter2 = function () {
this.specs = {};
this.suites = {};
this.rootSuites = [];
this.suiteStack = [];
// export methods under jasmine namespace
jasmine.getJSReport = this.getJSReport;
jasmine.getJSReportAsString = this.getJSReportAsString;
};
var JSR = jasmine.JSReporter2.prototype;
// Reporter API methods
// --------------------
JSR.suiteStarted = function (suite) {
suite = this._cacheSuite(suite);
// build up suite tree as we go
suite.specs = [];
suite.suites = [];
suite.passed = true;
suite.parentId = this.suiteStack.slice(this.suiteStack.length - 1)[0];
if (suite.parentId) {
this.suites[suite.parentId].suites.push(suite);
} else {
this.rootSuites.push(suite.id);
}
this.suiteStack.push(suite.id);
suite.timer = new Timer().start();
};
JSR.suiteDone = function (suite) {
suite = this._cacheSuite(suite);
suite.duration = suite.timer.elapsed();
suite.durationSec = suite.duration / 1000;
this.suiteStack.pop();
// maintain parent suite state
var parent = this.suites[suite.parentId];
if (parent) {
parent.passed = parent.passed && suite.passed;
}
// keep report representation clean
delete suite.timer;
delete suite.id;
delete suite.parentId;
delete suite.fullName;
};
JSR.specStarted = function (spec) {
spec = this._cacheSpec(spec);
spec.timer = new Timer().start();
// build up suites->spec tree as we go
spec.suiteId = this.suiteStack.slice(this.suiteStack.length - 1)[0];
this.suites[spec.suiteId].specs.push(spec);
};
JSR.specDone = function (spec) {
spec = this._cacheSpec(spec);
spec.duration = spec.timer.elapsed();
spec.durationSec = spec.duration / 1000;
spec.skipped = spec.status === 'pending';
spec.passed = spec.skipped || spec.status === 'passed';
spec.totalCount = spec.passedExpectations.length + spec.failedExpectations.length;
spec.passedCount = spec.passedExpectations.length;
spec.failedCount = spec.failedExpectations.length;
spec.failures = [];
for (var i = 0, j = spec.failedExpectations.length; i < j; i++) {
var fail = spec.failedExpectations[i];
spec.failures.push({
type: 'expect',
expected: fail.expected,
passed: false,
message: fail.message,
matcherName: fail.matcherName,
trace: {
stack: fail.stack
}
});
}
// maintain parent suite state
var parent = this.suites[spec.suiteId];
if (spec.failed) {
parent.failingSpecs.push(spec);
}
parent.passed = parent.passed && spec.passed;
// keep report representation clean
delete spec.timer;
delete spec.totalExpectations;
delete spec.passedExpectations;
delete spec.suiteId;
delete spec.fullName;
delete spec.id;
delete spec.status;
delete spec.failedExpectations;
};
JSR.jasmineDone = function () {
this._buildReport();
};
JSR.getJSReport = function () {
if (jasmine.jsReport) {
return jasmine.jsReport;
}
};
JSR.getJSReportAsString = function () {
if (jasmine.jsReport) {
return JSON.stringify(jasmine.jsReport);
}
};
// Private methods
// ---------------
JSR._haveSpec = function (spec) {
return this.specs[spec.id] != null;
};
JSR._cacheSpec = function (spec) {
var existing = this.specs[spec.id];
if (existing == null) {
existing = this.specs[spec.id] = _clone(spec);
} else {
_extend(existing, spec);
}
return existing;
};
JSR._haveSuite = function (suite) {
return this.suites[suite.id] != null;
};
JSR._cacheSuite = function (suite) {
var existing = this.suites[suite.id];
if (existing == null) {
existing = this.suites[suite.id] = _clone(suite);
} else {
_extend(existing, suite);
}
return existing;
};
JSR._buildReport = function () {
var overallDuration = 0;
var overallPassed = true;
var overallSuites = [];
for (var i = 0, j = this.rootSuites.length; i < j; i++) {
var suite = this.suites[this.rootSuites[i]];
overallDuration += suite.duration;
overallPassed = overallPassed && suite.passed;
overallSuites.push(suite);
}
jasmine.jsReport = {
passed: overallPassed,
durationSec: overallDuration / 1000,
suites: overallSuites
};
};
})(jasmine);

View File

@@ -0,0 +1,5 @@
@import "svg-gradient-mixin.less";
.gray-gradient {
.gradient-mixin(#999);
}

View File

@@ -0,0 +1,15 @@
.gradient-mixin(@color) {
background: svg-gradient(to bottom,
fade(@color, 0%) 0%,
fade(@color, 5%) 60%,
fade(@color, 10%) 70%,
fade(@color, 15%) 73%,
fade(@color, 20%) 75%,
fade(@color, 25%) 80%,
fade(@color, 30%) 85%,
fade(@color, 35%) 88%,
fade(@color, 40%) 90%,
fade(@color, 45%) 95%,
fade(@color, 50%) 100%
);
}

View File

@@ -1,5 +1,6 @@
@import "imports/urls.less";
@import "http://localhost:8081/test/browser/less/imports/urls2.less";
@import "http://localhost:8081/test/browser/less/nested-gradient-with-svg-gradient/mixin-consumer.less";
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium),

View File

@@ -10,34 +10,42 @@ var less = {logLevel: 4, errorReporting: "console"};
var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
testSheets = [];
// IE 8-10 does not support less in style tags
if (window.navigator.userAgent.indexOf("MSIE") >= 0) {
testFiles.length = 0;
}
// setup style tags with less and link tags pointing to expected css output
for (var i = 0; i < testFiles.length; i++) {
var file = testFiles[i],
lessPath = '/test/less/' + file + '.less',
cssPath = '/test/css/' + file + '.css',
lessStyle = document.createElement('style'),
cssLink = document.createElement('link'),
lessText = '@import "' + lessPath + '";';
var file = testFiles[i],
lessPath = '/test/less/' + file + '.less',
cssPath = '/test/css/' + file + '.css',
lessStyle = document.createElement('style'),
cssLink = document.createElement('link'),
lessText = '@import "' + lessPath + '";';
lessStyle.type = 'text/less';
lessStyle.id = file;
lessStyle.href = file;
lessStyle.type = 'text/less';
lessStyle.id = file;
lessStyle.href = file;
if (lessStyle.styleSheet) {
lessStyle.styleSheet.cssText = lessText;
} else {
lessStyle.innerHTML = lessText;
}
if (lessStyle.styleSheet === undefined) {
lessStyle.appendChild(document.createTextNode(lessText));
}
cssLink.rel = 'stylesheet';
cssLink.type = 'text/css';
cssLink.href = cssPath;
cssLink.id = 'expected-' + file;
cssLink.rel = 'stylesheet';
cssLink.type = 'text/css';
cssLink.href = cssPath;
cssLink.id = 'expected-' + file;
var head = document.getElementsByTagName('head')[0];
var head = document.getElementsByTagName('head')[0];
head.appendChild(lessStyle);
head.appendChild(cssLink);
testSheets[i] = lessStyle;
}
head.appendChild(lessStyle);
if (lessStyle.styleSheet) {
lessStyle.styleSheet.cssText = lessText;
}
head.appendChild(cssLink);
testSheets[i] = lessStyle;
}

View File

@@ -2,4 +2,3 @@ var less = {
strictUnits: true,
strictMath: true,
logLevel: 4 };

View File

@@ -1,4 +1,3 @@
describe("less.js error tests", function() {
testLessErrorsInDocument();
testLessErrorsInDocument();
});

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