merged upstream/master

This commit is contained in:
Oliver Becker
2013-10-27 13:02:09 +01:00
110 changed files with 9587 additions and 663 deletions

18
.gitignore vendored
View File

@@ -1,12 +1,24 @@
node_modules
# OS and IDE
.emacs*
*.flymake
*~
.#*
.idea
.gradle
*.sublime-*
# npm
node_modules
npm-debug.log
# project-specific
tmp
test/browser/less.js
test/browser/test-runner-*.htm
test/sourcemaps/*.map
test/sourcemaps/*.css
# grunt
.grunt
# gradle
.gradle
out

View File

@@ -1,5 +0,0 @@
benchmark/
build/
dist/
node_modules/
test/browser/

View File

@@ -1,24 +1,28 @@
# 1.5.0 Beta 1
# 1.5.0
2013-09-01
2013-10-21
- sourcemap support
- support for import inline option to include css that you do NOT want less to parse e.g. `@import (inline) "file.css";`
- better support for modifyVars (refresh styles with new variables, using a file cache), is now more resiliant
- support for import reference option to reference external css, but not output it. Any mixin calls or extend's will be output.
- 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
- 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
- change to not throw exceptions in toCSS - always return an error object
- allow property merging through the +: syntax
- Fix the saturate function to pass through when using the CSS syntax
- Added svg-gradient function
- Added no-js option to lessc (in browser, use javascriptEnabled: false) which disallows JavaScript in less files
- switched from the little supported and buggy cssmin (previously ycssmin) to clean-css
- support transparent as a color, but not convert between rgba(0, 0, 0, 0) and transparent
- remove sys.puts calls to stop deprecation warnings in future node.js releases
- Browser: added logLevel option to control logging (2 = everything, 1 = errors only, 0 = no logging)
- Browser: added errorReporting option which can be "html" (default) or "console" or a function
- A few bug fixes for media queries and extends
- Now uses grunt for building and testing
- A few bug fixes for media queries, extends, scoping, compression and import once.
# 1.4.2

View File

@@ -3,7 +3,7 @@
> We welcome feature requests and bug reports. Please read these guidelines before submitting one.
<span class="warning">**Words that begin with the at sign (`@`) must be wrapped in backticks!** </span>. as a courtesy to avoid sending notifications to any user that might have the `@username` being referenced. Remember, usernames start with the at sign.
<span class="warning">**Words that begin with the at sign (`@`) must be wrapped in backticks!** </span>. As a courtesy to avoid sending notifications to any user that might have the `@username` being referenced, please remember that GitHub usernames also start with the at sign. If you don't wrap them in backticks, users will get unintended notifications from you.
GitHub has other great markdown features as well, [go here to learn more about them](https://help.github.com/articles/github-flavored-markdown).

265
Gruntfile.js Normal file
View File

@@ -0,0 +1,265 @@
'use strict';
module.exports = function(grunt) {
// Report the elapsed execution time of tasks.
require('time-grunt')(grunt);
// Project configuration.
grunt.initConfig({
// Metadata required for build.
build: grunt.file.readYAML('build/build.yml'),
pkg: grunt.file.readJSON('package.json'),
meta: {
license: '<%= _.pluck(pkg.licenses, "type").join(", ") %>',
copyright: 'Copyright (c) 2009-<%= grunt.template.today("yyyy") %>',
banner:
'/*! \n' +
' * LESS - <%= pkg.description %> v<%= pkg.version %> \n' +
' * http://lesscss.org \n' +
' * \n' +
' * <%= meta.copyright %>, <%= pkg.author.name %> <<%= pkg.author.email %>> \n' +
' * Licensed under the <%= meta.license %> License. \n' +
' * \n' +
' * @licence \n' +
' */ \n\n'
},
shell: {
options: {stdout: true, failOnError: true},
test: {
command: 'node test/less-test.js'
},
benchmark: {
command: 'node benchmark/less-benchmark.js'
},
"browsertest-server": {
command: 'node node_modules/http-server/bin/http-server . -p 8088'
},
"sourcemap-test": {
command: [
'node bin/lessc --source-map --source-map-inline test/less/import.less test/sourcemaps/import.css',
'node bin/lessc --source-map --source-map-inline test/less/sourcemaps/basic.less test/sourcemaps/basic.css',
'node node_modules/http-server/bin/http-server test/sourcemaps -p 8084'].join('&&')
}
},
concat: {
options: {
stripBanners: 'all',
banner: '<%= meta.banner %>\n\n(function (window, undefined) {',
footer: '\n})(window);'
},
// Browser versions
browsertest: {
src: ['<%= build.browser %>'],
dest: 'test/browser/less.js'
},
stable: {
src: ['<%= build.browser %>'],
dest: 'dist/less-<%= pkg.version %>.js'
},
// Rhino
rhino: {
options: {
banner: '/* LESS.js v<%= pkg.version %> RHINO | <%= meta.copyright %>, <%= pkg.author.name %> <<%= pkg.author.email %>> */\n\n',
footer: '' // override task-level footer
},
src: ['<%= build.rhino %>'],
dest: 'dist/less-rhino-<%= pkg.version %>.js'
},
// Generate readme
readme: {
// override task-level banner and footer
options: {process: true, banner: '', footer: ''},
src: ['build/README.md'],
dest: 'README.md'
}
},
uglify: {
options: {
banner: '<%= meta.banner %>',
mangle: true
},
stable: {
src: ['<%= concat.stable.src %>'],
dest: 'dist/less-<%= pkg.version %>.min.js'
}
},
jshint: {
options: {jshintrc: '.jshintrc'},
files: {
src: [
'Gruntfile.js',
'lib/**/*.js'
]
}
},
connect: {
server: {
options: {
port: 8081
}
}
},
jasmine: {
options: {
// version: '2.0.0-rc2',
keepRunner: true,
host: 'http://localhost:8081/',
vendor: ['test/browser/common.js', 'test/browser/less.js'],
template: 'test/browser/test-runner-template.tmpl'
},
main: {
// src is used to build list of less files to compile
src: ['test/less/*.less', '!test/less/javascript.less', '!test/less/urls.less'],
options: {
helpers: 'test/browser/runner-main-options.js',
specs: 'test/browser/runner-main-spec.js',
outfile: 'tmp/browser/test-runner-main.html'
}
},
legacy: {
src: ['test/less/legacy/*.less'],
options: {
helpers: 'test/browser/runner-legacy-options.js',
specs: 'test/browser/runner-legacy-spec.js',
outfile: 'tmp/browser/test-runner-legacy.html'
}
},
errors: {
src: ['test/less/errors/*.less', '!test/less/errors/javascript-error.less'],
options: {
timeout: 20000,
helpers: 'test/browser/runner-errors-options.js',
specs: 'test/browser/runner-errors-spec.js',
outfile: 'tmp/browser/test-runner-errors.html'
}
},
noJsErrors: {
src: ['test/less/no-js-errors/*.less'],
options: {
helpers: 'test/browser/runner-no-js-errors-options.js',
specs: 'test/browser/runner-no-js-errors-spec.js',
outfile: 'tmp/browser/test-runner-no-js-errors.html'
}
},
browser: {
src: ['test/browser/less/*.less'],
options: {
helpers: 'test/browser/runner-browser-options.js',
specs: 'test/browser/runner-browser-spec.js',
outfile: 'tmp/browser/test-runner-browser.html'
}
},
relativeUrls: {
src: ['test/browser/less/relative-urls/*.less'],
options: {
helpers: 'test/browser/runner-relative-urls-options.js',
specs: 'test/browser/runner-relative-urls-spec.js',
outfile: 'tmp/browser/test-runner-relative-urls.html'
}
},
rootpath: {
src: ['test/browser/less/rootpath/*.less'],
options: {
helpers: 'test/browser/runner-rootpath-options.js',
specs: 'test/browser/runner-rootpath-spec.js',
outfile: 'tmp/browser/test-runner-rootpath.html'
}
},
rootpathRelative: {
src: ['test/browser/less/rootpath-relative/*.less'],
options: {
helpers: 'test/browser/runner-rootpath-relative-options.js',
specs: 'test/browser/runner-rootpath-relative-spec.js',
outfile: 'tmp/browser/test-runner-rootpath-relative.html'
}
},
production: {
src: ['test/browser/less/production/*.less'],
options: {
helpers: 'test/browser/runner-production-options.js',
specs: 'test/browser/runner-production-spec.js',
outfile: 'tmp/browser/test-runner-production.html'
}
},
modifyVars: {
src: ['test/browser/less/modify-vars/*.less'],
options: {
helpers: 'test/browser/runner-modify-vars-options.js',
specs: 'test/browser/runner-modify-vars-spec.js',
outfile: 'tmp/browser/test-runner-modify-vars.html'
}
}
},
// Clean the version of less built for the tests
clean: {
test: ['test/browser/less.js', 'tmp'],
"sourcemap-test": ['test/sourcemaps/*.css', 'test/sourcemaps/*.map']
}
});
// Load these plugins to provide the necessary tasks
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
// Actually load this plugin's task(s).
grunt.loadTasks('build/tasks');
// by default, run tests
grunt.registerTask('default', [
'test'
]);
// Release
grunt.registerTask('stable', [
'concat:stable',
'uglify:stable'
]);
// Run all browser tests
grunt.registerTask('browsertest', [
'browser',
'connect',
'jasmine'
]);
// setup a web server to run the browser tests in a browser rather than phantom
grunt.registerTask('browsertest-server', [
'shell:browsertest-server'
]);
// Create the browser version of less.js
grunt.registerTask('browser', [
'concat:browsertest'
]);
// Run all tests
grunt.registerTask('test', [
'clean',
'jshint',
'shell:test',
'browsertest'
]);
// generate a good test environment for testing sourcemaps
grunt.registerTask('sourcemap-test', [
'clean:sourcemap-test',
'shell:sourcemap-test'
]);
// Run benchmark
grunt.registerTask('benchmark', [
'shell:benchmark'
]);
// Readme.
grunt.registerTask('readme', [
'concat:readme'
]);
};

341
README.md
View File

@@ -1,20 +1,335 @@
less.js
=======
# [Less.js v1.5.0-b1](http://lesscss.org)
The **dynamic** stylesheet language.
<http://lesscss.org>
about
-----
> The **dynamic** stylesheet language. [http://lesscss.org](http://lesscss.org).
This is the JavaScript, and now official, stable version of LESS.
For more information on the language and usage visit [lesscss.org](http://lesscss.org). More information also available [in our wiki](https://github.com/less/less.js/wiki)
license
-------
## Getting Started
See `LICENSE` file.
Options for adding Less.js to your project:
> Copyright (c) 2009-2013 Alexis Sellier & The Core Less Team
* 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`
## Feature Highlights
LESS extends CSS with dynamic features such as:
* [nesting](#nesting)
* [variables](#variables)
* [operations](#operations)
* [mixins](#mixins)
* [extend](#extend) (selector inheritance)
To learn about the many other features Less.js has to offer please visit [http://lesscss.org](http://lesscss.org) and [the Less.js wiki][wiki]
### Examples
#### nesting
Take advantage of nesting to make code more readable and maintainable. This:
```less
.nav > li > a {
border: 1px solid #f5f5f5;
&:hover {
border-color: #ddd;
}
}
```
renders to:
```css
.nav > li > a {
border: 1px solid #f5f5f5;
}
.nav > li > a:hover {
border-color: #ddd;
}
```
#### variables
Updated commonly used values from a single location.
```less
// Variables ("inline" comments like this can be used)
@link-color: #428bca; // appears as "sea blue"
/* Or "block comments" that span
multiple lines, like this */
a {
color: @link-color; // use the variable in styles
}
```
Variables can also be used in `@import` statements, URLs, selector names, and more.
#### operations
Continuing with the same example above, we can use our variables even easier to maintain with _operations_, which enables the use of addition, subraction, multiplication and division in your styles:
```less
// Variables
@link-color: #428bca;
@link-color-hover: darken(@link-color, 10%);
// Styles
a {
color: @link-color;
}
a:hover {
color: @link-color-hover;
}
```
renders to:
```css
a {
color: #428bca;
}
a:hover {
color: #3071a9;
}
```
#### mixins
##### "implicit" mixins
Mixins enable you to apply the styles of one selector inside another selector like this:
```less
// Any "regular" class...
.link {
color: @link-color;
}
a {
font-weight: bold;
.link; // ...can be used as an "implicit" mixin
}
```
renders to:
```css
.link {
color: #428bca;
}
a {
font-weight: bold;
color: #428bca;
}
```
So any selector can be an "implicit mixin". We'll show you a DRYer way to do this below.
##### parametric mixins
Mixins can also accept parameters:
```less
// Transition mixin
.transition(@transition) {
-webkit-transition: @transition;
-moz-transition: @transition;
-o-transition: @transition;
transition: @transition;
}
```
used like this:
```less
a {
font-weight: bold;
color: @link-color;
.transition(color .2s ease-in-out);
// Hover state
&:hover {
color: @link-color-hover;
}
}
```
renders to:
```css
a {
font-weight: bold;
color: #428bca;
-webkit-transition: color 0.2s ease-in-out;
-moz-transition: color 0.2s ease-in-out;
-o-transition: color 0.2s ease-in-out;
transition: color 0.2s ease-in-out;
}
a:hover {
color: #3071a9;
}
```
#### extend
The `extend` feature can be thought of as the _inverse_ of mixins. It accomplishes the goal of "borrowing styles", but rather than copying all the rules of _Selector A_ over to _Selector B_, `extend` copies the name of the _inheriting selector_ (_Selector B_) over to the _extending selector_ (_Selector A_). So continuing with the example used for [mixins](#mixins) above, extend works like this:
```less
.link {
color: @link-color;
}
a:extend(.link) {
font-weight: bold;
}
// Can also be written as
a {
&:extend(.link);
font-weight: bold;
}
```
renders to:
```css
.link, a {
color: #428bca;
}
```
## Usage
### Compiling and Parsing
Invoke the compiler from node:
```javascript
var less = require('less');
less.render('.class { width: (1 + 1) }', function (e, css) {
console.log(css);
});
```
Outputs:
```css
.class {
width: 2;
}
```
You may also manually invoke the parser and compiler:
```javascript
var parser = new(less.Parser);
parser.parse('.class { width: (1 + 1) }', function (err, tree) {
if (err) { return console.error(err) }
console.log(tree.toCSS());
});
```
### Configuration
You may also pass options to the compiler:
```javascript
var parser = new(less.Parser)({
paths: ['.', './src/less'], // Specify search paths for @import directives
filename: 'style.less' // Specify a filename, for better error messages
});
parser.parse('.class { width: (1 + 1) }', function (e, tree) {
tree.toCSS({ compress: true }); // Minify CSS output
});
```
## More information
For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org) or [the less wiki][wiki].
Here are other resources for using Less.js:
* [stackoverflow.com][so] is a great place to get answers about Less.
* [node.js tools](https://github.com/less/less.js/wiki/Converting-LESS-to-CSS) for converting Less to CSS
* [GUI compilers for Less](https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js)
* [Less.js Issues][issues] for reporting bugs
## 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/).
### 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].
### Development
#### Install Less.js
Start by either [downloading this project][download] manually, or in the command line:
```shell
git clone https://github.com/less/less.js.git "less"
```
and then `cd less`.
#### Install dependencies
To install all the dependencies for less development, run:
```shell
npm install
```
If you haven't run grunt before, install grunt-cli globally so you can just run `grunt`
```shell
npm install grunt-cli -g
```
You should now be able to build Less.js, run tests, benchmarking, and other tasks listed in the Gruntfile.
## Using Less.js Grunt
Tests, benchmarking and building is done using Grunt `~0.4.1`. If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to install and use Grunt plugins, which are necessary for development with Less.js.
The Less.js [Gruntfile](Gruntfile.js) is configured with the following "convenience tasks" :
#### test - `grunt`
Runs jshint, nodeunit and headless jasmine tests using [phantomjs](http://code.google.com/p/phantomjs/). You must have phantomjs installed for the jasmine tests to run.
#### test - `grunt benchmark`
Runs the benchmark suite.
#### build for testing browser - 'grunt browser'
This builds less.js and puts it in 'test/browser/less.js'
#### build - `grunt stable | grunt beta | grunt alpha`
Builds Less.js from from the `/lib/less` source files. This is done by the developer releasing a new release, do not do this if you are creating a pull request.
#### readme - `grunt readme`
Build the README file from [a template](build/README.md) to ensure that metadata is up-to-date and (more likely to be) correct.
Please review the [Gruntfile](Gruntfile.js) to become acquainted with the other available tasks.
**Please note** that if you have any issues installing dependencies or running any of the Gruntfile commands, please make sure to uninstall any previous versions, both in the local node_modules directory, and clear your global npm cache, and then try running `npm install` again. After that if you still have issues, please let us know about it so we can help.
## Release History
See the [changelog](CHANGELOG.md)
## [License](LICENSE)
Copyright (c) 2009-2013 [Alexis Sellier](http://cloudhead.io/) & The Core Less Team
Licensed under the [Apache License](LICENSE).
[so]: http://stackoverflow.com/questions/tagged/twitter-bootstrap+less "StackOverflow.com"
[issues]: https://github.com/less/less.js/issues "GitHub Issues for Less.js"
[wiki]: https://github.com/less/less.js/wiki "The official wiki for Less.js"
[download]: https://github.com/less/less.js/zipball/master "Download Less.js"

View File

@@ -10,33 +10,32 @@ if (process.argv[2]) { file = path.join(process.cwd(), process.argv[2]) }
fs.readFile(file, 'utf8', function (e, data) {
var tree, css, start, end, total;
sys.puts("Benchmarking...\n", path.basename(file) + " (" +
console.log("Benchmarking...\n", path.basename(file) + " (" +
parseInt(data.length / 1024) + " KB)", "");
start = new(Date);
new(less.Parser)({ optimization: 2 }).parse(data, function (err, tree) {
end = new(Date);
end = new Date();
total = end - start;
sys.puts("Parsing: " +
console.log("Parsing: " +
total + " ms (" +
parseInt(1000 / total *
data.length / 1024) + " KB\/s)");
Number(1000 / total * data.length / 1024) + " KB\/s)");
start = new(Date);
start = new Date();
css = tree.toCSS();
end = new(Date);
end = new Date();
sys.puts("Generation: " + (end - start) + " ms (" +
console.log("Generation: " + (end - start) + " ms (" +
parseInt(1000 / (end - start) *
data.length / 1024) + " KB\/s)");
total += end - start;
sys.puts("Total: " + total + "ms (" +
parseInt(1000 / total * data.length / 1024) + " KB/s)");
console.log("Total: " + total + "ms (" +
Number(1000 / total * data.length / 1024) + " KB/s)");
if (err) {
less.writeError(err);

View File

@@ -20,6 +20,7 @@ var options = {
paths: [],
color: true,
strictImports: false,
insecure: false,
rootpath: '',
relativeUrls: false,
ieCompat: true,
@@ -35,7 +36,7 @@ process.on('exit', function() { process.reallyExit(currentErrorcode) });
var checkArgFunc = function(arg, option) {
if (!option) {
sys.puts(arg + " option requires a parameter");
console.log(arg + " option requires a parameter");
continueProcessing = false;
return false;
}
@@ -45,7 +46,7 @@ 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) {
sys.puts(" 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;
}
@@ -53,6 +54,7 @@ var checkBooleanArg = function(arg) {
};
var warningMessages = "";
var sourceMapFileInline = false;
args = args.filter(function (arg) {
var match;
@@ -68,7 +70,7 @@ args = args.filter(function (arg) {
switch (arg) {
case 'v':
case 'version':
sys.puts("lessc " + less.version.join('.') + " (LESS Compiler) [JavaScript]");
console.log("lessc " + less.version.join('.') + " (LESS Compiler) [JavaScript]");
continueProcessing = false;
case 'verbose':
options.verbose = true;
@@ -92,6 +94,9 @@ args = args.filter(function (arg) {
case 'compress':
options.compress = true;
break;
case 'insecure':
options.insecure = true;
break;
case 'M':
case 'depends':
options.depends = true;
@@ -150,7 +155,16 @@ args = args.filter(function (arg) {
options.sourceMapRootpath = match[2];
}
break;
case 'source-map-inline':
case 'source-map-basepath':
if (checkArgFunc(arg, match[2])) {
options.sourceMapBasepath = match[2];
}
break;
case 'source-map-map-inline':
sourceMapFileInline = true;
options.sourceMap = true;
break;
case 'source-map-less-inline':
options.outputSourceFiles = true;
break;
case 'rp':
@@ -193,15 +207,15 @@ if (output) {
options.sourceMapOutputFilename = output;
output = path.resolve(process.cwd(), output);
if (warningMessages) {
sys.puts(warningMessages);
console.log(warningMessages);
}
}
options.sourceMapBasepath = process.cwd();
options.sourceMapBasepath = options.sourceMapBasepath || (input ? path.dirname(input) : process.cwd());
if (options.sourceMap === true) {
if (!output) {
sys.puts("the sourcemap option only has an optional filename if the css filename is given");
if (!output && !sourceMapFileInline) {
console.log("the sourcemap option only has an optional filename if the css filename is given");
return;
}
options.sourceMapFullFilename = options.sourceMapOutputFilename + ".map";
@@ -209,8 +223,8 @@ if (options.sourceMap === true) {
}
if (! input) {
sys.puts("lessc: no input files");
sys.puts("");
console.log("lessc: no input files");
console.log("");
require('../lib/less/lessc_helper').printUsage();
currentErrorcode = 1;
return;
@@ -238,15 +252,17 @@ if (options.depends) {
sys.print(outputbase + ": ");
}
var writeSourceMap = function(output) {
var filename = options.sourceMapFullFilename || options.sourceMap;
ensureDirectory(filename);
fs.writeFileSync(filename, output, 'utf8');
};
if (!sourceMapFileInline) {
var writeSourceMap = function(output) {
var filename = options.sourceMapFullFilename || options.sourceMap;
ensureDirectory(filename);
fs.writeFileSync(filename, output, 'utf8');
};
}
var parseLessFile = function (e, data) {
if (e) {
sys.puts("lessc: " + e.message);
console.log("lessc: " + e.message);
currentErrorcode = 1;
return;
}

View File

@@ -1,5 +1,18 @@
{
"name": "less",
"version": "1.4.2",
"main": "./dist/less-1.4.2.js"
"main": "./dist/less-1.4.2.js",
"ignore": [
"**/.*",
"benchmark",
"bin",
"build",
"lib",
"test",
"*.md",
"LICENSE",
"Gruntfile.js",
"package.json",
"bower.json"
]
}

335
build/README.md Normal file
View File

@@ -0,0 +1,335 @@
# [Less.js v<%= pkg.version %>](http://lesscss.org)
> The **dynamic** stylesheet language. [http://lesscss.org](http://lesscss.org).
This is the JavaScript, and now official, stable version of LESS.
## Getting Started
Options for adding Less.js to your project:
* 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`
## Feature Highlights
LESS extends CSS with dynamic features such as:
* [nesting](#nesting)
* [variables](#variables)
* [operations](#operations)
* [mixins](#mixins)
* [extend](#extend) (selector inheritance)
To learn about the many other features Less.js has to offer please visit [http://lesscss.org](http://lesscss.org) and [the Less.js wiki][wiki]
### Examples
#### nesting
Take advantage of nesting to make code more readable and maintainable. This:
```less
.nav > li > a {
border: 1px solid #f5f5f5;
&:hover {
border-color: #ddd;
}
}
```
renders to:
```css
.nav > li > a {
border: 1px solid #f5f5f5;
}
.nav > li > a:hover {
border-color: #ddd;
}
```
#### variables
Updated commonly used values from a single location.
```less
// Variables ("inline" comments like this can be used)
@link-color: #428bca; // appears as "sea blue"
/* Or "block comments" that span
multiple lines, like this */
a {
color: @link-color; // use the variable in styles
}
```
Variables can also be used in `@import` statements, URLs, selector names, and more.
#### operations
Continuing with the same example above, we can use our variables even easier to maintain with _operations_, which enables the use of addition, subraction, multiplication and division in your styles:
```less
// Variables
@link-color: #428bca;
@link-color-hover: darken(@link-color, 10%);
// Styles
a {
color: @link-color;
}
a:hover {
color: @link-color-hover;
}
```
renders to:
```css
a {
color: #428bca;
}
a:hover {
color: #3071a9;
}
```
#### mixins
##### "implicit" mixins
Mixins enable you to apply the styles of one selector inside another selector like this:
```less
// Any "regular" class...
.link {
color: @link-color;
}
a {
font-weight: bold;
.link; // ...can be used as an "implicit" mixin
}
```
renders to:
```css
.link {
color: #428bca;
}
a {
font-weight: bold;
color: #428bca;
}
```
So any selector can be an "implicit mixin". We'll show you a DRYer way to do this below.
##### parametric mixins
Mixins can also accept parameters:
```less
// Transition mixin
.transition(@transition) {
-webkit-transition: @transition;
-moz-transition: @transition;
-o-transition: @transition;
transition: @transition;
}
```
used like this:
```less
a {
font-weight: bold;
color: @link-color;
.transition(color .2s ease-in-out);
// Hover state
&:hover {
color: @link-color-hover;
}
}
```
renders to:
```css
a {
font-weight: bold;
color: #428bca;
-webkit-transition: color 0.2s ease-in-out;
-moz-transition: color 0.2s ease-in-out;
-o-transition: color 0.2s ease-in-out;
transition: color 0.2s ease-in-out;
}
a:hover {
color: #3071a9;
}
```
#### extend
The `extend` feature can be thought of as the _inverse_ of mixins. It accomplishes the goal of "borrowing styles", but rather than copying all the rules of _Selector A_ over to _Selector B_, `extend` copies the name of the _inheriting selector_ (_Selector B_) over to the _extending selector_ (_Selector A_). So continuing with the example used for [mixins](#mixins) above, extend works like this:
```less
.link {
color: @link-color;
}
a:extend(.link) {
font-weight: bold;
}
// Can also be written as
a {
&:extend(.link);
font-weight: bold;
}
```
renders to:
```css
.link, a {
color: #428bca;
}
```
## Usage
### Compiling and Parsing
Invoke the compiler from node:
```javascript
var less = require('less');
less.render('.class { width: (1 + 1) }', function (e, css) {
console.log(css);
});
```
Outputs:
```css
.class {
width: 2;
}
```
You may also manually invoke the parser and compiler:
```javascript
var parser = new(less.Parser);
parser.parse('.class { width: (1 + 1) }', function (err, tree) {
if (err) { return console.error(err) }
console.log(tree.toCSS());
});
```
### Configuration
You may also pass options to the compiler:
```javascript
var parser = new(less.Parser)({
paths: ['.', './src/less'], // Specify search paths for @import directives
filename: 'style.less' // Specify a filename, for better error messages
});
parser.parse('.class { width: (1 + 1) }', function (e, tree) {
tree.toCSS({ compress: true }); // Minify CSS output
});
```
## More information
For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org) or [the less wiki][wiki].
Here are other resources for using Less.js:
* [stackoverflow.com][so] is a great place to get answers about Less.
* [node.js tools](https://github.com/less/less.js/wiki/Converting-LESS-to-CSS) for converting Less to CSS
* [GUI compilers for Less](https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js)
* [Less.js Issues][issues] for reporting bugs
## 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/).
### 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].
### Development
#### Install Less.js
Start by either [downloading this project][download] manually, or in the command line:
```shell
git clone https://github.com/less/less.js.git "less"
```
and then `cd less`.
#### Install dependencies
To install all the dependencies for less development, run:
```shell
npm install
```
If you haven't run grunt before, install grunt-cli globally so you can just run `grunt`
```shell
npm install grunt-cli -g
```
You should now be able to build Less.js, run tests, benchmarking, and other tasks listed in the Gruntfile.
## Using Less.js Grunt
Tests, benchmarking and building is done using Grunt `<%= pkg.devDependencies.grunt %>`. If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to install and use Grunt plugins, which are necessary for development with Less.js.
The Less.js [Gruntfile](Gruntfile.js) is configured with the following "convenience tasks" :
#### test - `grunt`
Runs jshint, nodeunit and headless jasmine tests using [phantomjs](http://code.google.com/p/phantomjs/). You must have phantomjs installed for the jasmine tests to run.
#### test - `grunt benchmark`
Runs the benchmark suite.
#### build for testing browser - 'grunt browser'
This builds less.js and puts it in 'test/browser/less.js'
#### build - `grunt stable | grunt beta | grunt alpha`
Builds Less.js from from the `/lib/less` source files. This is done by the developer releasing a new release, do not do this if you are creating a pull request.
#### readme - `grunt readme`
Build the README file from [a template](build/README.md) to ensure that metadata is up-to-date and (more likely to be) correct.
Please review the [Gruntfile](Gruntfile.js) to become acquainted with the other available tasks.
**Please note** that if you have any issues installing dependencies or running any of the Gruntfile commands, please make sure to uninstall any previous versions, both in the local node_modules directory, and clear your global npm cache, and then try running `npm install` again. After that if you still have issues, please let us know about it so we can help.
## Release History
See the [changelog](CHANGELOG)
## [License](LICENSE)
Copyright (c) 2009-<%= grunt.template.today("yyyy") %> [Alexis Sellier](http://cloudhead.io/) & The Core Less Team
Licensed under the [Apache License](LICENSE).
[so]: http://stackoverflow.com/questions/tagged/twitter-bootstrap+less "StackOverflow.com"
[issues]: https://github.com/less/less.js/issues "GitHub Issues for Less.js"
[wiki]: https://github.com/less/less.js/wiki "The official wiki for Less.js"
[download]: https://github.com/less/less.js/zipball/master "Download Less.js"

147
build/build.yml Normal file
View File

@@ -0,0 +1,147 @@
###
# NOTICE:
# this file is specifically for controlling
# paths for Less.js source files, as well as
# the order in which source files are
# concatenated.
#
# Please do not add paths for anything else
# to this file. All other paths for testing,
# benchmarking and so on should be controlled
# in the Gruntfile.
###
# Less.js Lib
lib: lib/less
# =================================
# General
# =================================
prepend:
browser: ['build/require.js', 'build/browser-header.js']
rhino: build/require-rhino.js
append:
amd: build/amd.js
browser: <%= build.lib %>/browser.js
rhino: <%= build.lib %>/rhino.js
# =================================
# Core less files
# =================================
# <%= build.less.* %>
less:
parser : <%= build.lib %>/parser.js
functions : <%= build.lib %>/functions.js
colors : <%= build.lib %>/colors.js
tree : <%= build.lib %>/tree.js
treedir : <%= build.lib %>/tree/*.js # glob all files in ./lib/less/tree directory
env : <%= build.lib %>/env.js
visitor : <%= build.lib %>/visitor.js
import_visitor : <%= build.lib %>/import-visitor.js
join : <%= build.lib %>/join-selector-visitor.js
to_css_visitor : <%= build.lib %>/to-css-visitor.js
extend_visitor : <%= build.lib %>/extend-visitor.js
browser : <%= build.lib %>/browser.js
source_map_output: <%= build.lib %>/source-map-output.js
# =================================
# Browser build
# =================================
# <%= build.browser %>
browser:
# prepend utils
- <%= build.prepend.browser %>
# core
- <%= build.less.parser %>
- <%= build.less.functions %>
- <%= build.less.colors %>
- <%= build.less.tree %>
- <%= build.less.treedir %> # glob all files
- <%= build.less.env %>
- <%= build.less.visitor %>
- <%= build.less.import_visitor %>
- <%= build.less.join %>
- <%= build.less.to_css_visitor %>
- <%= build.less.extend_visitor %>
- <%= build.less.source_map_output %>
# append browser-specific code
- <%= build.append.browser %>
- <%= build.append.amd %>
# =================================
# Rhino build
# =================================
# <%= build.rhino %>
rhino:
# prepend utils
- <%= build.prepend.rhino %>
# core
- <%= build.less.parser %>
- <%= build.less.env %>
- <%= build.less.visitor %>
- <%= build.less.import_visitor %>
- <%= build.less.join %>
- <%= build.less.to_css_visitor %>
- <%= build.less.extend_visitor %>
- <%= build.less.functions %>
- <%= build.less.colors %>
- <%= build.less.tree %>
- <%= build.less.treedir %> # glob all files
- <%= build.less.source_map_output %>
# append rhino-specific code
- <%= build.append.rhino %>
# =================================
# Tree files
# =================================
# <%= build.tree %>
# Technically listing the array out this way isn't
# necessary since we can glob the files in alphabetical
# order anyway. But this gives you control over the order
# the files are used, and allows targeting of individual
# files directly in the Gruntfile. But be we can just
# remove this if files can be concatenated in any order.
tree:
- <%= build.lib %>/tree/alpha.js
- <%= build.lib %>/tree/anonymous.js
- <%= build.lib %>/tree/assignment.js
- <%= build.lib %>/tree/call.js
- <%= build.lib %>/tree/color.js
- <%= build.lib %>/tree/comment.js
- <%= build.lib %>/tree/condition.js
- <%= build.lib %>/tree/dimension.js
- <%= build.lib %>/tree/directive.js
- <%= build.lib %>/tree/element.js
- <%= build.lib %>/tree/expression.js
- <%= build.lib %>/tree/extend.js
- <%= build.lib %>/tree/import.js
- <%= build.lib %>/tree/javascript.js
- <%= build.lib %>/tree/keyword.js
- <%= build.lib %>/tree/media.js
- <%= build.lib %>/tree/mixin.js
- <%= build.lib %>/tree/negative.js
- <%= build.lib %>/tree/operation.js
- <%= build.lib %>/tree/paren.js
- <%= build.lib %>/tree/quoted.js
- <%= build.lib %>/tree/rule.js
- <%= build.lib %>/tree/ruleset.js
- <%= build.lib %>/tree/selector.js
- <%= build.lib %>/tree/unicode-descriptor.js
- <%= build.lib %>/tree/url.js
- <%= build.lib %>/tree/value.js
- <%= build.lib %>/tree/variable.js

View File

@@ -1,9 +0,0 @@
/*
* LESS - Leaner CSS v@VERSION
* http://lesscss.org
*
* Copyright (c) 2009-2013, Alexis Sellier
* Licensed under the Apache 2.0 License.
*
* @licence
*/

1
build/tasks/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# Reserved for specialized Less.js tasks.

6888
dist/less-1.5.0-b4.js vendored Normal file

File diff suppressed because it is too large Load Diff

13
dist/less-1.5.0-b4.min.js vendored Normal file

File diff suppressed because one or more lines are too long

398
dist/less-1.5.0.js vendored
View File

@@ -1,24 +1,28 @@
/*
* LESS - Leaner CSS v1.5.0
* http://lesscss.org
*
* Copyright (c) 2009-2013, Alexis Sellier
* Licensed under the Apache 2.0 License.
*
* @licence
*/
(function (window, undefined) {
//
/*!
* LESS - Leaner CSS v1.5.0
* http://lesscss.org
*
* Copyright (c) 2009-2013, Alexis Sellier <self@cloudhead.net>
* Licensed under the Apache v2 License.
*
* @licence
*/
(function (window, undefined) {//
// Stub out `require` in the browser
//
function require(arg) {
return window.less[arg.split('/')[1]];
};
if (typeof(window.less) === 'undefined' || typeof(window.less.nodeType) !== 'undefined') { window.less = {}; }
less = window.less;
tree = window.less.tree = {};
less.mode = 'browser';
var less, tree;
// Node.js does not have a header file added which defines less
@@ -69,7 +73,8 @@ less.Parser = function Parser(env) {
furthest, // furthest index the parser has gone to
chunks, // chunkified input
current, // index of current chunk, in `input`
parser;
parser,
rootFilename = env && env.filename;
// Top parser on an import tree must be sure there is one "env"
// which will then be passed around by reference.
@@ -91,13 +96,13 @@ less.Parser = function Parser(env) {
var fileParsedFunc = function (e, root, fullPath) {
parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue
var importedPreviously = fullPath in parserImports.files;
var importedPreviously = fullPath in parserImports.files || fullPath === rootFilename;
parserImports.files[fullPath] = root; // Store the root
if (e && !parserImports.error) { parserImports.error = e; }
callback(e, root, importedPreviously);
callback(e, root, importedPreviously, fullPath);
};
if (less.Parser.importer) {
@@ -492,7 +497,8 @@ less.Parser = function Parser(env) {
outputFilename: options.sourceMapOutputFilename,
sourceMapBasepath: options.sourceMapBasepath,
sourceMapRootpath: options.sourceMapRootpath,
outputSourceFiles: options.outputSourceFiles
outputSourceFiles: options.outputSourceFiles,
sourceMapGenerator: options.sourceMapGenerator
});
}
@@ -551,10 +557,10 @@ less.Parser = function Parser(env) {
e = new(LessError)(e, env);
}
callback(e);
return callback(e);
}
else {
callback(null, root);
return callback(null, root);
}
};
@@ -562,7 +568,7 @@ less.Parser = function Parser(env) {
new tree.importVisitor(this.imports, finish)
.run(root);
} else {
finish();
return finish();
}
},
@@ -678,12 +684,11 @@ less.Parser = function Parser(env) {
var k;
if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
if (tree.colors.hasOwnProperty(k)) {
// detect named color
return new(tree.Color)(tree.colors[k].slice(1));
} else {
return new(tree.Keyword)(k);
var color = tree.Color.fromKeyword(k);
if (color) {
return color;
}
return new(tree.Keyword)(k);
}
},
@@ -1262,7 +1267,7 @@ less.Parser = function Parser(env) {
}
if ((op = $(/^[|~*$^]?=/))) {
val = $(this.entities.quoted) || $(/^[\w-]+/) || $(this.entities.variableCurly);
val = $(this.entities.quoted) || $(/^[0-9]+%/) || $(/^[\w-]+/) || $(this.entities.variableCurly);
}
expect(']');
@@ -1298,7 +1303,7 @@ less.Parser = function Parser(env) {
$(this.comments);
if (! $(',')) { break; }
if (s.condition) {
error("Guards are only currently allowed on a single selector");
error("Guards are only currently allowed on a single selector.");
}
$(this.comments);
}
@@ -1422,7 +1427,7 @@ less.Parser = function Parser(env) {
var e, p, nodes = [];
do {
if (e = $(this.entities.keyword)) {
if (e = ($(this.entities.keyword) || $(this.entities.variable))) {
nodes.push(e);
} else if ($('(')) {
p = $(this.property);
@@ -1487,7 +1492,7 @@ less.Parser = function Parser(env) {
//
directive: function () {
var name, value, rules, nonVendorSpecificName,
hasBlock, hasIdentifier, hasExpression;
hasBlock, hasIdentifier, hasExpression, identifier;
if (input.charAt(i) !== '@') { return; }
@@ -1529,6 +1534,7 @@ less.Parser = function Parser(env) {
case "@right-bottom":
hasBlock = true;
break;
case "@host":
case "@page":
case "@document":
case "@supports":
@@ -1542,7 +1548,10 @@ less.Parser = function Parser(env) {
}
if (hasIdentifier) {
name += " " + ($(/^[^{]+/) || '').trim();
identifier = ($(/^[^{]+/) || '').trim();
if (identifier) {
name += " " + identifier;
}
}
if (hasBlock)
@@ -1635,7 +1644,7 @@ less.Parser = function Parser(env) {
var a, b, index = i, condition;
if (a = $(this.condition)) {
while ($(',') && (b = $(this.condition))) {
while (peek(/^,\s*(not\s*)?\(/) && $(',') && (b = $(this.condition))) {
condition = new(tree.Condition)('or', condition || a, b, index);
}
return condition || a;
@@ -1647,7 +1656,7 @@ less.Parser = function Parser(env) {
if ($(/^not/)) { negate = true; }
expect('(');
if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
if (op = $(/^(?:>=|=<|[<=>])/)) {
if (op = $(/^(?:>=|<=|=<|[<=>])/)) {
if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
c = new(tree.Condition)(op, a, b, index, negate);
} else {
@@ -1720,6 +1729,7 @@ less.Parser = function Parser(env) {
};
};
(function (tree) {
tree.functions = {
@@ -1955,6 +1965,9 @@ tree.functions = {
return new(tree.Quoted)('"' + str + '"', str);
},
unit: function (val, unit) {
if(!(val instanceof tree.Dimension)) {
throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
}
return new(tree.Dimension)(val.value, unit ? unit.toCSS() : "");
},
convert: function (val, unit) {
@@ -2042,7 +2055,16 @@ tree.functions = {
},
color: function (n) {
if (n instanceof tree.Quoted) {
return new(tree.Color)(n.value.slice(1));
var colorCandidate = n.value,
returnColor;
returnColor = tree.Color.fromKeyword(colorCandidate);
if (returnColor) {
return returnColor;
}
if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/.test(colorCandidate)) {
return new(tree.Color)(colorCandidate.slice(1));
}
throw { type: "Argument", message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF" };
} else {
throw { type: "Argument", message: "argument must be a string" };
}
@@ -2142,10 +2164,17 @@ tree.functions = {
},
shade: function(color, amount) {
return this.mix(this.rgb(0, 0, 0), color, amount);
},
},
extract: function(values, index) {
index = index.value - 1; // (1-based index)
return values.value[index];
index = index.value - 1; // (1-based index)
// handle non-array values as an array of length 1
// return 'undefined' if index is invalid
return Array.isArray(values.value)
? values.value[index] : Array(values)[index];
},
length: function(values) {
var n = Array.isArray(values.value) ? values.value.length : 1;
return new tree.Dimension(n);
},
"data-uri": function(mimetypeNode, filePathNode) {
@@ -2207,9 +2236,6 @@ tree.functions = {
}
return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
} else if (!this.env.silent) {
// if explicitly disabled (via --no-ie-compat on CLI, or env.ieCompat === false), merely warn
console.warn("WARNING: Embedding %s (%dKB) exceeds IE8's data-uri size limit of %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
}
}
@@ -2380,6 +2406,7 @@ tree.functionCall = function(env, currentFileInfo) {
tree.functionCall.prototype = tree.functions;
})(require('./tree'));
(function (tree) {
tree.colors = {
'aliceblue':'#f0f8ff',
@@ -2522,7 +2549,6 @@ tree.functionCall.prototype = tree.functions;
'teal':'#008080',
'thistle':'#d8bfd8',
'tomato':'#ff6347',
// 'transparent':'rgba(0,0,0,0)',
'turquoise':'#40e0d0',
'violet':'#ee82ee',
'wheat':'#f5deb3',
@@ -2532,6 +2558,7 @@ tree.functionCall.prototype = tree.functions;
'yellowgreen':'#9acd32'
};
})(require('./tree'));
(function (tree) {
tree.debugInfo = function(env, ctx, lineSeperator) {
@@ -2585,8 +2612,11 @@ tree.jsify = function (obj) {
tree.toCSS = function (env) {
var strs = [];
this.genCSS(env, {
add: function(chunk, node) {
add: function(chunk, fileInfo, index) {
strs.push(chunk);
},
isEmpty: function () {
return strs.length === 0;
}
});
return strs.join('');
@@ -2607,6 +2637,7 @@ tree.outputRuleset = function (env, output, rules) {
};
})(require('./tree'));
(function (tree) {
tree.Alpha = function (val) {
@@ -2636,10 +2667,14 @@ tree.Alpha.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Anonymous = function (string) {
tree.Anonymous = function (string, index, currentFileInfo, mapLines) {
this.value = string.value || string;
this.index = index;
this.mapLines = mapLines;
this.currentFileInfo = currentFileInfo;
};
tree.Anonymous.prototype = {
type: "Anonymous",
@@ -2659,12 +2694,13 @@ tree.Anonymous.prototype = {
return left < right ? -1 : 1;
},
genCSS: function (env, output) {
output.add(this.value);
output.add(this.value, this.currentFileInfo, this.index, this.mapLines);
},
toCSS: tree.toCSS
};
})(require('../tree'));
(function (tree) {
tree.Assignment = function (key, val) {
@@ -2694,6 +2730,7 @@ tree.Assignment.prototype = {
};
})(require('../tree'));
(function (tree) {
//
@@ -2764,6 +2801,7 @@ tree.Call.prototype = {
};
})(require('../tree'));
(function (tree) {
//
// RGB Colors - #ff0014, #eee
@@ -2788,6 +2826,9 @@ tree.Color = function (rgb, a) {
}
this.alpha = typeof(a) === 'number' ? a : 1;
};
var transparentKeyword = "transparent";
tree.Color.prototype = {
type: "Color",
eval: function () { return this; },
@@ -2804,6 +2845,9 @@ tree.Color.prototype = {
// which has better compatibility with older browsers.
// Values are capped between `0` and `255`, rounded and zero-padded.
if (this.alpha < 1.0) {
if (this.alpha === 0 && this.isTransparentKeyword) {
return transparentKeyword;
}
return "rgba(" + this.rgb.map(function (c) {
return Math.round(c);
}).concat(this.alpha).join(',' + (compress ? '' : ' ')) + ")";
@@ -2922,8 +2966,21 @@ tree.Color.prototype = {
}
};
tree.Color.fromKeyword = function(keyword) {
if (tree.colors.hasOwnProperty(keyword)) {
// detect named color
return new(tree.Color)(tree.colors[keyword].slice(1));
}
if (keyword === transparentKeyword) {
var transparent = new(tree.Color)([0, 0, 0], 0);
transparent.isTransparentKeyword = true;
return transparent;
}
};
})(require('../tree'));
(function (tree) {
tree.Comment = function (value, silent, index, currentFileInfo) {
@@ -2952,6 +3009,7 @@ tree.Comment.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Condition = function (op, l, r, i, negate) {
@@ -2990,8 +3048,8 @@ tree.Condition.prototype = {
index: i };
}
switch (result) {
case -1: return op === '<' || op === '=<';
case 0: return op === '=' || op === '>=' || op === '=<';
case -1: return op === '<' || op === '=<' || op === '<=';
case 0: return op === '=' || op === '>=' || op === '=<' || op === '<=';
case 1: return op === '>' || op === '>=';
}
}
@@ -3001,6 +3059,7 @@ tree.Condition.prototype = {
};
})(require('../tree'));
(function (tree) {
//
@@ -3314,6 +3373,7 @@ tree.Unit.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Directive = function (name, value, index, currentFileInfo) {
@@ -3374,6 +3434,7 @@ tree.Directive.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Element = function (combinator, value, index, currentFileInfo) {
@@ -3478,6 +3539,7 @@ tree.Combinator.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Expression = function (value) { this.value = value; };
@@ -3530,6 +3592,7 @@ tree.Expression.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Extend = function Extend(selector, option, index) {
@@ -3562,9 +3625,16 @@ tree.Extend.prototype = {
},
findSelfSelectors: function (selectors) {
var selfElements = [],
i;
i,
selectorElements;
for(i = 0; i < selectors.length; i++) {
selectorElements = selectors[i].elements;
// duplicate the logic in genCSS function inside the selector node.
// future TODO - move both logics into the selector joiner visitor
if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === "") {
selectorElements[0].combinator.value = ' ';
}
selfElements = selfElements.concat(selectors[i].elements);
}
@@ -3573,6 +3643,7 @@ tree.Extend.prototype = {
};
})(require('../tree'));
(function (tree) {
//
// CSS @import node
@@ -3668,7 +3739,8 @@ tree.Import.prototype = {
if (this.skip) { return []; }
if (this.options.inline) {
var contents = new(tree.Anonymous)(this.root);
//todo needs to reference css file not import
var contents = new(tree.Anonymous)(this.root, 0, {filename: this.importedFilename}, true);
return this.features ? new(tree.Media)([contents], this.features.value) : [contents];
} else if (this.css) {
var newImport = new(tree.Import)(this.evalPath(env), features, this.options, this.index);
@@ -3687,6 +3759,7 @@ tree.Import.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.JavaScript = function (string, index, escaped) {
@@ -3740,6 +3813,7 @@ tree.JavaScript.prototype = {
})(require('../tree'));
(function (tree) {
tree.Keyword = function (value) { this.value = value; };
@@ -3763,6 +3837,7 @@ tree.True = new(tree.Keyword)('true');
tree.False = new(tree.Keyword)('false');
})(require('../tree'));
(function (tree) {
tree.Media = function (value, features, index, currentFileInfo) {
@@ -3911,6 +3986,7 @@ tree.Media.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.mixin = {};
@@ -3954,9 +4030,17 @@ tree.mixin.Call.prototype = {
try {
if (!(mixin instanceof tree.mixin.Definition)) {
mixin = new tree.mixin.Definition("", [], mixin.rules, null, false);
mixin.originalRuleset = mixins[m].originalRuleset || mixins[m];
}
//if (this.important) {
// isImportant = env.isImportant;
// env.isImportant = true;
//}
Array.prototype.push.apply(
rules, mixin.eval(env, args, this.important).rules);
//if (this.important) {
// env.isImportant = isImportant;
//}
} catch (e) {
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
}
@@ -4113,20 +4197,22 @@ tree.mixin.Definition.prototype = {
frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
rules = important ?
this.parent.makeImportant.apply(this).rules : this.rules.slice(0);
rules = this.rules.slice(0);
ruleset = new(tree.Ruleset)(null, rules).eval(new(tree.evalEnv)(env,
[this, frame].concat(mixinFrames)));
ruleset = new(tree.Ruleset)(null, rules);
ruleset.originalRuleset = this;
ruleset = ruleset.eval(new(tree.evalEnv)(env, [this, frame].concat(mixinFrames)));
if (important) {
ruleset = this.parent.makeImportant.apply(ruleset);
}
return ruleset;
},
matchCondition: function (args, env) {
if (this.condition && !this.condition.eval(
new(tree.evalEnv)(env,
[this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])]
.concat(env.frames)))) {
[this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])] // the parameter variables
.concat(this.frames) // the parent namespace/mixin frames
.concat(env.frames)))) { // the current environment frames
return false;
}
return true;
@@ -4137,7 +4223,8 @@ tree.mixin.Definition.prototype = {
if (! this.variadic) {
if (argsLength < this.required) { return false; }
if (argsLength > this.params.length) { return false; }
if ((this.required > 0) && (argsLength > this.params.length)) { return false; }
} else {
if (argsLength < (this.required - 1)) { return false; }
}
len = Math.min(argsLength, this.arity);
@@ -4154,6 +4241,7 @@ tree.mixin.Definition.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Negative = function (node) {
@@ -4178,6 +4266,7 @@ tree.Negative.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Operation = function (op, operands, isSpaced) {
@@ -4239,6 +4328,7 @@ tree.operate = function (env, op, a, b) {
})(require('../tree'));
(function (tree) {
tree.Paren = function (node) {
@@ -4261,6 +4351,7 @@ tree.Paren.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Quoted = function (str, content, escaped, index, currentFileInfo) {
@@ -4290,7 +4381,7 @@ tree.Quoted.prototype = {
var v = new(tree.Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true);
return (v instanceof tree.Quoted) ? v.value : v.toCSS();
});
return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index);
return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index, this.currentFileInfo);
},
compare: function (x) {
if (!x.toCSS) {
@@ -4309,6 +4400,7 @@ tree.Quoted.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline) {
@@ -4369,6 +4461,7 @@ tree.Rule.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Ruleset = function (selectors, rules, strictImports) {
@@ -4595,7 +4688,7 @@ tree.Ruleset.prototype = {
path = this.paths[i];
env.firstSelector = true;
for(j = 0; j < path.length; j++) {
output.add(path[j].genCSS(env, output));
path[j].genCSS(env, output);
env.firstSelector = false;
}
if (i + 1 < this.paths.length) {
@@ -4610,12 +4703,14 @@ tree.Ruleset.prototype = {
for (i = 0; i < ruleNodes.length; i++) {
rule = ruleNodes[i];
if (i + 1 === ruleNodes.length) {
// @page{ directive ends up with root elements inside it, a mix of rules and rulesets
// In this instance we do not know whether it is the last property
if (i + 1 === ruleNodes.length && (!this.root || rulesetNodes.length === 0 || this.firstRoot)) {
env.lastRule = true;
}
if (rule.toCSS) {
output.add(rule.genCSS(env, output));
if (rule.genCSS) {
rule.genCSS(env, output);
} else if (rule.value) {
output.add(rule.value.toString());
}
@@ -4634,16 +4729,18 @@ tree.Ruleset.prototype = {
for (i = 0; i < rulesetNodes.length; i++) {
if (ruleNodes.length && firstRuleset) {
output.add("\n" + (this.root ? tabRuleStr : tabSetStr));
output.add((env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr));
}
if (!firstRuleset) {
output.add('\n' + (this.root ? tabRuleStr : tabSetStr));
output.add((env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr));
}
firstRuleset = false;
output.add(rulesetNodes[i].genCSS(env, output));
rulesetNodes[i].genCSS(env, output);
}
output.add(!env.compress && this.firstRoot ? '\n' : '');
if (!output.isEmpty() && !env.compress && this.firstRoot) {
output.add('\n');
}
},
toCSS: tree.toCSS,
@@ -4827,6 +4924,7 @@ tree.Ruleset.prototype = {
}
};
})(require('../tree'));
(function (tree) {
tree.Selector = function (elements, extendList, condition, index, currentFileInfo, isReferenced) {
@@ -4909,6 +5007,7 @@ tree.Selector.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.UnicodeDescriptor = function (value) {
@@ -4924,6 +5023,7 @@ tree.UnicodeDescriptor.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.URL = function (val, currentFileInfo) {
@@ -4960,6 +5060,7 @@ tree.URL.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Value = function (value) {
@@ -4992,6 +5093,7 @@ tree.Value.prototype = {
};
})(require('../tree'));
(function (tree) {
tree.Variable = function (name, index, currentFileInfo) {
@@ -5035,6 +5137,7 @@ tree.Variable.prototype = {
};
})(require('../tree'));
(function (tree) {
var parseCopyProperties = [
@@ -5045,6 +5148,7 @@ tree.Variable.prototype = {
'relativeUrls', // option - whether to adjust URL's to be relative
'rootpath', // option - rootpath to append to URL's
'strictImports', // option -
'insecure', // option - whether to allow imports from insecure ssl hosts
'dumpLineNumbers', // option - whether to dump line numbers
'compress', // option - whether to compress
'processImports', // option - whether to process imports. if false then imports will not be imported
@@ -5096,7 +5200,8 @@ tree.Variable.prototype = {
'strictMath', // whether math has to be within parenthesis
'strictUnits', // whether units need to evaluate correctly
'cleancss', // whether to compress with clean-css
'sourceMap' // whether to output a source map
'sourceMap', // whether to output a source map
'importMultiple'// whether we are currently importing multiple copies
];
tree.evalEnv = function(options, frames) {
@@ -5166,6 +5271,7 @@ tree.Variable.prototype = {
};
})(require('./tree'));
(function (tree) {
tree.visitor = function(implementation) {
@@ -5234,7 +5340,8 @@ tree.Variable.prototype = {
}
};
})(require('./tree'));(function (tree) {
})(require('./tree'));
(function (tree) {
tree.importVisitor = function(importer, finish, evalEnv) {
this._visitor = new tree.visitor(this);
this._importer = importer;
@@ -5283,9 +5390,14 @@ tree.Variable.prototype = {
this.importCount++;
var env = new tree.evalEnv(this.env, this.env.frames.slice(0));
this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported) {
if (importNode.options.multiple) {
env.importMultiple = true;
}
this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported, fullPath) {
if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
if (imported && !importNode.options.multiple) { importNode.skip = imported; }
if (imported && !env.importMultiple) { importNode.skip = imported; }
var subFinish = function(e) {
importVisitor.importCount--;
@@ -5297,6 +5409,7 @@ tree.Variable.prototype = {
if (root) {
importNode.root = root;
importNode.importedFilename = fullPath;
if (!inlineCSS && !importNode.skip) {
new(tree.importVisitor)(importVisitor._importer, subFinish, env)
.run(root);
@@ -5345,7 +5458,8 @@ tree.Variable.prototype = {
}
};
})(require('./tree'));(function (tree) {
})(require('./tree'));
(function (tree) {
tree.joinSelectorVisitor = function() {
this.contexts = [[]];
this._visitor = new tree.visitor(this);
@@ -5385,7 +5499,8 @@ tree.Variable.prototype = {
}
};
})(require('./tree'));(function (tree) {
})(require('./tree'));
(function (tree) {
tree.toCSSVisitor = function(env) {
this._visitor = new tree.visitor(this);
this._env = env;
@@ -5583,7 +5698,8 @@ tree.Variable.prototype = {
}
};
})(require('./tree'));(function (tree) {
})(require('./tree'));
(function (tree) {
/*jshint loopfunc:true */
tree.extendFinderVisitor = function() {
@@ -5616,6 +5732,7 @@ tree.Variable.prototype = {
for(i = 0; i < rulesetNode.rules.length; i++) {
if (rulesetNode.rules[i] instanceof tree.Extend) {
allSelectorsExtendList.push(rulesetNode.rules[i]);
rulesetNode.extendOnEveryPath = true;
}
}
@@ -5799,7 +5916,7 @@ tree.Variable.prototype = {
selectorPath = rulesetNode.paths[pathIndex];
// extending extends happens initially, before the main pass
if (selectorPath[selectorPath.length-1].extendList.length) { continue; }
if (rulesetNode.extendOnEveryPath || selectorPath[selectorPath.length-1].extendList.length) { continue; }
matches = this.findMatch(allExtends[extendIndex], selectorPath);
@@ -5900,6 +6017,24 @@ tree.Variable.prototype = {
elementValue2 = elementValue2.value.value || elementValue2.value;
return elementValue1 === elementValue2;
}
elementValue1 = elementValue1.value;
elementValue2 = elementValue2.value;
if (elementValue1 instanceof tree.Selector) {
if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
return false;
}
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;
}
}
if (!this.isElementValuesEqual(elementValue1.elements[i].value, elementValue2.elements[i].value)) {
return false;
}
}
return true;
}
return false;
},
extendSelector:function (matches, selectorPath, replacementSelector) {
@@ -5956,7 +6091,6 @@ tree.Variable.prototype = {
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
currentSelectorPathElementIndex = 0;
currentSelectorPathIndex++;
}
@@ -5985,6 +6119,124 @@ tree.Variable.prototype = {
};
})(require('./tree'));
(function (tree) {
tree.sourceMapOutput = function (options) {
this._css = [];
this._rootNode = options.rootNode;
this._writeSourceMap = options.writeSourceMap;
this._contentsMap = options.contentsMap;
this._sourceMapFilename = options.sourceMapFilename;
this._outputFilename = options.outputFilename;
this._sourceMapBasepath = options.sourceMapBasepath;
this._sourceMapRootpath = options.sourceMapRootpath;
this._outputSourceFiles = options.outputSourceFiles;
this._sourceMapGeneratorConstructor = options.sourceMapGenerator || require("source-map").SourceMapGenerator;
if (this._sourceMapRootpath && this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') {
this._sourceMapRootpath += '/';
}
this._lineNumber = 0;
this._column = 0;
};
tree.sourceMapOutput.prototype.normalizeFilename = function(filename) {
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);
}
}
return (this._sourceMapRootpath || "") + filename.replace(/\\/g, '/');
};
tree.sourceMapOutput.prototype.add = function(chunk, fileInfo, index, mapLines) {
//ignore adding empty strings
if (!chunk) {
return;
}
var lines,
sourceLines,
columns,
sourceColumns,
i;
if (fileInfo) {
var inputSource = this._contentsMap[fileInfo.filename].substring(0, index);
sourceLines = inputSource.split("\n");
sourceColumns = sourceLines[sourceLines.length-1];
}
lines = chunk.split("\n");
columns = lines[lines.length-1];
if (fileInfo) {
if (!mapLines) {
this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + 1, column: this._column},
original: { line: sourceLines.length, column: sourceColumns.length},
source: this.normalizeFilename(fileInfo.filename)});
} else {
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)});
}
}
}
if (lines.length === 1) {
this._column += columns.length;
} else {
this._lineNumber += lines.length - 1;
this._column = columns.length;
}
this._css.push(chunk);
};
tree.sourceMapOutput.prototype.isEmpty = function() {
return this._css.length === 0;
};
tree.sourceMapOutput.prototype.toCSS = function(env) {
this._sourceMapGenerator = new this._sourceMapGeneratorConstructor({ file: this._outputFilename, sourceRoot: null });
if (this._outputSourceFiles) {
for(var filename in this._contentsMap) {
this._sourceMapGenerator.setSourceContent(this.normalizeFilename(filename), this._contentsMap[filename]);
}
}
this._rootNode.genCSS(env, this);
if (this._css.length > 0) {
var sourceMapFilename,
sourceMapContent = JSON.stringify(this._sourceMapGenerator.toJSON());
if (this._sourceMapFilename) {
sourceMapFilename = this.normalizeFilename(this._sourceMapFilename);
}
if (this._writeSourceMap) {
this._writeSourceMap(sourceMapContent);
} else {
sourceMapFilename = "data:application/json," + encodeURIComponent(sourceMapContent);
}
if (sourceMapFilename) {
this._css.push("/*# sourceMappingURL=" + sourceMapFilename + " */");
}
}
return this._css.join('');
};
})(require('./tree'));
//
// browser.js - client-side engine
//
@@ -6651,10 +6903,12 @@ less.refreshStyles = loadStyles;
less.Parser.fileLoader = loadFile;
less.refresh(less.env === 'development');
// amd.js
//
// Define Less as an AMD module.
if (typeof define === "function" && define.amd) {
define(function () { return less; } );
}
})(window);
})(window);

File diff suppressed because one or more lines are too long

View File

@@ -140,7 +140,6 @@
'teal':'#008080',
'thistle':'#d8bfd8',
'tomato':'#ff6347',
// 'transparent':'rgba(0,0,0,0)',
'turquoise':'#40e0d0',
'violet':'#ee82ee',
'wheat':'#f5deb3',

View File

@@ -8,6 +8,7 @@
'relativeUrls', // option - whether to adjust URL's to be relative
'rootpath', // option - rootpath to append to URL's
'strictImports', // option -
'insecure', // option - whether to allow imports from insecure ssl hosts
'dumpLineNumbers', // option - whether to dump line numbers
'compress', // option - whether to compress
'processImports', // option - whether to process imports. if false then imports will not be imported
@@ -59,7 +60,8 @@
'strictMath', // whether math has to be within parenthesis
'strictUnits', // whether units need to evaluate correctly
'cleancss', // whether to compress with clean-css
'sourceMap' // whether to output a source map
'sourceMap', // whether to output a source map
'importMultiple'// whether we are currently importing multiple copies
];
tree.evalEnv = function(options, frames) {

View File

@@ -31,6 +31,7 @@
for(i = 0; i < rulesetNode.rules.length; i++) {
if (rulesetNode.rules[i] instanceof tree.Extend) {
allSelectorsExtendList.push(rulesetNode.rules[i]);
rulesetNode.extendOnEveryPath = true;
}
}
@@ -214,7 +215,7 @@
selectorPath = rulesetNode.paths[pathIndex];
// extending extends happens initially, before the main pass
if (selectorPath[selectorPath.length-1].extendList.length) { continue; }
if (rulesetNode.extendOnEveryPath || selectorPath[selectorPath.length-1].extendList.length) { continue; }
matches = this.findMatch(allExtends[extendIndex], selectorPath);
@@ -315,6 +316,24 @@
elementValue2 = elementValue2.value.value || elementValue2.value;
return elementValue1 === elementValue2;
}
elementValue1 = elementValue1.value;
elementValue2 = elementValue2.value;
if (elementValue1 instanceof tree.Selector) {
if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
return false;
}
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;
}
}
if (!this.isElementValuesEqual(elementValue1.elements[i].value, elementValue2.elements[i].value)) {
return false;
}
}
return true;
}
return false;
},
extendSelector:function (matches, selectorPath, replacementSelector) {
@@ -371,7 +390,6 @@
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
currentSelectorPathElementIndex = 0;
currentSelectorPathIndex++;
}

View File

@@ -233,6 +233,9 @@ tree.functions = {
return new(tree.Quoted)('"' + str + '"', str);
},
unit: function (val, unit) {
if(!(val instanceof tree.Dimension)) {
throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
}
return new(tree.Dimension)(val.value, unit ? unit.toCSS() : "");
},
convert: function (val, unit) {
@@ -320,7 +323,16 @@ tree.functions = {
},
color: function (n) {
if (n instanceof tree.Quoted) {
return new(tree.Color)(n.value.slice(1));
var colorCandidate = n.value,
returnColor;
returnColor = tree.Color.fromKeyword(colorCandidate);
if (returnColor) {
return returnColor;
}
if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/.test(colorCandidate)) {
return new(tree.Color)(colorCandidate.slice(1));
}
throw { type: "Argument", message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF" };
} else {
throw { type: "Argument", message: "argument must be a string" };
}
@@ -420,10 +432,17 @@ tree.functions = {
},
shade: function(color, amount) {
return this.mix(this.rgb(0, 0, 0), color, amount);
},
},
extract: function(values, index) {
index = index.value - 1; // (1-based index)
return values.value[index];
index = index.value - 1; // (1-based index)
// handle non-array values as an array of length 1
// return 'undefined' if index is invalid
return Array.isArray(values.value)
? values.value[index] : Array(values)[index];
},
length: function(values) {
var n = Array.isArray(values.value) ? values.value.length : 1;
return new tree.Dimension(n);
},
"data-uri": function(mimetypeNode, filePathNode) {
@@ -485,9 +504,6 @@ tree.functions = {
}
return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
} else if (!this.env.silent) {
// if explicitly disabled (via --no-ie-compat on CLI, or env.ieCompat === false), merely warn
console.warn("WARNING: Embedding %s (%dKB) exceeds IE8's data-uri size limit of %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
}
}

View File

@@ -47,9 +47,14 @@
this.importCount++;
var env = new tree.evalEnv(this.env, this.env.frames.slice(0));
this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported) {
if (importNode.options.multiple) {
env.importMultiple = true;
}
this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported, fullPath) {
if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
if (imported && !importNode.options.multiple) { importNode.skip = imported; }
if (imported && !env.importMultiple) { importNode.skip = imported; }
var subFinish = function(e) {
importVisitor.importCount--;
@@ -61,6 +66,7 @@
if (root) {
importNode.root = root;
importNode.importedFilename = fullPath;
if (!inlineCSS && !importNode.skip) {
new(tree.importVisitor)(importVisitor._importer, subFinish, env)
.run(root);

View File

@@ -1,11 +1,10 @@
var path = require('path'),
sys = require('util'),
url = require('url'),
request,
fs = require('fs');
var less = {
version: [1, 5, "0-b1"],
version: [1, 5, 0],
Parser: require('./parser').Parser,
tree: require('./tree'),
render: function (input, options, callback) {
@@ -86,7 +85,7 @@ var less = {
writeError: function (ctx, options) {
options = options || {};
if (options.silent) { return; }
sys.error(less.formatError(ctx, options));
console.error(less.formatError(ctx, options));
}
};
@@ -148,13 +147,13 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) {
var urlStr = isUrl ? file : url.resolve(currentFileInfo.currentDirectory, file),
urlObj = url.parse(urlStr);
request.get(urlStr, function (error, res, body) {
request.get({uri: urlStr, strictSSL: !env.insecure }, function (error, res, body) {
if (res.statusCode === 404) {
callback({ type: 'File', message: "resource '" + urlStr + "' was not found\n" });
return;
}
if (!body) {
sys.error( 'Warning: Empty body (HTTP '+ res.statusCode + ') returned by "' + urlStr +'"' );
console.error( 'Warning: Empty body (HTTP '+ res.statusCode + ') returned by "' + urlStr +'"' );
}
if (error) {
callback({ type: 'File', message: "resource '" + urlStr + "' gave this Error:\n "+ error +"\n" });

View File

@@ -1,8 +1,6 @@
// lessc_helper.js
//
// helper functions for lessc
var sys = require('util');
var lessc_helper = {
//Stylize a string
@@ -23,48 +21,53 @@ var lessc_helper = {
//Print command line options
printUsage: function() {
sys.puts("usage: lessc [option option=parameter ...] <source> [destination]");
sys.puts("");
sys.puts("If source is set to `-' (dash or hyphen-minus), input is read from stdin.");
sys.puts("");
sys.puts("options:");
sys.puts(" -h, --help Print help (this message) and exit.");
sys.puts(" --include-path=PATHS Set include paths. Separated by `:'. Use `;' on Windows.");
sys.puts(" -M, --depends Output a makefile import dependency list to stdout");
sys.puts(" --no-color Disable colorized output.");
sys.puts(" --no-ie-compat Disable IE compatibility checks.");
sys.puts(" --no-js Disable JavaScript in less files");
sys.puts(" -l, --lint Syntax check only (lint).");
sys.puts(" -s, --silent Suppress output of error messages.");
sys.puts(" --strict-imports Force evaluation of imports.");
sys.puts(" --verbose Be verbose.");
sys.puts(" -v, --version Print version number and exit.");
sys.puts(" -x, --compress Compress output by removing some whitespaces.");
sys.puts(" --clean-css Compress output using clean-css");
sys.puts(" -O0, -O1, -O2 Set the parser's optimization level. The lower");
sys.puts(" the number, the less nodes it will create in the");
sys.puts(" tree. This could matter for debugging, or if you");
sys.puts(" want to access the individual nodes in the tree.");
sys.puts(" --line-numbers=TYPE Outputs filename and line numbers.");
sys.puts(" TYPE can be either 'comments', which will output");
sys.puts(" the debug info within comments, 'mediaquery'");
sys.puts(" that will output the information within a fake");
sys.puts(" media query which is compatible with the SASS");
sys.puts(" format, and 'all' which will do both.");
sys.puts(" --source-map[=FILENAME] Outputs a v3 sourcemap to the filename (or output filename.map)");
sys.puts(" --source-map-rootpath=X adds this path onto the sourcemap filename and less file paths");
sys.puts(" --source-map-inline puts the less files into the map instead of referencing them");
sys.puts(" -rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls.");
sys.puts(" Works with or without the relative-urls option.");
sys.puts(" -ru, --relative-urls re-write relative urls to the base less file.");
sys.puts(" -sm=on|off Turn on or off strict math, where in strict mode, math");
sys.puts(" --strict-math=on|off requires brackets. This option may default to on and then");
sys.puts(" be removed in the future.");
sys.puts(" -su=on|off Allow mixed units, e.g. 1px+1em or 1px*1px which have units");
sys.puts(" --strict-units=on|off that cannot be represented.");
sys.puts("");
sys.puts("Report bugs to: http://github.com/less/less.js/issues");
sys.puts("Home page: <http://lesscss.org/>");
console.log("usage: lessc [option option=parameter ...] <source> [destination]");
console.log("");
console.log("If source is set to `-' (dash or hyphen-minus), input is read from stdin.");
console.log("");
console.log("options:");
console.log(" -h, --help 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(" -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(" --clean-css Compress output using clean-css");
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(" -rp, --rootpath=URL Set 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(" 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(" --strict-units=on|off that cannot be represented.");
console.log("");
console.log("-------------------------- Deprecated ----------------");
console.log(" -O0, -O1, -O2 Set the parser's optimization level. The lower");
console.log(" the number, the less nodes it will create in the");
console.log(" tree. This could matter for debugging, or if you");
console.log(" want to access the individual nodes in the tree.");
console.log(" --line-numbers=TYPE Outputs filename and line numbers.");
console.log(" TYPE can be either 'comments', which will output");
console.log(" the debug info within comments, 'mediaquery'");
console.log(" that will output the information within a fake");
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("");
console.log("Report bugs to: http://github.com/less/less.js/issues");
console.log("Home page: <http://lesscss.org/>");
}
};

View File

@@ -48,7 +48,8 @@ less.Parser = function Parser(env) {
furthest, // furthest index the parser has gone to
chunks, // chunkified input
current, // index of current chunk, in `input`
parser;
parser,
rootFilename = env && env.filename;
// Top parser on an import tree must be sure there is one "env"
// which will then be passed around by reference.
@@ -70,13 +71,13 @@ less.Parser = function Parser(env) {
var fileParsedFunc = function (e, root, fullPath) {
parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue
var importedPreviously = fullPath in parserImports.files;
var importedPreviously = fullPath in parserImports.files || fullPath === rootFilename;
parserImports.files[fullPath] = root; // Store the root
if (e && !parserImports.error) { parserImports.error = e; }
callback(e, root, importedPreviously);
callback(e, root, importedPreviously, fullPath);
};
if (less.Parser.importer) {
@@ -471,7 +472,8 @@ less.Parser = function Parser(env) {
outputFilename: options.sourceMapOutputFilename,
sourceMapBasepath: options.sourceMapBasepath,
sourceMapRootpath: options.sourceMapRootpath,
outputSourceFiles: options.outputSourceFiles
outputSourceFiles: options.outputSourceFiles,
sourceMapGenerator: options.sourceMapGenerator
});
}
@@ -530,10 +532,10 @@ less.Parser = function Parser(env) {
e = new(LessError)(e, env);
}
callback(e);
return callback(e);
}
else {
callback(null, root);
return callback(null, root);
}
};
@@ -541,7 +543,7 @@ less.Parser = function Parser(env) {
new tree.importVisitor(this.imports, finish)
.run(root);
} else {
finish();
return finish();
}
},
@@ -657,12 +659,11 @@ less.Parser = function Parser(env) {
var k;
if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
if (tree.colors.hasOwnProperty(k)) {
// detect named color
return new(tree.Color)(tree.colors[k].slice(1));
} else {
return new(tree.Keyword)(k);
var color = tree.Color.fromKeyword(k);
if (color) {
return color;
}
return new(tree.Keyword)(k);
}
},
@@ -1241,7 +1242,7 @@ less.Parser = function Parser(env) {
}
if ((op = $(/^[|~*$^]?=/))) {
val = $(this.entities.quoted) || $(/^[\w-]+/) || $(this.entities.variableCurly);
val = $(this.entities.quoted) || $(/^[0-9]+%/) || $(/^[\w-]+/) || $(this.entities.variableCurly);
}
expect(']');
@@ -1277,7 +1278,7 @@ less.Parser = function Parser(env) {
$(this.comments);
if (! $(',')) { break; }
if (s.condition) {
error("Guards are only currently allowed on a single selector");
error("Guards are only currently allowed on a single selector.");
}
$(this.comments);
}
@@ -1401,7 +1402,7 @@ less.Parser = function Parser(env) {
var e, p, nodes = [];
do {
if (e = $(this.entities.keyword)) {
if (e = ($(this.entities.keyword) || $(this.entities.variable))) {
nodes.push(e);
} else if ($('(')) {
p = $(this.property);
@@ -1466,7 +1467,7 @@ less.Parser = function Parser(env) {
//
directive: function () {
var name, value, rules, nonVendorSpecificName,
hasBlock, hasIdentifier, hasExpression;
hasBlock, hasIdentifier, hasExpression, identifier;
if (input.charAt(i) !== '@') { return; }
@@ -1508,6 +1509,7 @@ less.Parser = function Parser(env) {
case "@right-bottom":
hasBlock = true;
break;
case "@host":
case "@page":
case "@document":
case "@supports":
@@ -1521,7 +1523,10 @@ less.Parser = function Parser(env) {
}
if (hasIdentifier) {
name += " " + ($(/^[^{]+/) || '').trim();
identifier = ($(/^[^{]+/) || '').trim();
if (identifier) {
name += " " + identifier;
}
}
if (hasBlock)
@@ -1614,7 +1619,7 @@ less.Parser = function Parser(env) {
var a, b, index = i, condition;
if (a = $(this.condition)) {
while ($(',') && (b = $(this.condition))) {
while (peek(/^,\s*(not\s*)?\(/) && $(',') && (b = $(this.condition))) {
condition = new(tree.Condition)('or', condition || a, b, index);
}
return condition || a;
@@ -1626,7 +1631,7 @@ less.Parser = function Parser(env) {
if ($(/^not/)) { negate = true; }
expect('(');
if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
if (op = $(/^(?:>=|=<|[<=>])/)) {
if (op = $(/^(?:>=|<=|=<|[<=>])/)) {
if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
c = new(tree.Condition)(op, a, b, index, negate);
} else {

View File

@@ -1,5 +1,4 @@
(function (tree) {
var sourceMap = require("source-map");
tree.sourceMapOutput = function (options) {
this._css = [];
@@ -11,6 +10,7 @@
this._sourceMapBasepath = options.sourceMapBasepath;
this._sourceMapRootpath = options.sourceMapRootpath;
this._outputSourceFiles = options.outputSourceFiles;
this._sourceMapGeneratorConstructor = options.sourceMapGenerator || require("source-map").SourceMapGenerator;
if (this._sourceMapRootpath && this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') {
this._sourceMapRootpath += '/';
@@ -27,30 +27,45 @@
filename = filename.substring(1);
}
}
return this._sourceMapRootpath + filename.replace(/\\/g, '/');
return (this._sourceMapRootpath || "") + filename.replace(/\\/g, '/');
};
tree.sourceMapOutput.prototype.add = function(chunk, fileInfo, index) {
tree.sourceMapOutput.prototype.add = function(chunk, fileInfo, index, mapLines) {
//ignore adding empty strings
if (!chunk) {
//TODO what is calling this with undefined?
return;
}
var lines,
columns;
sourceLines,
columns,
sourceColumns,
i;
if (fileInfo) {
var inputSource = this._contentsMap[fileInfo.filename].substring(0, index);
lines = inputSource.split("\n");
columns = lines[lines.length-1];
this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + 1, column: this._column},
original: { line: lines.length, column: columns.length},
source: this.normalizeFilename(fileInfo.filename)});
sourceLines = inputSource.split("\n");
sourceColumns = sourceLines[sourceLines.length-1];
}
lines = chunk.split("\n");
columns = lines[lines.length-1];
if (fileInfo) {
if (!mapLines) {
this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + 1, column: this._column},
original: { line: sourceLines.length, column: sourceColumns.length},
source: this.normalizeFilename(fileInfo.filename)});
} else {
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)});
}
}
}
if (lines.length === 1) {
this._column += columns.length;
} else {
@@ -61,8 +76,12 @@
this._css.push(chunk);
};
tree.sourceMapOutput.prototype.isEmpty = function() {
return this._css.length === 0;
};
tree.sourceMapOutput.prototype.toCSS = function(env) {
this._sourceMapGenerator = new sourceMap.SourceMapGenerator({ file: this._outputFilename, sourceRoot: null });
this._sourceMapGenerator = new this._sourceMapGeneratorConstructor({ file: this._outputFilename, sourceRoot: null });
if (this._outputSourceFiles) {
for(var filename in this._contentsMap) {
@@ -72,13 +91,26 @@
this._rootNode.genCSS(env, this);
this._writeSourceMap(JSON.stringify(this._sourceMapGenerator.toJSON()));
if (this._css.length > 0) {
var sourceMapFilename,
sourceMapContent = JSON.stringify(this._sourceMapGenerator.toJSON());
if (this._sourceMapFilename) {
this._css.push("/*# sourceMappingURL=" + this._sourceMapRootpath + this._sourceMapFilename + " */");
if (this._sourceMapFilename) {
sourceMapFilename = this.normalizeFilename(this._sourceMapFilename);
}
if (this._writeSourceMap) {
this._writeSourceMap(sourceMapContent);
} else {
sourceMapFilename = "data:application/json," + encodeURIComponent(sourceMapContent);
}
if (sourceMapFilename) {
this._css.push("/*# sourceMappingURL=" + sourceMapFilename + " */");
}
}
return this._css.join('');
};
})(require('./tree'));
})(require('./tree'));

View File

@@ -51,8 +51,11 @@ tree.jsify = function (obj) {
tree.toCSS = function (env) {
var strs = [];
this.genCSS(env, {
add: function(chunk, node) {
add: function(chunk, fileInfo, index) {
strs.push(chunk);
},
isEmpty: function () {
return strs.length === 0;
}
});
return strs.join('');

View File

@@ -1,7 +1,10 @@
(function (tree) {
tree.Anonymous = function (string) {
tree.Anonymous = function (string, index, currentFileInfo, mapLines) {
this.value = string.value || string;
this.index = index;
this.mapLines = mapLines;
this.currentFileInfo = currentFileInfo;
};
tree.Anonymous.prototype = {
type: "Anonymous",
@@ -21,7 +24,7 @@ tree.Anonymous.prototype = {
return left < right ? -1 : 1;
},
genCSS: function (env, output) {
output.add(this.value);
output.add(this.value, this.currentFileInfo, this.index, this.mapLines);
},
toCSS: tree.toCSS
};

View File

@@ -22,6 +22,9 @@ tree.Color = function (rgb, a) {
}
this.alpha = typeof(a) === 'number' ? a : 1;
};
var transparentKeyword = "transparent";
tree.Color.prototype = {
type: "Color",
eval: function () { return this; },
@@ -38,6 +41,9 @@ tree.Color.prototype = {
// which has better compatibility with older browsers.
// Values are capped between `0` and `255`, rounded and zero-padded.
if (this.alpha < 1.0) {
if (this.alpha === 0 && this.isTransparentKeyword) {
return transparentKeyword;
}
return "rgba(" + this.rgb.map(function (c) {
return Math.round(c);
}).concat(this.alpha).join(',' + (compress ? '' : ' ')) + ")";
@@ -156,5 +162,17 @@ tree.Color.prototype = {
}
};
tree.Color.fromKeyword = function(keyword) {
if (tree.colors.hasOwnProperty(keyword)) {
// detect named color
return new(tree.Color)(tree.colors[keyword].slice(1));
}
if (keyword === transparentKeyword) {
var transparent = new(tree.Color)([0, 0, 0], 0);
transparent.isTransparentKeyword = true;
return transparent;
}
};
})(require('../tree'));

View File

@@ -36,8 +36,8 @@ tree.Condition.prototype = {
index: i };
}
switch (result) {
case -1: return op === '<' || op === '=<';
case 0: return op === '=' || op === '>=' || op === '=<';
case -1: return op === '<' || op === '=<' || op === '<=';
case 0: return op === '=' || op === '>=' || op === '=<' || op === '<=';
case 1: return op === '>' || op === '>=';
}
}

View File

@@ -30,9 +30,16 @@ tree.Extend.prototype = {
},
findSelfSelectors: function (selectors) {
var selfElements = [],
i;
i,
selectorElements;
for(i = 0; i < selectors.length; i++) {
selectorElements = selectors[i].elements;
// duplicate the logic in genCSS function inside the selector node.
// future TODO - move both logics into the selector joiner visitor
if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === "") {
selectorElements[0].combinator.value = ' ';
}
selfElements = selfElements.concat(selectors[i].elements);
}

View File

@@ -93,7 +93,8 @@ tree.Import.prototype = {
if (this.skip) { return []; }
if (this.options.inline) {
var contents = new(tree.Anonymous)(this.root);
//todo needs to reference css file not import
var contents = new(tree.Anonymous)(this.root, 0, {filename: this.importedFilename}, true);
return this.features ? new(tree.Media)([contents], this.features.value) : [contents];
} else if (this.css) {
var newImport = new(tree.Import)(this.evalPath(env), features, this.options, this.index);

View File

@@ -41,9 +41,17 @@ tree.mixin.Call.prototype = {
try {
if (!(mixin instanceof tree.mixin.Definition)) {
mixin = new tree.mixin.Definition("", [], mixin.rules, null, false);
mixin.originalRuleset = mixins[m].originalRuleset || mixins[m];
}
//if (this.important) {
// isImportant = env.isImportant;
// env.isImportant = true;
//}
Array.prototype.push.apply(
rules, mixin.eval(env, args, this.important).rules);
//if (this.important) {
// env.isImportant = isImportant;
//}
} catch (e) {
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
}
@@ -200,20 +208,22 @@ tree.mixin.Definition.prototype = {
frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
rules = important ?
this.parent.makeImportant.apply(this).rules : this.rules.slice(0);
rules = this.rules.slice(0);
ruleset = new(tree.Ruleset)(null, rules).eval(new(tree.evalEnv)(env,
[this, frame].concat(mixinFrames)));
ruleset = new(tree.Ruleset)(null, rules);
ruleset.originalRuleset = this;
ruleset = ruleset.eval(new(tree.evalEnv)(env, [this, frame].concat(mixinFrames)));
if (important) {
ruleset = this.parent.makeImportant.apply(ruleset);
}
return ruleset;
},
matchCondition: function (args, env) {
if (this.condition && !this.condition.eval(
new(tree.evalEnv)(env,
[this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])]
.concat(env.frames)))) {
[this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])] // the parameter variables
.concat(this.frames) // the parent namespace/mixin frames
.concat(env.frames)))) { // the current environment frames
return false;
}
return true;
@@ -224,7 +234,8 @@ tree.mixin.Definition.prototype = {
if (! this.variadic) {
if (argsLength < this.required) { return false; }
if (argsLength > this.params.length) { return false; }
if ((this.required > 0) && (argsLength > this.params.length)) { return false; }
} else {
if (argsLength < (this.required - 1)) { return false; }
}
len = Math.min(argsLength, this.arity);

View File

@@ -27,7 +27,7 @@ tree.Quoted.prototype = {
var v = new(tree.Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true);
return (v instanceof tree.Quoted) ? v.value : v.toCSS();
});
return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index);
return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index, this.currentFileInfo);
},
compare: function (x) {
if (!x.toCSS) {

View File

@@ -224,7 +224,7 @@ tree.Ruleset.prototype = {
path = this.paths[i];
env.firstSelector = true;
for(j = 0; j < path.length; j++) {
output.add(path[j].genCSS(env, output));
path[j].genCSS(env, output);
env.firstSelector = false;
}
if (i + 1 < this.paths.length) {
@@ -239,12 +239,14 @@ tree.Ruleset.prototype = {
for (i = 0; i < ruleNodes.length; i++) {
rule = ruleNodes[i];
if (i + 1 === ruleNodes.length) {
// @page{ directive ends up with root elements inside it, a mix of rules and rulesets
// In this instance we do not know whether it is the last property
if (i + 1 === ruleNodes.length && (!this.root || rulesetNodes.length === 0 || this.firstRoot)) {
env.lastRule = true;
}
if (rule.toCSS) {
output.add(rule.genCSS(env, output));
if (rule.genCSS) {
rule.genCSS(env, output);
} else if (rule.value) {
output.add(rule.value.toString());
}
@@ -263,16 +265,18 @@ tree.Ruleset.prototype = {
for (i = 0; i < rulesetNodes.length; i++) {
if (ruleNodes.length && firstRuleset) {
output.add("\n" + (this.root ? tabRuleStr : tabSetStr));
output.add((env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr));
}
if (!firstRuleset) {
output.add('\n' + (this.root ? tabRuleStr : tabSetStr));
output.add((env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr));
}
firstRuleset = false;
output.add(rulesetNodes[i].genCSS(env, output));
rulesetNodes[i].genCSS(env, output);
}
output.add(!env.compress && this.firstRoot ? '\n' : '');
if (!output.isEmpty() && !env.compress && this.firstRoot) {
output.add('\n');
}
},
toCSS: tree.toCSS,

View File

@@ -1,9 +1,12 @@
{
"name": "less",
"version": "1.5.0-b1",
"version": "1.5.0",
"description": "Leaner CSS",
"homepage": "http://lesscss.org",
"author": "Alexis Sellier <self@cloudhead.net>",
"author": {
"name": "Alexis Sellier",
"email": "self@cloudhead.net"
},
"contributors": [
"The Core Less Team"
],
@@ -14,6 +17,12 @@
"type": "git",
"url": "https://github.com/less/less.js.git"
},
"licenses": [
{
"type": "Apache v2",
"url": "https://github.com/less/less.js/blob/master/LICENSE"
}
],
"bin": {
"lessc": "./bin/lessc"
},
@@ -22,14 +31,13 @@
"test": "./test"
},
"jam": {
"main": "./dist/less-1.4.2.js"
"main": "./dist/less-1.5.0.js"
},
"engines": {
"node": ">=0.4.2"
},
"scripts": {
"pretest": "make jshint",
"test": "make test"
"test": "grunt test"
},
"optionalDependencies": {
"mime": "1.2.x",
@@ -40,9 +48,18 @@
},
"devDependencies": {
"diff": "~1.0",
"jshint": "~2.1.4",
"http-server": "~0.5.5"
},
"grunt": "~0.4.1",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-connect": "~0.3.0",
"grunt-contrib-jasmine": "~0.5.1",
"grunt-contrib-jshint": "~0.6.0",
"grunt-contrib-uglify": "~0.2.2",
"grunt-shell": "~0.3.1",
"http-server": "~0.5.5",
"matchdep": "~0.1.2",
"time-grunt": "~0.1.1"
},
"keywords": [
"compile less",
"css nesting",
@@ -67,11 +84,5 @@
"stylesheet",
"variables in css",
"css less"
],
"licenses": [
{
"type": "Apache v2",
"url": "https://github.com/less/less.js/blob/master/LICENSE"
}
]
}
}

View File

@@ -1,48 +0,0 @@
var path = require('path'),
fs = require('fs');
var readDirFilesSync = function(dir, regex, callback) {
fs.readdirSync(dir).forEach(function (file) {
if (! regex.test(file)) { return; }
callback(file);
});
};
var createTestRunnerPage = function(dir, exclude, testSuiteName, dir2) {
var output = '<html><head>\n';
readDirFilesSync(path.join("test", dir, 'less', dir2 || ""), /\.less$/, function (file) {
var name = path.basename(file, '.less'),
id = (dir ? dir + '-' : "") + 'less-' + (dir2 ? dir2 + "-" : "") + name;
if (exclude && name.match(exclude)) { return; }
output += '<link id="original-less:' + id + '" rel="stylesheet/less" type="text/css" href="/' + path.join(dir, 'less', dir2 || "", name).replace("\\", "/") + '.less' +'" />\n';
output += '<link id="expected-less:' + id + '" rel="stylesheet" type="text/css" href="/' + path.join(dir, 'css', dir2 || "", name).replace("\\", "/") + '.css' + '" />\n';
});
output += String(fs.readFileSync(path.join('test/browser', 'template.htm'))).replace("{runner-name}", testSuiteName);
fs.writeFileSync(path.join('test/browser', 'test-runner-'+testSuiteName+'.htm'), output);
};
var removeFiles = function(dir, regex) {
readDirFilesSync(dir, regex, function(file) {
fs.unlinkSync(path.join(dir, file), function() {
console.log("Failed to delete " + file);
});
});
};
removeFiles("test/browser", /test-runner-[a-zA-Z-]*\.htm$/);
createTestRunnerPage("", /javascript|urls/, "main");
createTestRunnerPage("", null, "legacy", "legacy");
createTestRunnerPage("", /javascript/, "errors", "errors");
createTestRunnerPage("", null, "no-js-errors", "no-js-errors");
createTestRunnerPage("browser", null, "console-errors", "console-errors");
createTestRunnerPage("browser", null, "browser");
createTestRunnerPage("browser", null, "relative-urls", "relative-urls");
createTestRunnerPage("browser", null, "rootpath", "rootpath");
createTestRunnerPage("browser", null, "rootpath-relative", "rootpath-relative");
createTestRunnerPage("browser", null, "production");
createTestRunnerPage("browser", null, "modify-vars", "modify-vars");

View File

@@ -1,16 +1,24 @@
/*if not async then phantomjs fails to run the webserver and the test concurrently*/
var less = { async: true, strictMath: true };
/* record log messages for testing */
// var logAllIds = function() {
// var allTags = document.head.getElementsByTagName('style');
// var ids = [];
// for (var tg = 0; tg < allTags.length; tg++) {
// var tag = allTags[tg];
// if (tag.id) {
// console.log(tag.id);
// }
// }
// };
var logMessages = [],
realConsoleLog = console.log;
realConsoleLog = console.log;
console.log = function(msg) {
logMessages.push(msg);
realConsoleLog.call(console, msg);
logMessages.push(msg);
realConsoleLog.call(console, msg);
};
var testLessEqualsInDocument = function() {
testLessInDocument(testSheet);
testLessInDocument(testSheet);
};
var testLessErrorsInDocument = function(isConsole) {
@@ -18,68 +26,97 @@ var testLessErrorsInDocument = function(isConsole) {
};
var testLessInDocument = function(testFunc) {
var links = document.getElementsByTagName('link'),
typePattern = /^text\/(x-)?less$/;
var links = document.getElementsByTagName('link'),
typePattern = /^text\/(x-)?less$/;
for (var i = 0; i < links.length; i++) {
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
(links[i].type.match(typePattern)))) {
testFunc(links[i]);
}
for (var i = 0; i < links.length; i++) {
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
(links[i].type.match(typePattern)))) {
testFunc(links[i]);
}
}
};
var testSheet = function(sheet) {
it(sheet.id + " should match the expected output", function() {
var lessOutputId = sheet.id.replace("original-", ""),
expectedOutputId = "expected-" + lessOutputId,
lessOutput = document.getElementById(lessOutputId).innerText,
expectedOutputHref = document.getElementById(expectedOutputId).href,
expectedOutput = loadFile(expectedOutputHref);
it(sheet.id + " should match the expected output", function() {
var lessOutputId = sheet.id.replace("original-", ""),
expectedOutputId = "expected-" + lessOutputId,
lessOutputObj,
lessOutput,
expectedOutputHref = document.getElementById(expectedOutputId).href,
expectedOutput = loadFile(expectedOutputHref);
waitsFor(function() {
return expectedOutput.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
// use sheet to do testing
expect(expectedOutput.text).toEqual(lessOutput);
});
// Browser spec generates less on the fly, so we need to loose control
waitsFor(function() {
lessOutputObj = document.getElementById(lessOutputId);
// the type condition is necessary because of inline browser tests
return lessOutputObj !== null && lessOutputObj.type === "text/css";
}, "generation of " + lessOutputId + "", 700);
runs(function() {
lessOutput = lessOutputObj.innerText;
});
waitsFor(function() {
return expectedOutput.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
// use sheet to do testing
expect(expectedOutput.text).toEqual(lessOutput);
});
});
};
//TODO: do it cleaner - the same way as in css
function extractId(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)
}
var testErrorSheet = function(sheet) {
it(sheet.id + " should match an error", function() {
var lessHref = sheet.href,
id = sheet.id.replace(/^original-less:/, "less-error-message:"),
errorHref = lessHref.replace(/.less$/, ".txt"),
errorFile = loadFile(errorHref),
actualErrorElement = document.getElementById(id),
actualErrorMsg;
it(sheet.id + " should match an error", function() {
var lessHref = sheet.href,
id = "less-error-message:" + extractId(lessHref),
// id = sheet.id.replace(/^original-less:/, "less-error-message:"),
errorHref = lessHref.replace(/.less$/, ".txt"),
errorFile = loadFile(errorHref),
actualErrorElement,
actualErrorMsg;
describe("the error", function() {
expect(actualErrorElement).not.toBe(null);
});
actualErrorMsg = actualErrorElement.innerText
.replace(/\n\d+/g, function(lineNo) { return lineNo + " "; })
.replace(/\n\s*in /g, " in ")
.replace("\n\n", "\n");
// Less.js sets 10ms timer in order to add error message on top of page.
waitsFor(function() {
actualErrorElement = document.getElementById(id);
return actualErrorElement !== null;
}, "error message was not generated", 70);
waitsFor(function() {
return errorFile.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
var errorTxt = errorFile.text
.replace("{path}", "")
.replace("{pathrel}", "")
.replace("{pathhref}", "http://localhost:8081/less/errors/")
.replace("{404status}", " (404)");
expect(errorTxt).toEqual(actualErrorMsg);
if (errorTxt == actualErrorMsg) {
actualErrorElement.style.display = "none";
}
runs(function() {
actualErrorMsg = actualErrorElement.innerText
.replace(/\n\d+/g, function(lineNo) {
return lineNo + " ";
})
.replace(/\n\s*in /g, " in ")
.replace("\n\n", "\n");
});
waitsFor(function() {
return errorFile.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
var errorTxt = errorFile.text
.replace("{path}", "")
.replace("{pathrel}", "")
.replace("{pathhref}", "http://localhost:8081/test/less/errors/")
.replace("{404status}", " (404)");
expect(errorTxt).toEqual(actualErrorMsg);
if (errorTxt == actualErrorMsg) {
actualErrorElement.style.display = "none";
}
});
});
};
@@ -115,20 +152,23 @@ var testErrorSheetConsole = function(sheet) {
.replace("{404status}", " (404)")
.trim();
expect(errorTxt).toEqual(actualErrorMsg);
});
});
});
};
var loadFile = function(href) {
var request = new XMLHttpRequest(),
response = { loaded: false, text: ""};
request.open('GET', href, true);
request.onload = function(e) {
response.text = request.response.replace(/\r/g, "");
response.loaded = true;
}
request.send();
return response;
var request = new XMLHttpRequest(),
response = {
loaded: false,
text: ""
};
request.open('GET', href, true);
request.onload = function(e) {
response.text = request.response.replace(/\r/g, "");
response.loaded = true;
};
request.send();
return response;
};
(function() {
@@ -154,7 +194,7 @@ var loadFile = function(href) {
function execJasmine() {
setTimeout(function() {
jasmineEnv.execute();
jasmineEnv.execute();
}, 3000);
}

View File

@@ -1,20 +1,20 @@
@import "http://localhost:8081/browser/less/imports/modify-this.css";
@import "http://localhost:8081/browser/less/imports/modify-again.css";
@import "http://localhost:8081/test/browser/less/imports/modify-this.css";
@import "http://localhost:8081/test/browser/less/imports/modify-again.css";
.modify {
my-url: url("http://localhost:8081/browser/less/imports/a.png");
my-url: url("http://localhost:8081/test/browser/less/imports/a.png");
}
.modify {
my-url: url("http://localhost:8081/browser/less/imports/b.png");
my-url: url("http://localhost:8081/test/browser/less/imports/b.png");
}
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium), url(http://localhost:8081/browser/less/relative-urls/fonts.svg#MyGeometricModern) format("svg");
src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/relative-urls/fonts.svg#MyGeometricModern) format("svg");
}
#shorthands {
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#misc {
background-image: url(http://localhost:8081/browser/less/relative-urls/images/image.jpg);
background-image: url(http://localhost:8081/test/browser/less/relative-urls/images/image.jpg);
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
@@ -28,8 +28,8 @@
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
.comma-delimited {
background: url(http://localhost:8081/browser/less/relative-urls/bg.jpg) no-repeat, url(http://localhost:8081/browser/less/relative-urls/bg.png) repeat-x top left, url(http://localhost:8081/browser/less/relative-urls/bg);
background: url(http://localhost:8081/test/browser/less/relative-urls/bg.jpg) no-repeat, url(http://localhost:8081/test/browser/less/relative-urls/bg.png) repeat-x top left, url(http://localhost:8081/test/browser/less/relative-urls/bg);
}
.values {
url: url('http://localhost:8081/browser/less/relative-urls/Trebuchet');
url: url('http://localhost:8081/test/browser/less/relative-urls/Trebuchet');
}

View File

@@ -1,20 +1,20 @@
@import "http://localhost:8081/browser/less/modify-this.css";
@import "http://localhost:8081/browser/less/modify-again.css";
@import "http://localhost:8081/test/browser/less/modify-this.css";
@import "http://localhost:8081/test/browser/less/modify-again.css";
.modify {
my-url: url("http://localhost:8081/browser/less/a.png");
my-url: url("http://localhost:8081/test/browser/less/a.png");
}
.modify {
my-url: url("http://localhost:8081/browser/less/b.png");
my-url: url("http://localhost:8081/test/browser/less/b.png");
}
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium), url(http://localhost:8081/browser/less/fonts.svg#MyGeometricModern) format("svg");
src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/fonts.svg#MyGeometricModern) format("svg");
}
#shorthands {
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#misc {
background-image: url(http://localhost:8081/browser/less/images/image.jpg);
background-image: url(http://localhost:8081/test/browser/less/images/image.jpg);
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
@@ -28,23 +28,23 @@
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
.comma-delimited {
background: url(http://localhost:8081/browser/less/bg.jpg) no-repeat, url(http://localhost:8081/browser/less/bg.png) repeat-x top left, url(http://localhost:8081/browser/less/bg);
background: url(http://localhost:8081/test/browser/less/bg.jpg) no-repeat, url(http://localhost:8081/test/browser/less/bg.png) repeat-x top left, url(http://localhost:8081/test/browser/less/bg);
}
.values {
url: url('http://localhost:8081/browser/less/Trebuchet');
url: url('http://localhost:8081/test/browser/less/Trebuchet');
}
#data-uri {
uri: url('http://localhost:8081/data/image.jpg');
uri: url('http://localhost:8081/test/data/image.jpg');
}
#data-uri-guess {
uri: url('http://localhost:8081/data/image.jpg');
uri: url('http://localhost:8081/test/data/image.jpg');
}
#data-uri-ascii {
uri-1: url('http://localhost:8081/data/page.html');
uri-2: url('http://localhost:8081/data/page.html');
uri-1: url('http://localhost:8081/test/data/page.html');
uri-2: url('http://localhost:8081/test/data/page.html');
}
#data-uri-toobig {
uri: url('http://localhost:8081/data/data-uri-fail.png');
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>');

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,31 +2,35 @@ var webpage = require('webpage');
var server = require('webserver').create();
var system = require('system');
var fs = require('fs');
var host, port = 8081;
var host;
var port = 8081;
var listening = server.listen(port, function (request, response) {
//console.log("Requested "+request.url);
var filename = ("test/" + request.url.slice(1)).replace(/[\\\/]/g, fs.separator);
if (!fs.exists(filename) || !fs.isFile(filename)) {
response.statusCode = 404;
response.write("<html><head></head><body><h1>File Not Found</h1><h2>File:"+filename+"</h2></body></html>");
response.close();
return;
}
var listening = server.listen(port, function(request, response) {
//console.log("Requested " + request.url);
// we set the headers here
response.statusCode = 200;
response.headers = {"Cache": "no-cache", "Content-Type": "text/html"};
response.write(fs.read(filename));
var filename = ("test/" + request.url.slice(1)).replace(/[\\\/]/g, fs.separator);
if (!fs.exists(filename) || !fs.isFile(filename)) {
response.statusCode = 404;
response.write("<html><head></head><body><h1>File Not Found</h1><h2>File:" + filename + "</h2></body></html>");
response.close();
return;
}
// we set the headers here
response.statusCode = 200;
response.headers = {
"Cache": "no-cache",
"Content-Type": "text/html"
};
response.write(fs.read(filename));
response.close();
});
if (!listening) {
console.log("could not create web server listening on port " + port);
phantom.exit();
console.log("could not create web server listening on port " + port);
phantom.exit();
}
/**
@@ -42,100 +46,105 @@ if (!listening) {
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
* @param timeOutErrorMessage the error message if time out occurs
*/
function waitFor(testFx, onReady, timeOutMillis, timeOutErrorMessage) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10001, //< Default Max Timeout is 10s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log(timeOutErrorMessage || "'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 100ms
};
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10001, //< Default Max Timeout is 10s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if (!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log(timeOutErrorMessage || "'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 100ms
}
function testPage(url) {
var page = webpage.create();
page.open(url, function (status) {
if (status !== "success") {
console.log("Unable to access network - " + status);
phantom.exit();
} else {
waitFor(function(){
return page.evaluate(function(){
return document.body && document.body.querySelector &&
document.body.querySelector('.symbolSummary .pending') === null &&
document.body.querySelector('.results') !== null;
});
}, function(){
page.onConsoleMessage = function (msg) {
console.log(msg);
};
var exitCode = page.evaluate(function(){
console.log('');
console.log(document.body.querySelector('.description').innerText);
var list = document.body.querySelectorAll('.results > #details > .specDetail.failed');
if (list && list.length > 0) {
console.log('');
console.log(list.length + ' test(s) FAILED:');
for (var i = 0; i < list.length; ++i) {
var el = list[i],
desc = el.querySelector('.description'),
msg = el.querySelector('.resultMessage.fail');
console.log('');
console.log(desc.innerText);
console.log(msg.innerText);
console.log('');
}
return 1;
} else {
console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText);
return 0;
}
});
testFinished(exitCode);
},
10000, // 10 second timeout
"Test failed waiting for jasmine results on page: " + url);
}
});
var page = webpage.create();
page.open(url, function(status) {
if (status !== "success") {
console.log("Unable to access network - " + status);
phantom.exit();
} else {
waitFor(function() {
return page.evaluate(function() {
return document.body && document.body.querySelector &&
document.body.querySelector('.symbolSummary .pending') === null &&
document.body.querySelector('.results') !== null;
});
}, function() {
page.onConsoleMessage = function(msg) {
console.log(msg);
};
var exitCode = page.evaluate(function() {
console.log('');
console.log(document.body.querySelector('.description').innerText);
var list = document.body.querySelectorAll('.results > #details > .specDetail.failed');
if (list && list.length > 0) {
console.log('');
console.log(list.length + ' test(s) FAILED:');
for (var i = 0; i < list.length; ++i) {
var el = list[i],
desc = el.querySelector('.description'),
msg = el.querySelector('.resultMessage.fail');
console.log('');
console.log(desc.innerText);
console.log(msg.innerText);
console.log('');
}
return 1;
} else {
console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText);
return 0;
}
});
testFinished(exitCode);
},
10000, // 10 second timeout
"Test failed waiting for jasmine results on page: " + url);
}
});
}
function scanDirectory(path, regex) {
var files = [];
fs.list(path).forEach(function (file) {
if (file.match(regex)) {
files.push(file);
}
});
return files;
};
var files = [];
fs.list(path).forEach(function(file) {
if (file.match(regex)) {
files.push(file);
}
});
return files;
}
var totalTests = 0,
totalFailed = 0,
totalDone = 0;
totalFailed = 0,
totalDone = 0;
function testFinished(failed) {
if (failed) { totalFailed++; }
totalDone++;
if (totalDone === totalTests) { phantom.exit(totalFailed > 0 ? 1 : 0); }
if (failed) {
totalFailed++;
}
totalDone++;
if (totalDone === totalTests) {
phantom.exit(totalFailed > 0 ? 1 : 0);
}
}
if (system.args.length != 2 && system.args[1] != "--no-tests") {
var files = scanDirectory("test/browser/", /^test-runner-.+\.htm$/);
totalTests = files.length;
console.log("found " + files.length + " tests");
files.forEach(function(file) {
testPage("http://localhost:8081/browser/" + file);
});
var files = scanDirectory("test/browser/", /^test-runner-.+\.htm$/);
totalTests = files.length;
console.log("found " + files.length + " tests");
files.forEach(function(file) {
testPage("http://localhost:8081/browser/" + file);
});
}

View File

@@ -0,0 +1,42 @@
var less = {};
// There originally run inside describe method. However, since they have not
// been inside it, they run at jasmine compile time (not runtime). It all
// worked cause less.js was in async mode and custom phantom runner had
// different setup then grunt-contrib-jasmine. They have been created before
// less.js run, even as they have been defined in spec.
// test inline less in style tags by grabbing an assortment of less files and doing `@import`s
var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
testSheets = [];
// 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 + '";';
lessStyle.type = 'text/less';
lessStyle.id = file;
lessStyle.href = file;
if (lessStyle.styleSheet) {
lessStyle.styleSheet.cssText = lessText;
} else {
lessStyle.innerHTML = lessText;
}
cssLink.rel = 'stylesheet';
cssLink.type = 'text/css';
cssLink.href = cssPath;
cssLink.id = 'expected-' + file;
var head = document.getElementsByTagName('head')[0];
head.appendChild(lessStyle);
head.appendChild(cssLink);
testSheets[i] = lessStyle;
}

View File

@@ -0,0 +1,12 @@
describe("less.js browser behaviour", function() {
testLessEqualsInDocument();
it("has some log messages", function() {
expect(logMessages.length).toBeGreaterThan(0);
});
for (var i = 0; i < testFiles.length; i++) {
var sheet = testSheets[i];
testSheet(sheet);
}
});

View File

@@ -1,42 +0,0 @@
describe("less.js browser behaviour", function() {
testLessEqualsInDocument();
it("has some log messages", function() {
expect(logMessages.length).toBeGreaterThan(0);
});
// test inline less in style tags by grabbing an assortment of less files and doing `@import`s
var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
testSheets = [];
// 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 = '/less/' + file + '.less',
cssPath = '/css/' + file + '.css',
lessStyle = document.createElement('style'),
cssLink = document.createElement('link'),
lessText = '@import "' + lessPath + '";';
lessStyle.type = 'text/less';
lessStyle.id = file;
if (lessStyle.styleSheet) {
lessStyle.styleSheet.cssText = lessText;
} else {
lessStyle.innerHTML = lessText;
}
cssLink.rel = 'stylesheet';
cssLink.type = 'text/css';
cssLink.href = cssPath;
cssLink.id = 'expected-' + file;
var head = document.getElementsByTagName('head')[0];
head.appendChild(lessStyle);
head.appendChild(cssLink);
testSheets[i] = lessStyle;
}
for (var i = 0; i < testFiles.length; i++) {
var sheet = testSheets[i];
testSheet(sheet);
}
});

View File

@@ -0,0 +1,5 @@
var less = {
strictUnits: true,
strictMath: true
};

View File

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

View File

@@ -1,5 +0,0 @@
less.strictUnits = true;
describe("less.js error tests", function() {
testLessErrorsInDocument();
});

View File

@@ -0,0 +1,4 @@
var less = {};
less.strictMath = false;
less.strictUnits = false;

View File

@@ -1,6 +1,3 @@
less.strictMath = false;
less.strictUnits = false;
describe("less.js legacy tests", function() {
testLessEqualsInDocument();
});
});

View File

@@ -0,0 +1,15 @@
var less = {};
less.strictMath = true;
less.functions = {
add: function(a, b) {
return new(less.tree.Dimension)(a.value + b.value);
},
increment: function(a) {
return new(less.tree.Dimension)(a.value + 1);
},
_color: function(str) {
if (str.value === "evil red") {
return new(less.tree.Color)("600");
}
}
};

View File

@@ -0,0 +1,3 @@
describe("less.js main tests", function() {
testLessEqualsInDocument();
});

View File

@@ -1,15 +0,0 @@
less.functions = {
add: function (a, b) {
return new(less.tree.Dimension)(a.value + b.value);
},
increment: function (a) {
return new(less.tree.Dimension)(a.value + 1);
},
_color: function (str) {
if (str.value === "evil red") { return new(less.tree.Color)("600") }
}
};
describe("less.js main tests", function() {
testLessEqualsInDocument();
});

View File

@@ -0,0 +1,2 @@
/* exported less */
var less = {};

View File

@@ -0,0 +1,42 @@
var alreadyRun = false;
describe("less.js modify vars", function() {
beforeEach(function() {
// simulating "setUp" or "beforeAll" method
var lessOutputObj;
if (alreadyRun)
return;
alreadyRun = true;
// wait until the sheet is compiled first time
waitsFor(function() {
lessOutputObj = document.getElementById("less:test-less-simple");
return lessOutputObj !== null;
}, "first generation of less:test-less-simple", 7000);
// modify variables
runs(function() {
lessOutputObj.type = "not compiled yet";
less.modifyVars({
var1: "green",
var2: "purple"
});
});
// wait until variables are modified
waitsFor(function() {
lessOutputObj = document.getElementById("less:test-less-simple");
return lessOutputObj !== null && lessOutputObj.type === "text/css";
}, "second generation of less:test-less-simple", 7000);
});
testLessEqualsInDocument();
it("Should log only 2 XHR requests", function() {
var xhrLogMessages = logMessages.filter(function(item) {
return (/XHR: Getting '/).test(item);
});
expect(xhrLogMessages.length).toEqual(2);
});
});

View File

@@ -1,14 +0,0 @@
setTimeout(function(){
less.modifyVars({var1: "green", var2: "purple"});
}, 1000);
describe("less.js modify vars", function() {
testLessEqualsInDocument();
it("Should log only 2 XHR requests", function() {
var xhrLogMessages = logMessages.filter(function(item) {
return /XHR: Getting '/.test(item);
})
expect(xhrLogMessages.length).toEqual(2);
});
});

View File

@@ -0,0 +1,4 @@
var less = {};
less.strictUnits = true;
less.javascriptEnabled = false;

View File

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

View File

@@ -1,6 +0,0 @@
less.strictUnits = true;
less.javascriptEnabled = false;
describe("less.js javascript disabled error tests", function() {
testLessErrorsInDocument();
});

View File

@@ -0,0 +1,3 @@
var less = {};
less.env = "production";

View File

@@ -1,7 +1,5 @@
less.env = "production";
describe("less.js production behaviour", function() {
it("doesn't log any messages", function() {
expect(logMessages.length).toEqual(0);
});
});
});

View File

@@ -0,0 +1,3 @@
var less = {};
less.relativeUrls = true;

View File

@@ -1,4 +1,3 @@
less.relativeUrls = true;
describe("less.js browser test - relative url's", function() {
testLessEqualsInDocument();
});
});

View File

@@ -0,0 +1,3 @@
var less = {};
less.rootpath = "https://www.github.com/";

View File

@@ -0,0 +1,4 @@
var less = {};
less.rootpath = "https://www.github.com/cloudhead/less.js/";
less.relativeUrls = true;

View File

@@ -1,5 +1,3 @@
less.rootpath = "https://www.github.com/cloudhead/less.js/";
less.relativeUrls = true;
describe("less.js browser test - rootpath and relative url's", function() {
testLessEqualsInDocument();
});
});

View File

@@ -1,4 +1,3 @@
less.rootpath = "https://www.github.com/";
describe("less.js browser test - rootpath url's", function() {
testLessEqualsInDocument();
});
});

View File

@@ -1,11 +0,0 @@
<script src="/browser/jasmine.js" type="text/javascript"></script>
<script src="/browser/jasmine-html.js" type="text/javascript"></script>
<script src="/browser/common.js" type="text/javascript"></script>
<script src="/browser/es5.js" type="text/javascript"></script>
<script src="/browser/runner-{runner-name}.js" type="text/javascript"></script>
<script src="/browser/less.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="/browser/jasmine.css" />
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner</title>
<!-- generate script tags for tests -->
<% var generateScriptTags = function(allScripts) { allScripts.forEach(function(script){ %>
<script src="<%= script %>"></script>
<% }); }; %>
<!-- for each test, generate CSS/LESS link tags -->
<% scripts.src.forEach(function(fullLessName) {
var pathParts = fullLessName.split('/');
var fullCssName = fullLessName.replace(/less/g, 'css');
var lessName = pathParts[pathParts.length - 1];
var name = lessName.split('.')[0]; %>
<!-- the tags to be generated -->
<link id="original-less:test-less-<%= name %>" title="test-less-<%= name %>" rel="stylesheet/less" type="text/css" href="<%= fullLessName %>">
<link id="expected-less:test-less-<%= name %>" rel="stylesheet" type="text/css" href="<%= fullCssName %>">
<% }); %>
<!-- generate grunt-contrib-jasmine link tags -->
<% css.forEach(function(style){ %>
<link rel="stylesheet" type="text/css" href="<%= style %>">
<% }) %>
<!-- inital grunt-contrib-jasmine scripts -->
<% generateScriptTags([].concat(scripts.polyfills, scripts.jasmine)); %>
<!-- Helpers - The less options -->
<% generateScriptTags(scripts.helpers); %>
<!-- Vendor - less.js and common code -->
<% generateScriptTags(scripts.vendor); %>
<!-- Spec -->
<% generateScriptTags(scripts.specs); %>
<!-- final grunt-contrib-jasmine scripts -->
<% generateScriptTags([].concat(scripts.reporters, scripts.start)); %>
</head>
<body>
<!-- content -->
</body>
</html>

View File

@@ -1,4 +1,3 @@
#colours{color1:#fea;color2:#fea;color3:rgba(255,238,170,0.1);string:"#ffeeaa";/*! but not this type
Note preserved whitespace
*/}
dimensions{val:.1px;val:0;val:4cm;val:.2;val:5;angles-must-have-unit:0deg;durations-must-have-unit:0s;length-doesnt-have-unit:0;width:auto\9}
*/}dimensions{val:.1px;val:0;val:4cm;val:.2;val:5;angles-must-have-unit:0deg;durations-must-have-unit:0s;length-doesnt-have-unit:0;width:auto\9}@page{marks:none;@top-left-corner{vertical-align:top}@top-left{vertical-align:top}}

View File

@@ -115,3 +115,11 @@ h1 {
.upper-test {
UpperCaseProperties: allowed;
}
@host {
div {
display: block;
}
}
::distributed(input::placeholder) {
color: #b3b3b3;
}

View File

@@ -51,6 +51,15 @@
.y {
color: z;
}
.va,
.vb,
.vc {
color: black;
}
.vb,
.vc {
color: white;
}
@media tv {
.ma,
.mb,

View File

@@ -0,0 +1,133 @@
.multiunit {
length: 6;
extract: abc "abc" 1 1px 1% #112233;
}
.incorrect-index {
v1: extract(a b c, 5);
v2: extract(a, b, c, -2);
}
.scalar {
var-value: variable;
var-length: 1;
ill-index: extract(variable, 2);
name-value: name;
string-value: "string";
number-value: 12345678;
color-value: #0000ff;
rgba-value: rgba(80, 160, 240, 0.67);
empty-value: ;
name-length: 1;
string-length: 1;
number-length: 1;
color-length: 1;
rgba-length: 1;
empty-length: 1;
}
.mixin-arguments-1 {
length: 4;
extract: c | b | a;
}
.mixin-arguments-2 {
length: 4;
extract: c | b | a;
}
.mixin-arguments-3 {
length: 4;
extract: c | b | a;
}
.mixin-arguments-4 {
length: 0;
extract: extract(, 2) | extract(, 1);
}
.mixin-arguments-2 {
length: 4;
extract: c | b | a;
}
.mixin-arguments-3 {
length: 4;
extract: c | b | a;
}
.mixin-arguments-4 {
length: 3;
extract: c | b;
}
.mixin-arguments-2 {
length: 4;
extract: 3 | 2 | 1;
}
.mixin-arguments-3 {
length: 4;
extract: 3 | 2 | 1;
}
.mixin-arguments-4 {
length: 3;
extract: 3 | 2;
}
.md-space-comma {
length-1: 3;
extract-1: 1 2 3;
length-2: 3;
extract-2: 2;
}
.md-space-comma-as-args-2 {
length: 3;
extract: "x" "y" "z" | 1 2 3 | a b c;
}
.md-space-comma-as-args-3 {
length: 3;
extract: "x" "y" "z" | 1 2 3 | a b c;
}
.md-space-comma-as-args-4 {
length: 2;
extract: "x" "y" "z" | 1 2 3;
}
.md-cat-space-comma {
length-1: 3;
extract-1: 1 2 3;
length-2: 3;
extract-2: 2;
}
.md-cat-space-comma-as-args-2 {
length: 3;
extract: "x" "y" "z" | 1 2 3 | a b c;
}
.md-cat-space-comma-as-args-3 {
length: 3;
extract: "x" "y" "z" | 1 2 3 | a b c;
}
.md-cat-space-comma-as-args-4 {
length: 2;
extract: "x" "y" "z" | 1 2 3;
}
.md-cat-comma-space {
length-1: 3;
extract-1: 1, 2, 3;
length-2: 3;
extract-2: 2;
}
.md-cat-comma-space-as-args-1 {
length: 3;
extract: "x", "y", "z" | 1, 2, 3 | a, b, c;
}
.md-cat-comma-space-as-args-2 {
length: 3;
extract: "x", "y", "z" | 1, 2, 3 | a, b, c;
}
.md-cat-comma-space-as-args-3 {
length: 3;
extract: "x", "y", "z" | 1, 2, 3 | a, b, c;
}
.md-cat-comma-space-as-args-4 {
length: 0;
extract: extract(, 2) | extract(, 1);
}
.md-3D {
length-1: 2;
extract-1: a b c d, 1 2 3 4;
length-2: 2;
extract-2: 5 6 7 8;
length-3: 4;
extract-3: 7;
length-4: 1;
extract-4: 8;
}

View File

@@ -102,11 +102,13 @@
mix-0: #ffff00;
mix-100: #ff0000;
mix-weightless: #ff8000;
mixt: rgba(255, 0, 0, 0.5);
}
#built-in .is-a {
color: true;
color1: true;
color2: true;
color3: true;
keyword: true;
number: true;
string: true;
@@ -117,6 +119,8 @@
}
#alpha {
alpha: rgba(153, 94, 51, 0.6);
alpha2: 0.5;
alpha3: 0;
}
#blendmodes {
multiply: #ed0000;
@@ -129,6 +133,7 @@
average: #7b007b;
negation: #d73131;
}
#extract {
result: 3 2 1 C B A;
#extract-and-length {
extract: 3 2 1 C B A;
length: 6;
}

View File

@@ -1,3 +1,15 @@
#import {
color: #ff0000;
}
body {
width: 100%;
}
.test-f {
height: 10px;
}
body {
width: 100%;
}
.test-f {
height: 10px;
}

View File

@@ -119,6 +119,7 @@
}
@page :first {
size: 8.5in 11in;
@top-left {
margin: 1cm;
}
@@ -211,3 +212,8 @@ body {
display: table-cell;
}
}
@media all and tv {
.all-and-tv-variables {
var: all-and-tv;
}
}

View File

@@ -18,6 +18,7 @@
.ops1 {
height: gt-or-eq;
height: lt-or-eq;
height: lt-or-eq-alias;
}
.ops2 {
height: gt-or-eq;
@@ -25,6 +26,7 @@
}
.ops3 {
height: lt-or-eq;
height: lt-or-eq-alias;
height: not-eq;
}
.default1 {
@@ -74,3 +76,7 @@
declare: 4;
declare: 4px;
}
.call-lock-mixin .call-inner-lock-mixin {
a: 1;
x: 1;
}

View File

@@ -1,19 +1,26 @@
.class {
border: 1;
boxer: 1;
border-width: 1;
border: 2 !important;
boxer: 2 !important;
border-width: 2 !important;
border: 3;
boxer: 3;
border-width: 3;
border: 4 !important;
boxer: 4 !important;
border-width: 4 !important;
border: 5;
boxer: 5;
border-width: 5;
border: 0 !important;
boxer: 0 !important;
border-width: 0 !important;
border: 9 !important;
border: 9;
boxer: 9;
border-width: 9;
}
.class .inner {
test: 1;

0
test/css/no-output.css Normal file
View File

View File

@@ -129,6 +129,7 @@ p a span {
color: #ff0000;
}
[prop],
[prop=10%],
[prop="value3"],
[prop*="val3"],
[|prop~="val3"],

View File

@@ -20,6 +20,7 @@
kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
background-image: url("http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700");
}
#svg-data-uri {
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');

View File

@@ -26,7 +26,7 @@ less.tree.functions._color = function (str) {
if (str.value === "evil red") { return new(less.tree.Color)("600"); }
};
sys.puts("\n" + stylize("LESS", 'underline') + "\n");
console.log("\n" + stylize("LESS", 'underline') + "\n");
runTestSet({strictMath: true, relativeUrls: true, silent: true});
runTestSet({strictMath: true, strictUnits: true}, "errors/",
@@ -72,7 +72,6 @@ function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) {
} else {
difference("FAIL", expectedSourcemap, sourcemap);
}
sys.puts("");
});
}
@@ -94,7 +93,6 @@ function testErrors(name, err, compiledLess, doReplacements) {
difference("FAIL", expectedErr, errMessage);
}
}
sys.puts("");
});
}
@@ -120,16 +118,19 @@ function checkGlobalLeaks() {
function runTestSet(options, foldername, verifyFunction, nameModifier, doReplacements, getFilename) {
foldername = foldername || "";
if(!doReplacements)
if(!doReplacements) {
doReplacements = globalReplacements;
}
fs.readdirSync(path.join('test/less/', foldername)).forEach(function (file) {
if (! /\.less/.test(file)) { return; }
var name = foldername + path.basename(file, '.less');
if (oneTestOnly && name !== oneTestOnly) { return; }
if (oneTestOnly && name !== oneTestOnly) {
return;
}
totalTests++;
if (options.sourceMap) {
@@ -163,14 +164,12 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace
} else {
difference("FAIL", css, less);
}
sys.puts("");
});
});
});
}
function diff(left, right) {
sys.puts("");
require('diff').diffLines(left, right).forEach(function(item) {
if(item.added || item.removed) {
var text = item.value.replace("\n", String.fromCharCode(182) + "\n");
@@ -179,16 +178,17 @@ function diff(left, right) {
sys.print(item.value);
}
});
console.log("");
}
function fail(msg) {
sys.print(stylize(msg, 'red'));
console.error(stylize(msg, 'red'));
failedTests++;
endTest();
}
function difference(msg, left, right) {
sys.print(stylize(msg, 'yellow'));
console.warn(stylize(msg, 'yellow'));
failedTests++;
diff(left, right);
@@ -196,7 +196,7 @@ function difference(msg, left, right) {
}
function ok(msg) {
sys.print(stylize(msg, 'green'));
console.log(stylize(msg, 'green'));
passedTests++;
endTest();
}
@@ -204,21 +204,20 @@ function ok(msg) {
function endTest() {
var leaked = checkGlobalLeaks();
if (failedTests + passedTests === totalTests) {
sys.puts("");
sys.puts("");
console.log("");
if (failedTests > 0) {
sys.print(failedTests);
sys.print(stylize(" Failed", "red"));
sys.print(", " + passedTests + " passed");
console.error(failedTests + stylize(" Failed", "red") + ", " + passedTests + " passed");
} else {
sys.print(stylize("All Passed ", "green"));
sys.print(passedTests + " run");
console.log(stylize("All Passed ", "green") + passedTests + " run");
}
if (leaked.length > 0) {
sys.puts("");
sys.puts("");
sys.print(stylize("Global leak detected: ", "red") + leaked.join(', '));
sys.print("\n");
console.log("");
console.warn(stylize("Global leak detected: ", "red") + leaked.join(', '));
}
if (leaked.length || failedTests) {
//process.exit(1);
process.on('exit', function() { process.reallyExit(1) });
}
}
}

View File

@@ -20,4 +20,13 @@ dimensions {
durations-must-have-unit: 0s;
length-doesnt-have-unit: 0px;
width: auto\9;
}
@page {
marks: none;
@top-left-corner {
vertical-align: top;
}
@top-left {
vertical-align: top;
}
}

View File

@@ -114,4 +114,12 @@ foo|* { color: yellow; }
h1 { color: green; }
.upper-test {
UpperCaseProperties: allowed;
}
@host {
div {
display: block;
}
}
::distributed(input::placeholder) {
color: #b3b3b3;
}

View File

@@ -0,0 +1,3 @@
.test {
color: color("NOT A COLOR");
}

View File

@@ -0,0 +1,4 @@
ArgumentError: error evaluating function `color`: argument must be a color keyword or 3/6 digit hex e.g. #FFF in {path}color-func-invalid-color.less on line 2, column 10:
1 .test {
2 color: color("NOT A COLOR");
3 }

View File

@@ -0,0 +1,4 @@
@ie8: true;
.a when (@ie8 = true),
.b {
}

View File

@@ -0,0 +1,4 @@
SyntaxError: Guards are only currently allowed on a single selector. in {path}multiple-guards-on-css-selectors.less on line 3, column 1:
2 .a when (@ie8 = true),
3 .b {
4 }

View File

@@ -0,0 +1,3 @@
.a {
font-size: unit(80/16,rem);
}

View File

@@ -0,0 +1,4 @@
ArgumentError: error evaluating function `unit`: the first argument to unit must be a number. Have you forgotten parenthesis? in {path}unit-function.less on line 2, column 14:
1 .a {
2 font-size: unit(80/16,rem);
3 }

View File

@@ -59,6 +59,18 @@
color: z;
}
//very simple chaining, but with the extend inside the ruleset
.va {
color: black;
}
.vb {
&:extend(.va);
color: white;
}
.vc {
&:extend(.vb);
}
// media queries - dont extend outside, do extend inside
@media tv {

View File

@@ -0,0 +1,133 @@
// simple array/list:
.multiunit {
@v: abc "abc" 1 1px 1% #123;
length: length(@v);
extract: extract(@v, 1) extract(@v, 2) extract(@v, 3) extract(@v, 4) extract(@v, 5) extract(@v, 6);
}
.incorrect-index {
@v1: a b c;
@v2: a, b, c;
v1: extract(@v1, 5);
v2: extract(@v2, -2);
}
.scalar {
@var: variable;
var-value: extract(@var, 1);
var-length: length(@var);
ill-index: extract(@var, 2);
name-value: extract(name, 1);
string-value: extract("string", 1);
number-value: extract(12345678, 1);
color-value: extract(blue, 1);
rgba-value: extract(rgba(80, 160, 240, 0.67), 1);
empty-value: extract(~'', 1);
name-length: length(name);
string-length: length("string");
number-length: length(12345678);
color-length: length(blue);
rgba-length: length(rgba(80, 160, 240, 0.67));
empty-length: length(~'');
}
.mixin-arguments {
.mixin-args(a b c d);
.mixin-args(a, b, c, d);
.mixin-args(1; 2; 3; 4);
}
.mixin-args(@value) {
&-1 {
length: length(@value);
extract: extract(@value, 3) ~"|" extract(@value, 2) ~"|" extract(@value, 1);
}
}
.mixin-args(...) {
&-2 {
length: length(@arguments);
extract: extract(@arguments, 3) ~"|" extract(@arguments, 2) ~"|" extract(@arguments, 1);
}
}
.mixin-args(@values...) {
&-3 {
length: length(@values);
extract: extract(@values, 3) ~"|" extract(@values, 2) ~"|" extract(@values, 1);
}
}
.mixin-args(@head, @tail...) {
&-4 {
length: length(@tail);
extract: extract(@tail, 2) ~"|" extract(@tail, 1);
}
}
// "multidimensional" array/list
.md-space-comma {
@v: a b c, 1 2 3, "x" "y" "z";
length-1: length(@v);
extract-1: extract(@v, 2);
length-2: length(extract(@v, 2));
extract-2: extract(extract(@v, 2), 2);
&-as-args {.mixin-args(a b c, 1 2 3, "x" "y" "z")}
}
.md-cat-space-comma {
@a: a b c;
@b: 1 2 3;
@c: "x" "y" "z";
@v: @a, @b, @c;
length-1: length(@v);
extract-1: extract(@v, 2);
length-2: length(extract(@v, 2));
extract-2: extract(extract(@v, 2), 2);
&-as-args {.mixin-args(@a, @b, @c)}
}
.md-cat-comma-space {
@a: a, b, c;
@b: 1, 2, 3;
@c: "x", "y", "z";
@v: @a @b @c;
length-1: length(@v);
extract-1: extract(@v, 2);
length-2: length(extract(@v, 2));
extract-2: extract(extract(@v, 2), 2);
&-as-args {.mixin-args(@a @b @c)}
}
.md-3D {
@a: a b c d, 1 2 3 4;
@b: 5 6 7 8, e f g h;
.3D(@a, @b);
.3D(...) {
@v1: @arguments;
length-1: length(@v1);
extract-1: extract(@v1, 1);
@v2: extract(@v1, 2);
length-2: length(@v2);
extract-2: extract(@v2, 1);
@v3: extract(@v2, 1);
length-3: length(@v3);
extract-3: extract(@v3, 3);
@v4: extract(@v3, 4);
length-4: length(@v4);
extract-4: extract(@v4, 1);
}
}

View File

@@ -111,11 +111,13 @@
mix-0: mix(#ff0000, #ffff00, 0);
mix-100: mix(#ff0000, #ffff00, 100);
mix-weightless: mix(#ff0000, #ffff00);
mixt: mix(#ff0000, transparent);
.is-a {
color: iscolor(#ddd);
color1: iscolor(red);
color2: iscolor(rgb(0, 0, 0));
color3: iscolor(transparent);
keyword: iskeyword(hello);
number: isnumber(32);
string: isstring("hello");
@@ -128,6 +130,8 @@
#alpha {
alpha: darken(hsla(25, 50%, 50%, 0.6), 10%);
alpha2: alpha(rgba(3, 4, 5, 0.5));
alpha3: alpha(transparent);
}
#blendmodes {
@@ -142,7 +146,8 @@
negation: negation(#f60000, #313131);
}
#extract {
#extract-and-length {
@anon: A B C 1 2 3;
result: extract(@anon, 6) extract(@anon, 5) extract(@anon, 4) extract(@anon, 3) extract(@anon, 2) extract(@anon, 1);
extract: extract(@anon, 6) extract(@anon, 5) extract(@anon, 4) extract(@anon, 3) extract(@anon, 2) extract(@anon, 1);
length: length(@anon);
}

View File

@@ -2,3 +2,5 @@
@import "import/import-once-test-c";
@import "import/import-once-test-c.less";
@import "import/deeper/import-once-test-a";
@import (multiple) "import/import-test-f.less";
@import (multiple) "import/import-test-f.less";

View File

@@ -0,0 +1,5 @@
@import "import-test-e";
.test-f {
height: 10px;
}

View File

@@ -225,3 +225,10 @@ body {
.nav-justified();
}
}
@all: ~"all";
@tv: ~"tv";
@media @all and @tv {
.all-and-tv-variables {
var: all-and-tv;
}
}

View File

@@ -202,4 +202,14 @@ body {
}
.selector3 {
.mixin-comma-default3(4,2,2,2);
}
.test-calling-one-arg-mixin(@a) {
}
.test-calling-one-arg-mixin(@a, @b, @rest...) {
}
div {
.test-calling-one-arg-mixin(1);
}

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