mirror of
https://github.com/less/less.js.git
synced 2026-04-09 03:00:20 -04:00
Merge branch 'master' into 2_0_0
Conflicts: package.json
This commit is contained in:
304
README.md
304
README.md
@@ -14,265 +14,16 @@ Options for adding Less.js to your project:
|
||||
* [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
|
||||
// Variables
|
||||
@link-color: #428bca;
|
||||
|
||||
// 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
|
||||
// Variables
|
||||
@link-color: #428bca;
|
||||
@link-color-hover: darken(@link-color, 10%);
|
||||
|
||||
//Transition mixin would be anywhere here
|
||||
|
||||
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
|
||||
// Variables
|
||||
@link-color: #428bca;
|
||||
|
||||
.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;
|
||||
}
|
||||
a {
|
||||
font-weight: bold;
|
||||
}
|
||||
```
|
||||
|
||||
## 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].
|
||||
For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org).
|
||||
|
||||
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/).
|
||||
|
||||
@@ -284,57 +35,7 @@ Please report documentation issues in [the documentation project](https://github
|
||||
|
||||
### 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.
|
||||
|
||||
Read [Developing Less](http://lesscss.org/usage/#developing-less).
|
||||
|
||||
## Release History
|
||||
See the [changelog](CHANGELOG.md)
|
||||
@@ -347,5 +48,4 @@ 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"
|
||||
|
||||
29
bin/lessc
29
bin/lessc
@@ -227,13 +227,13 @@ args = args.filter(function (arg) {
|
||||
case "--advanced":
|
||||
cleancssOptions.noAdvanced = false;
|
||||
break;
|
||||
case "--selectors-merge-mode":
|
||||
cleancssOptions.selectorsMergeMode = cleanOptionArgs[1];
|
||||
case "--compatibility":
|
||||
cleancssOptions.compatibility = cleanOptionArgs[1];
|
||||
break;
|
||||
default:
|
||||
console.log("unrecognised clean-css option '" + cleanOptionArgs[0] + "'");
|
||||
console.log("we support only arguments that make sense for less, '--keep-line-breaks', '-b'");
|
||||
console.log("'--s0', '--s1', '--advanced', '--skip-advanced', '--selectors-merge-mode'");
|
||||
console.log("'--s0', '--s1', '--advanced', '--skip-advanced', '--compatibility'");
|
||||
continueProcessing = false;
|
||||
currentErrorcode = 1;
|
||||
break;
|
||||
@@ -348,8 +348,9 @@ var parseLessFile = function (e, data) {
|
||||
sys.print(file + " ")
|
||||
}
|
||||
sys.print("\n");
|
||||
} else if(!options.lint) {
|
||||
} else {
|
||||
try {
|
||||
if (options.lint) { writeSourceMap = function() {} }
|
||||
var css = tree.toCSS({
|
||||
silent: options.silent,
|
||||
verbose: options.verbose,
|
||||
@@ -370,15 +371,17 @@ var parseLessFile = function (e, data) {
|
||||
strictUnits: options.strictUnits,
|
||||
urlArgs: options.urlArgs
|
||||
});
|
||||
if (output) {
|
||||
ensureDirectory(output);
|
||||
fs.writeFileSync(output, css, 'utf8');
|
||||
if (options.verbose) {
|
||||
console.log('lessc: wrote ' + output);
|
||||
}
|
||||
} else {
|
||||
sys.print(css);
|
||||
}
|
||||
if(!options.lint) {
|
||||
if (output) {
|
||||
ensureDirectory(output);
|
||||
fs.writeFileSync(output, css, 'utf8');
|
||||
if (options.verbose) {
|
||||
console.log('lessc: wrote ' + output);
|
||||
}
|
||||
} else {
|
||||
sys.print(css);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
less.writeError(e, options);
|
||||
currentErrorcode = 2;
|
||||
|
||||
304
build/README.md
304
build/README.md
@@ -14,265 +14,16 @@ Options for adding Less.js to your project:
|
||||
* [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
|
||||
// Variables
|
||||
@link-color: #428bca;
|
||||
|
||||
// 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
|
||||
// Variables
|
||||
@link-color: #428bca;
|
||||
@link-color-hover: darken(@link-color, 10%);
|
||||
|
||||
//Transition mixin would be anywhere here
|
||||
|
||||
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
|
||||
// Variables
|
||||
@link-color: #428bca;
|
||||
|
||||
.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;
|
||||
}
|
||||
a {
|
||||
font-weight: bold;
|
||||
}
|
||||
```
|
||||
|
||||
## 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].
|
||||
For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org).
|
||||
|
||||
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/).
|
||||
|
||||
@@ -284,57 +35,7 @@ Please report documentation issues in [the documentation project](https://github
|
||||
|
||||
### 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.
|
||||
|
||||
Read [Developing Less](http://lesscss.org/usage/#developing-less).
|
||||
|
||||
## Release History
|
||||
See the [changelog](CHANGELOG.md)
|
||||
@@ -347,5 +48,4 @@ 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"
|
||||
|
||||
@@ -127,6 +127,7 @@ tree:
|
||||
- <%= build.lib %>/tree/color.js
|
||||
- <%= build.lib %>/tree/comment.js
|
||||
- <%= build.lib %>/tree/condition.js
|
||||
- <%= build.lib %>/tree/detached-ruleset.js
|
||||
- <%= build.lib %>/tree/dimension.js
|
||||
- <%= build.lib %>/tree/directive.js
|
||||
- <%= build.lib %>/tree/element.js
|
||||
@@ -143,6 +144,7 @@ tree:
|
||||
- <%= build.lib %>/tree/quoted.js
|
||||
- <%= build.lib %>/tree/rule.js
|
||||
- <%= build.lib %>/tree/ruleset.js
|
||||
- <%= build.lib %>/tree/ruleset-call.js
|
||||
- <%= build.lib %>/tree/selector.js
|
||||
- <%= build.lib %>/tree/unicode-descriptor.js
|
||||
- <%= build.lib %>/tree/url.js
|
||||
|
||||
@@ -218,19 +218,25 @@ tree.functions = {
|
||||
escape: function (str) {
|
||||
return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
|
||||
},
|
||||
'%': function (quoted /* arg, arg, ...*/) {
|
||||
replace: function (string, pattern, replacement, flags) {
|
||||
var result = string.value;
|
||||
|
||||
result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value);
|
||||
return new(tree.Quoted)(string.quote || '', result, string.escaped);
|
||||
},
|
||||
'%': function (string /* arg, arg, ...*/) {
|
||||
var args = Array.prototype.slice.call(arguments, 1),
|
||||
str = quoted.value;
|
||||
result = string.value;
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
/*jshint loopfunc:true */
|
||||
str = str.replace(/%[sda]/i, function(token) {
|
||||
result = result.replace(/%[sda]/i, function(token) {
|
||||
var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
|
||||
return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
|
||||
});
|
||||
}
|
||||
str = str.replace(/%%/g, '%');
|
||||
return new(tree.Quoted)('"' + str + '"', str);
|
||||
result = result.replace(/%%/g, '%');
|
||||
return new(tree.Quoted)(string.quote || '', result, string.escaped);
|
||||
},
|
||||
unit: function (val, unit) {
|
||||
if(!(val instanceof tree.Dimension)) {
|
||||
|
||||
@@ -94,6 +94,7 @@ var less = {
|
||||
|
||||
require('./tree/color');
|
||||
require('./tree/directive');
|
||||
require('./tree/detached-ruleset');
|
||||
require('./tree/operation');
|
||||
require('./tree/dimension');
|
||||
require('./tree/keyword');
|
||||
@@ -120,6 +121,7 @@ require('./tree/media');
|
||||
require('./tree/unicode-descriptor');
|
||||
require('./tree/negative');
|
||||
require('./tree/extend');
|
||||
require('./tree/ruleset-call');
|
||||
|
||||
|
||||
var isUrlRe = /^(?:https?:)?\/\//i;
|
||||
|
||||
@@ -43,8 +43,7 @@ less.Parser = function Parser(env) {
|
||||
var input, // LeSS input string
|
||||
i, // current index in `input`
|
||||
j, // current chunk
|
||||
temp, // temporarily holds a chunk's state, for backtracking
|
||||
memo, // temporarily holds `i`, when backtracking
|
||||
saveStack = [], // holds state for backtracking
|
||||
furthest, // furthest index the parser has gone to
|
||||
chunks, // chunkified input
|
||||
current, // current chunk
|
||||
@@ -111,8 +110,9 @@ less.Parser = function Parser(env) {
|
||||
}
|
||||
};
|
||||
|
||||
function save() { temp = current; memo = currentPos = i; }
|
||||
function restore() { current = temp; currentPos = i = memo; }
|
||||
function save() { currentPos = i; saveStack.push( { current: current, i: i, j: j }); }
|
||||
function restore() { var state = saveStack.pop(); current = state.current; currentPos = i = state.i; j = state.j; }
|
||||
function forget() { saveStack.pop(); }
|
||||
|
||||
function sync() {
|
||||
if (i > currentPos) {
|
||||
@@ -418,7 +418,7 @@ less.Parser = function Parser(env) {
|
||||
if (--level < 0) {
|
||||
return fail("missing opening `{`");
|
||||
}
|
||||
if (!level) { emitChunk(); }
|
||||
if (!level && !parenLevel) { emitChunk(); }
|
||||
continue;
|
||||
case 92: // \
|
||||
if (parserCurrentIndex < len - 1) { parserCurrentIndex++; continue; }
|
||||
@@ -723,7 +723,7 @@ less.Parser = function Parser(env) {
|
||||
while (current)
|
||||
{
|
||||
node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() ||
|
||||
mixin.call() || this.comment() || this.directive();
|
||||
mixin.call() || this.comment() || this.rulesetCall() || this.directive();
|
||||
if (node) {
|
||||
root.push(node);
|
||||
} else {
|
||||
@@ -731,6 +731,9 @@ less.Parser = function Parser(env) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (peekChar('}')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
@@ -1015,6 +1018,19 @@ less.Parser = function Parser(env) {
|
||||
if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { return name[1]; }
|
||||
},
|
||||
|
||||
//
|
||||
// The variable part of a variable definition. Used in the `rule` parser
|
||||
//
|
||||
// @fink();
|
||||
//
|
||||
rulesetCall: function () {
|
||||
var name;
|
||||
|
||||
if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) {
|
||||
return new tree.RulesetCall(name[1]);
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// extend syntax - used to extend selectors
|
||||
//
|
||||
@@ -1100,6 +1116,7 @@ less.Parser = function Parser(env) {
|
||||
}
|
||||
|
||||
if (parsers.end()) {
|
||||
forget();
|
||||
return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
|
||||
}
|
||||
}
|
||||
@@ -1112,9 +1129,11 @@ less.Parser = function Parser(env) {
|
||||
expressions = [], argsSemiColon = [], argsComma = [],
|
||||
isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg;
|
||||
|
||||
save();
|
||||
|
||||
while (true) {
|
||||
if (isCall) {
|
||||
arg = parsers.expression();
|
||||
arg = parsers.detachedRuleset() || parsers.expression();
|
||||
} else {
|
||||
parsers.comments();
|
||||
if (input.charAt(i) === '.' && $re(/^\.{3}/)) {
|
||||
@@ -1142,7 +1161,7 @@ less.Parser = function Parser(env) {
|
||||
|
||||
if (isCall) {
|
||||
// Variable
|
||||
if (arg.value.length == 1) {
|
||||
if (arg.value && arg.value.length == 1) {
|
||||
val = arg.value[0];
|
||||
}
|
||||
} else {
|
||||
@@ -1157,7 +1176,21 @@ less.Parser = function Parser(env) {
|
||||
}
|
||||
expressionContainsNamed = true;
|
||||
}
|
||||
value = expect(parsers.expression);
|
||||
|
||||
// we do not support setting a ruleset as a default variable - it doesn't make sense
|
||||
// However if we do want to add it, there is nothing blocking it, just don't error
|
||||
// and remove isCall dependency below
|
||||
value = (isCall && parsers.detachedRuleset()) || parsers.expression();
|
||||
|
||||
if (!value) {
|
||||
if (isCall) {
|
||||
error("could not understand value for named argument");
|
||||
} else {
|
||||
restore();
|
||||
returner.args = [];
|
||||
return returner;
|
||||
}
|
||||
}
|
||||
nameLoop = (name = val.name);
|
||||
} else if (!isCall && $re(/^\.{3}/)) {
|
||||
returner.variadic = true;
|
||||
@@ -1202,6 +1235,7 @@ less.Parser = function Parser(env) {
|
||||
}
|
||||
}
|
||||
|
||||
forget();
|
||||
returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
|
||||
return returner;
|
||||
},
|
||||
@@ -1242,10 +1276,14 @@ less.Parser = function Parser(env) {
|
||||
variadic = argInfo.variadic;
|
||||
|
||||
// .mixincall("@{a}");
|
||||
// looks a bit like a mixin definition.. so we have to be nice and restore
|
||||
// looks a bit like a mixin definition..
|
||||
// also
|
||||
// .mixincall(@a: {rule: set;});
|
||||
// so we have to be nice and restore
|
||||
if (!$char(')')) {
|
||||
furthest = i;
|
||||
restore();
|
||||
return;
|
||||
}
|
||||
|
||||
parsers.comments();
|
||||
@@ -1257,10 +1295,13 @@ less.Parser = function Parser(env) {
|
||||
ruleset = parsers.block();
|
||||
|
||||
if (ruleset) {
|
||||
forget();
|
||||
return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
|
||||
} else {
|
||||
restore();
|
||||
}
|
||||
} else {
|
||||
forget();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1324,10 +1365,16 @@ less.Parser = function Parser(env) {
|
||||
this.entities.variableCurly();
|
||||
|
||||
if (! e) {
|
||||
save();
|
||||
if ($char('(')) {
|
||||
if ((v = this.selector()) && $char(')')) {
|
||||
e = new(tree.Paren)(v);
|
||||
forget();
|
||||
} else {
|
||||
restore();
|
||||
}
|
||||
} else {
|
||||
forget();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1430,6 +1477,22 @@ less.Parser = function Parser(env) {
|
||||
}
|
||||
},
|
||||
|
||||
blockRuleset: function() {
|
||||
var block = this.block();
|
||||
|
||||
if (block) {
|
||||
block = new tree.Ruleset(null, block);
|
||||
}
|
||||
return block;
|
||||
},
|
||||
|
||||
detachedRuleset: function() {
|
||||
var blockRuleset = this.blockRuleset();
|
||||
if (blockRuleset) {
|
||||
return new tree.DetachedRuleset(blockRuleset);
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// div, .class, body > p {...}
|
||||
//
|
||||
@@ -1460,6 +1523,7 @@ less.Parser = function Parser(env) {
|
||||
}
|
||||
|
||||
if (selectors && (rules = this.block())) {
|
||||
forget();
|
||||
var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports);
|
||||
if (env.dumpLineNumbers) {
|
||||
ruleset.debugInfo = debugInfo;
|
||||
@@ -1472,28 +1536,38 @@ less.Parser = function Parser(env) {
|
||||
}
|
||||
},
|
||||
rule: function (tryAnonymous) {
|
||||
var name, value, c = input.charAt(i), important, merge;
|
||||
save();
|
||||
var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable;
|
||||
|
||||
if (c === '.' || c === '#' || c === '&') { return; }
|
||||
|
||||
save();
|
||||
|
||||
name = this.variable() || this.ruleProperty();
|
||||
if (name) {
|
||||
// prefer to try to parse first if its a variable or we are compressing
|
||||
// but always fallback on the other one
|
||||
value = !tryAnonymous && (env.compress || (name.charAt && (name.charAt(0) === '@'))) ?
|
||||
(this.value() || this.anonymousValue()) :
|
||||
(this.anonymousValue() || this.value());
|
||||
|
||||
important = this.important();
|
||||
isVariable = typeof name === "string";
|
||||
|
||||
// a name returned by this.ruleProperty() is always an array of the form:
|
||||
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
|
||||
// where each item is a tree.Keyword or tree.Variable
|
||||
merge = name.pop && name.pop().value;
|
||||
if (isVariable) {
|
||||
value = this.detachedRuleset();
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
// prefer to try to parse first if its a variable or we are compressing
|
||||
// but always fallback on the other one
|
||||
value = !tryAnonymous && (env.compress || isVariable) ?
|
||||
(this.value() || this.anonymousValue()) :
|
||||
(this.anonymousValue() || this.value());
|
||||
|
||||
important = this.important();
|
||||
|
||||
// a name returned by this.ruleProperty() is always an array of the form:
|
||||
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
|
||||
// where each item is a tree.Keyword or tree.Variable
|
||||
merge = !isVariable && name.pop().value;
|
||||
}
|
||||
|
||||
if (value && this.end()) {
|
||||
return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo);
|
||||
forget();
|
||||
return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo);
|
||||
} else {
|
||||
furthest = i;
|
||||
restore();
|
||||
@@ -1501,6 +1575,8 @@ less.Parser = function Parser(env) {
|
||||
return this.rule(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forget();
|
||||
}
|
||||
},
|
||||
anonymousValue: function () {
|
||||
@@ -1534,6 +1610,7 @@ less.Parser = function Parser(env) {
|
||||
if (dir && (path = this.entities.quoted() || this.entities.url())) {
|
||||
features = this.mediaFeatures();
|
||||
if ($char(';')) {
|
||||
forget();
|
||||
features = features && new(tree.Value)(features);
|
||||
return new(tree.Import)(path, features, options, index, env.currentFileInfo);
|
||||
}
|
||||
@@ -1730,13 +1807,11 @@ less.Parser = function Parser(env) {
|
||||
}
|
||||
|
||||
if (hasBlock) {
|
||||
rules = this.block();
|
||||
if (rules) {
|
||||
rules = new(tree.Ruleset)(null, rules);
|
||||
}
|
||||
rules = this.blockRuleset();
|
||||
}
|
||||
|
||||
if (rules || (!hasBlock && value && $char(';'))) {
|
||||
forget();
|
||||
return new(tree.Directive)(name, value, rules, index, env.currentFileInfo,
|
||||
env.dumpLineNumbers ? getDebugInfo(index, input, env) : null);
|
||||
}
|
||||
|
||||
20
lib/less/tree/detached-ruleset.js
Normal file
20
lib/less/tree/detached-ruleset.js
Normal file
@@ -0,0 +1,20 @@
|
||||
(function (tree) {
|
||||
|
||||
tree.DetachedRuleset = function (ruleset, frames) {
|
||||
this.ruleset = ruleset;
|
||||
this.frames = frames;
|
||||
};
|
||||
tree.DetachedRuleset.prototype = {
|
||||
type: "DetachedRuleset",
|
||||
accept: function (visitor) {
|
||||
this.ruleset = visitor.visit(this.ruleset);
|
||||
},
|
||||
eval: function (env) {
|
||||
var frames = this.frames || env.frames.slice(0);
|
||||
return new tree.DetachedRuleset(this.ruleset, frames);
|
||||
},
|
||||
callEval: function (env) {
|
||||
return this.ruleset.eval(this.frames ? new(tree.evalEnv)(env, this.frames.concat(env.frames)) : env);
|
||||
}
|
||||
};
|
||||
})(require('../tree'));
|
||||
@@ -74,6 +74,7 @@ tree.Media.prototype = {
|
||||
},
|
||||
markReferenced: function () {
|
||||
var i, rules = this.rules[0].rules;
|
||||
this.rules[0].markReferenced();
|
||||
this.isReferenced = true;
|
||||
for (i = 0; i < rules.length; i++) {
|
||||
if (rules[i].markReferenced) {
|
||||
@@ -147,6 +148,8 @@ tree.Media.prototype = {
|
||||
}
|
||||
},
|
||||
bubbleSelectors: function (selectors) {
|
||||
if (!selectors)
|
||||
return;
|
||||
this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ tree.mixin.Call.prototype = {
|
||||
mixin.originalRuleset = mixins[m].originalRuleset || mixins[m];
|
||||
}
|
||||
Array.prototype.push.apply(
|
||||
rules, mixin.eval(env, args, this.important).rules);
|
||||
rules, mixin.evalCall(env, args, this.important).rules);
|
||||
} catch (e) {
|
||||
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
|
||||
}
|
||||
@@ -150,7 +150,7 @@ tree.mixin.Call.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
tree.mixin.Definition = function (name, params, rules, condition, variadic) {
|
||||
tree.mixin.Definition = function (name, params, rules, condition, variadic, frames) {
|
||||
this.name = name;
|
||||
this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])];
|
||||
this.params = params;
|
||||
@@ -164,7 +164,7 @@ tree.mixin.Definition = function (name, params, rules, condition, variadic) {
|
||||
else { return count; }
|
||||
}, 0);
|
||||
this.parent = tree.Ruleset.prototype;
|
||||
this.frames = [];
|
||||
this.frames = frames;
|
||||
};
|
||||
tree.mixin.Definition.prototype = {
|
||||
type: "MixinDefinition",
|
||||
@@ -258,9 +258,12 @@ tree.mixin.Definition.prototype = {
|
||||
|
||||
return frame;
|
||||
},
|
||||
eval: function (env, args, important) {
|
||||
eval: function (env) {
|
||||
return new tree.mixin.Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || env.frames.slice(0));
|
||||
},
|
||||
evalCall: function (env, args, important) {
|
||||
var _arguments = [],
|
||||
mixinFrames = this.frames.concat(env.frames),
|
||||
mixinFrames = this.frames ? this.frames.concat(env.frames) : env.frames,
|
||||
frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments),
|
||||
rules, ruleset;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline) {
|
||||
this.name = name;
|
||||
this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]);
|
||||
this.value = (value instanceof tree.Value || value instanceof tree.Ruleset) ? value : new(tree.Value)([value]);
|
||||
this.important = important ? ' ' + important.trim() : '';
|
||||
this.merge = merge;
|
||||
this.index = index;
|
||||
@@ -30,7 +30,7 @@ tree.Rule.prototype = {
|
||||
},
|
||||
toCSS: tree.toCSS,
|
||||
eval: function (env) {
|
||||
var strictMathBypass = false, name = this.name;
|
||||
var strictMathBypass = false, name = this.name, evaldValue;
|
||||
if (typeof name !== "string") {
|
||||
// expand 'primitive' name directly to get
|
||||
// things faster (~10% for benchmark.less):
|
||||
@@ -43,14 +43,24 @@ tree.Rule.prototype = {
|
||||
env.strictMath = true;
|
||||
}
|
||||
try {
|
||||
evaldValue = this.value.eval(env);
|
||||
|
||||
if (!this.variable && evaldValue.type === "DetachedRuleset") {
|
||||
throw { message: "Rulesets cannot be evaluated on a property.",
|
||||
index: this.index, filename: this.currentFileInfo.filename };
|
||||
}
|
||||
|
||||
return new(tree.Rule)(name,
|
||||
this.value.eval(env),
|
||||
evaldValue,
|
||||
this.important,
|
||||
this.merge,
|
||||
this.index, this.currentFileInfo, this.inline);
|
||||
}
|
||||
catch(e) {
|
||||
e.index = e.index || this.index;
|
||||
if (typeof e.index !== 'number') {
|
||||
e.index = this.index;
|
||||
e.filename = this.currentFileInfo.filename;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
|
||||
16
lib/less/tree/ruleset-call.js
Normal file
16
lib/less/tree/ruleset-call.js
Normal file
@@ -0,0 +1,16 @@
|
||||
(function (tree) {
|
||||
|
||||
tree.RulesetCall = function (variable) {
|
||||
this.variable = variable;
|
||||
};
|
||||
tree.RulesetCall.prototype = {
|
||||
type: "RulesetCall",
|
||||
accept: function (visitor) {
|
||||
},
|
||||
eval: function (env) {
|
||||
var detachedRuleset = new(tree.Variable)(this.variable).eval(env);
|
||||
return detachedRuleset.callEval(env);
|
||||
}
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
@@ -20,7 +20,8 @@ tree.Ruleset.prototype = {
|
||||
},
|
||||
eval: function (env) {
|
||||
var thisSelectors = this.selectors, selectors,
|
||||
selCnt, i, defaultFunc = tree.defaultFunc;
|
||||
selCnt, selector, i, defaultFunc = tree.defaultFunc, hasOnePassingSelector = false;
|
||||
|
||||
if (thisSelectors && (selCnt = thisSelectors.length)) {
|
||||
selectors = [];
|
||||
defaultFunc.error({
|
||||
@@ -28,9 +29,15 @@ tree.Ruleset.prototype = {
|
||||
message: "it is currently only allowed in parametric mixin guards,"
|
||||
});
|
||||
for (i = 0; i < selCnt; i++) {
|
||||
selectors.push(thisSelectors[i].eval(env));
|
||||
selector = thisSelectors[i].eval(env);
|
||||
selectors.push(selector);
|
||||
if (selector.evaldCondition) {
|
||||
hasOnePassingSelector = true;
|
||||
}
|
||||
}
|
||||
defaultFunc.reset();
|
||||
} else {
|
||||
hasOnePassingSelector = true;
|
||||
}
|
||||
|
||||
var rules = this.rules ? this.rules.slice(0) : null,
|
||||
@@ -45,6 +52,10 @@ tree.Ruleset.prototype = {
|
||||
if(this.debugInfo) {
|
||||
ruleset.debugInfo = this.debugInfo;
|
||||
}
|
||||
|
||||
if (!hasOnePassingSelector) {
|
||||
rules.length = 0;
|
||||
}
|
||||
|
||||
// push the current ruleset to the frames stack
|
||||
var envFrames = env.frames;
|
||||
@@ -66,8 +77,8 @@ tree.Ruleset.prototype = {
|
||||
// so they can be evaluated like closures when the time comes.
|
||||
var rsRules = ruleset.rules, rsRuleCnt = rsRules ? rsRules.length : 0;
|
||||
for (i = 0; i < rsRuleCnt; i++) {
|
||||
if (rsRules[i] instanceof tree.mixin.Definition) {
|
||||
rsRules[i].frames = envFrames.slice(0);
|
||||
if (rsRules[i] instanceof tree.mixin.Definition || rsRules[i] instanceof tree.DetachedRuleset) {
|
||||
rsRules[i] = rsRules[i].eval(env);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,28 +101,43 @@ tree.Ruleset.prototype = {
|
||||
rsRuleCnt += rules.length - 1;
|
||||
i += rules.length-1;
|
||||
ruleset.resetCache();
|
||||
} else if (rsRules[i] instanceof tree.RulesetCall) {
|
||||
/*jshint loopfunc:true */
|
||||
rules = rsRules[i].eval(env).rules.filter(function(r) {
|
||||
if ((r instanceof tree.Rule) && r.variable) {
|
||||
// do not pollute the scope at all
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
rsRules.splice.apply(rsRules, [i, 1].concat(rules));
|
||||
rsRuleCnt += rules.length - 1;
|
||||
i += rules.length-1;
|
||||
ruleset.resetCache();
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate everything else
|
||||
for (i = 0; i < rsRules.length; i++) {
|
||||
rule = rsRules[i];
|
||||
if (! (rule instanceof tree.mixin.Definition)) {
|
||||
if (! (rule instanceof tree.mixin.Definition || rule instanceof tree.DetachedRuleset)) {
|
||||
rsRules[i] = rule = rule.eval ? rule.eval(env) : rule;
|
||||
// for rulesets, check if it is a css guard and can be removed
|
||||
if (rule instanceof tree.Ruleset && rule.selectors && rule.selectors.length === 1) {
|
||||
// check if it can be folded in (e.g. & where)
|
||||
if (rule.selectors[0].isJustParentSelector()) {
|
||||
rsRules.splice(i--, 1);
|
||||
// cannot call if there is no selector, so we can just continue
|
||||
if (!rule.selectors[0].evaldCondition) {
|
||||
continue;
|
||||
}
|
||||
for(var j = 0; j < rule.rules.length; j++) {
|
||||
subRule = rule.rules[j];
|
||||
if (!(subRule instanceof tree.Rule) || !subRule.variable) {
|
||||
rsRules.splice(++i, 0, subRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate everything else
|
||||
for (i = 0; i < rsRules.length; i++) {
|
||||
rule = rsRules[i];
|
||||
// for rulesets, check if it is a css guard and can be removed
|
||||
if (rule instanceof tree.Ruleset && rule.selectors && rule.selectors.length === 1) {
|
||||
// check if it can be folded in (e.g. & where)
|
||||
if (rule.selectors[0].isJustParentSelector()) {
|
||||
rsRules.splice(i--, 1);
|
||||
|
||||
for(var j = 0; j < rule.rules.length; j++) {
|
||||
subRule = rule.rules[j];
|
||||
if (!(subRule instanceof tree.Rule) || !subRule.variable) {
|
||||
rsRules.splice(++i, 0, subRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,6 +371,9 @@ tree.Ruleset.prototype = {
|
||||
toCSS: tree.toCSS,
|
||||
|
||||
markReferenced: function () {
|
||||
if (!this.selectors) {
|
||||
return;
|
||||
}
|
||||
for (var s = 0; s < this.selectors.length; s++) {
|
||||
this.selectors[s].markReferenced();
|
||||
}
|
||||
|
||||
18
package.json
18
package.json
@@ -41,9 +41,9 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"mime": "1.2.x",
|
||||
"request": ">=2.12.0",
|
||||
"request": ">=2.33.0",
|
||||
"mkdirp": "~0.3.5",
|
||||
"clean-css": "2.0.x",
|
||||
"clean-css": "2.1.x",
|
||||
"source-map": "0.1.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -51,14 +51,14 @@
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-connect": "~0.3.0",
|
||||
"grunt-contrib-connect": "~0.6.0",
|
||||
"grunt-contrib-jasmine": "~0.5.2",
|
||||
"grunt-contrib-jshint": "~0.7.2",
|
||||
"grunt-contrib-uglify": "~0.2.7",
|
||||
"grunt-shell": "~0.3.1",
|
||||
"http-server": "~0.5.5",
|
||||
"matchdep": "~0.1.2",
|
||||
"time-grunt": "~0.1.1",
|
||||
"grunt-contrib-jshint": "~0.8.0",
|
||||
"grunt-contrib-uglify": "~0.3.2",
|
||||
"grunt-shell": "~0.6.4",
|
||||
"http-server": "~0.6.1",
|
||||
"matchdep": "~0.3.0",
|
||||
"time-grunt": "~0.2.9",
|
||||
"grunt-contrib-copy": "~0.4.1"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
71
test/css/detached-rulesets.css
Normal file
71
test/css/detached-rulesets.css
Normal file
@@ -0,0 +1,71 @@
|
||||
.wrap-selector {
|
||||
color: black;
|
||||
one: 1px;
|
||||
four: magic-frame;
|
||||
visible-one: visible;
|
||||
visible-two: visible;
|
||||
}
|
||||
.wrap-selector {
|
||||
color: red;
|
||||
visible-one: visible;
|
||||
visible-two: visible;
|
||||
}
|
||||
.wrap-selector {
|
||||
color: black;
|
||||
background: white;
|
||||
visible-one: visible;
|
||||
visible-two: visible;
|
||||
}
|
||||
header {
|
||||
background: blue;
|
||||
}
|
||||
@media screen and (min-width: 1200) {
|
||||
header {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
html.lt-ie9 header {
|
||||
background: red;
|
||||
}
|
||||
.wrap-selector {
|
||||
test: extra-wrap;
|
||||
visible-one: visible;
|
||||
visible-two: visible;
|
||||
}
|
||||
.wrap-selector .wrap-selector {
|
||||
test: wrapped-twice;
|
||||
visible-one: visible;
|
||||
visible-two: visible;
|
||||
}
|
||||
.wrap-selector {
|
||||
test-func: 90;
|
||||
test-arithmetic: 18px;
|
||||
visible-one: visible;
|
||||
visible-two: visible;
|
||||
}
|
||||
.without-mixins {
|
||||
b: 1;
|
||||
}
|
||||
@media (orientation: portrait) and tv {
|
||||
.my-selector {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
@media (orientation: portrait) and widescreen and print and tv {
|
||||
.triple-wrapped-mq {
|
||||
triple: true;
|
||||
}
|
||||
}
|
||||
@media (orientation: portrait) and widescreen and tv {
|
||||
.triple-wrapped-mq {
|
||||
triple: true;
|
||||
}
|
||||
}
|
||||
@media (orientation: portrait) and tv {
|
||||
.triple-wrapped-mq {
|
||||
triple: true;
|
||||
}
|
||||
}
|
||||
.a {
|
||||
test: test;
|
||||
}
|
||||
@@ -45,10 +45,18 @@
|
||||
contrast-dark-thresh-per: #eeeeee;
|
||||
contrast-high-thresh-per: #eeeeee;
|
||||
contrast-low-thresh-per: #111111;
|
||||
replace: "Hello, World!";
|
||||
replace-captured: "This is a new string.";
|
||||
replace-with-flags: "2 + 2 = 4";
|
||||
replace-single-quoted: 'foo-2';
|
||||
replace-escaped-string: bar-2;
|
||||
replace-keyword: baz-2;
|
||||
format: "rgb(32, 128, 64)";
|
||||
format-string: "hello world";
|
||||
format-multiple: "hello earth 2";
|
||||
format-url-encode: "red is %23ff0000";
|
||||
format-single-quoted: 'hello single world';
|
||||
format-escaped-string: hello escaped world;
|
||||
eformat: rgb(32, 128, 64);
|
||||
unitless: 12;
|
||||
unit: 14em;
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
/*
|
||||
The media statement above is invalid (no selector)
|
||||
We should ban invalid media queries with properties and no selector?
|
||||
*/
|
||||
input[type="text"].class#id[attr=32]:not(1) {
|
||||
color: white;
|
||||
}
|
||||
div#id.class[a=1][b=2].class:not(1) {
|
||||
color: white;
|
||||
}
|
||||
@media print {
|
||||
.class {
|
||||
color: blue;
|
||||
}
|
||||
.class .sub {
|
||||
width: 42;
|
||||
}
|
||||
}
|
||||
.visible {
|
||||
color: red;
|
||||
}
|
||||
@@ -47,3 +57,12 @@
|
||||
.visible {
|
||||
extend: test;
|
||||
}
|
||||
.test-mediaq-import {
|
||||
color: green;
|
||||
test: 340px;
|
||||
}
|
||||
@media (max-size: 450px) {
|
||||
.test-mediaq-import {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,3 +33,6 @@
|
||||
scope: 'top level';
|
||||
sub-scope-only: 'inside';
|
||||
}
|
||||
#parentSelectorScope {
|
||||
prop: #ffffff;
|
||||
}
|
||||
|
||||
@@ -97,3 +97,6 @@
|
||||
.scope-check();
|
||||
@k:4px;
|
||||
}
|
||||
.errors-if-called when (@c = never) {
|
||||
.mixin-doesnt-exist();
|
||||
}
|
||||
103
test/less/detached-rulesets.less
Normal file
103
test/less/detached-rulesets.less
Normal file
@@ -0,0 +1,103 @@
|
||||
@ruleset: {
|
||||
color: black;
|
||||
background: white;
|
||||
};
|
||||
|
||||
@a: 1px;
|
||||
.wrap-mixin(@ruleset) {
|
||||
@a: hidden and if you see this in the output its a bug;
|
||||
@b: visible;
|
||||
@d: magic-frame; // same behaviour as mixin calls - falls back to this frame
|
||||
.wrap-selector {
|
||||
@c: visible;
|
||||
@ruleset();
|
||||
visible-one: @b;
|
||||
visible-two: @c;
|
||||
}
|
||||
};
|
||||
|
||||
.wrap-mixin({
|
||||
color: black;
|
||||
one: @a;
|
||||
@b: hidden and if you see this in the output its a bug;
|
||||
@c: hidden and if you see this in the output its a bug;
|
||||
four: @d;
|
||||
});
|
||||
|
||||
.wrap-mixin(@ruleset: {
|
||||
color: red;
|
||||
});
|
||||
|
||||
.wrap-mixin(@ruleset);
|
||||
|
||||
.desktop-and-old-ie(@rules) {
|
||||
@media screen and (min-width: 1200) { @rules(); }
|
||||
html.lt-ie9 & { @rules(); }
|
||||
}
|
||||
|
||||
header {
|
||||
background: blue;
|
||||
|
||||
.desktop-and-old-ie({
|
||||
background: red;
|
||||
});
|
||||
}
|
||||
|
||||
.wrap-mixin-calls-wrap(@ruleset) {
|
||||
.wrap-mixin(@ruleset);
|
||||
};
|
||||
|
||||
.wrap-mixin({
|
||||
test: extra-wrap;
|
||||
.wrap-mixin-calls-wrap({
|
||||
test: wrapped-twice;
|
||||
});
|
||||
});
|
||||
|
||||
.wrap-mixin({
|
||||
test-func: unit(90px);
|
||||
test-arithmetic: unit((9+9), px);
|
||||
});
|
||||
// without mixins
|
||||
@ruleset-2: {
|
||||
b: 1;
|
||||
};
|
||||
.without-mixins {
|
||||
@ruleset-2();
|
||||
}
|
||||
@my-ruleset: {
|
||||
.my-selector {
|
||||
@media tv {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
};
|
||||
@media (orientation:portrait) {
|
||||
@my-ruleset();
|
||||
.wrap-media-mixin({
|
||||
@media tv {
|
||||
.triple-wrapped-mq {
|
||||
triple: true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
.wrap-media-mixin(@ruleset) {
|
||||
@media widescreen {
|
||||
@media print {
|
||||
@ruleset();
|
||||
}
|
||||
@ruleset();
|
||||
}
|
||||
@ruleset();
|
||||
}
|
||||
// unlocking mixins
|
||||
@my-mixins: {
|
||||
.mixin() {
|
||||
test: test;
|
||||
}
|
||||
};
|
||||
@my-mixins();
|
||||
.a {
|
||||
.mixin();
|
||||
}
|
||||
6
test/less/errors/detached-ruleset-1.less
Normal file
6
test/less/errors/detached-ruleset-1.less
Normal file
@@ -0,0 +1,6 @@
|
||||
@a: {
|
||||
b: 1;
|
||||
};
|
||||
.a {
|
||||
a: @a;
|
||||
}
|
||||
4
test/less/errors/detached-ruleset-1.txt
Normal file
4
test/less/errors/detached-ruleset-1.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
SyntaxError: Rulesets cannot be evaluated on a property. in {path}detached-ruleset-1.less on line 5, column 3:
|
||||
4 .a {
|
||||
5 a: @a;
|
||||
6 }
|
||||
6
test/less/errors/detached-ruleset-2.less
Normal file
6
test/less/errors/detached-ruleset-2.less
Normal file
@@ -0,0 +1,6 @@
|
||||
@a: {
|
||||
b: 1;
|
||||
};
|
||||
.a {
|
||||
a: @a();
|
||||
}
|
||||
4
test/less/errors/detached-ruleset-2.txt
Normal file
4
test/less/errors/detached-ruleset-2.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
ParseError: Unrecognised input in {path}detached-ruleset-2.less on line 5, column 3:
|
||||
4 .a {
|
||||
5 a: @a();
|
||||
6 }
|
||||
4
test/less/errors/detached-ruleset-3.less
Normal file
4
test/less/errors/detached-ruleset-3.less
Normal file
@@ -0,0 +1,4 @@
|
||||
@a: {
|
||||
b: 1;
|
||||
};
|
||||
@a();
|
||||
4
test/less/errors/detached-ruleset-3.txt
Normal file
4
test/less/errors/detached-ruleset-3.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}detached-ruleset-3.less on line 2, column 3:
|
||||
1 @a: {
|
||||
2 b: 1;
|
||||
3 };
|
||||
5
test/less/errors/detached-ruleset-4.less
Normal file
5
test/less/errors/detached-ruleset-4.less
Normal file
@@ -0,0 +1,5 @@
|
||||
.mixin-definition(@a: {
|
||||
b: 1;
|
||||
}) {
|
||||
@a();
|
||||
}
|
||||
3
test/less/errors/detached-ruleset-4.txt
Normal file
3
test/less/errors/detached-ruleset-4.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
ParseError: Unrecognised input in {path}detached-ruleset-4.less on line 1, column 18:
|
||||
1 .mixin-definition(@a: {
|
||||
2 b: 1;
|
||||
4
test/less/errors/detached-ruleset-5.less
Normal file
4
test/less/errors/detached-ruleset-5.less
Normal file
@@ -0,0 +1,4 @@
|
||||
.mixin-definition(@b) {
|
||||
@a();
|
||||
}
|
||||
.mixin-definition({color: red;});
|
||||
3
test/less/errors/detached-ruleset-5.txt
Normal file
3
test/less/errors/detached-ruleset-5.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
SyntaxError: variable @a is undefined in {path}detached-ruleset-5.less on line 4, column 1:
|
||||
3 }
|
||||
4 .mixin-definition({color: red;});
|
||||
5
test/less/errors/detached-ruleset-6.less
Normal file
5
test/less/errors/detached-ruleset-6.less
Normal file
@@ -0,0 +1,5 @@
|
||||
.a {
|
||||
b: {
|
||||
color: red;
|
||||
};
|
||||
}
|
||||
4
test/less/errors/detached-ruleset-6.txt
Normal file
4
test/less/errors/detached-ruleset-6.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
ParseError: Unrecognised input in {path}detached-ruleset-6.less on line 2, column 3:
|
||||
1 .a {
|
||||
2 b: {
|
||||
3 color: red;
|
||||
9
test/less/errors/mixin-not-visible-in-scope-1.less
Normal file
9
test/less/errors/mixin-not-visible-in-scope-1.less
Normal file
@@ -0,0 +1,9 @@
|
||||
.something {
|
||||
& {
|
||||
.a {value: a}
|
||||
}
|
||||
|
||||
& {
|
||||
.b {.a} // was Err. before 1.6.2
|
||||
}
|
||||
}
|
||||
4
test/less/errors/mixin-not-visible-in-scope-1.txt
Normal file
4
test/less/errors/mixin-not-visible-in-scope-1.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
NameError: .a is undefined in {path}mixin-not-visible-in-scope-1.less on line 7, column 13:
|
||||
6 & {
|
||||
7 .b {.a} // was Err. before 1.6.2
|
||||
8 }
|
||||
@@ -49,10 +49,18 @@
|
||||
contrast-dark-thresh-per: contrast(#000, #111111, #eeeeee, 50%);
|
||||
contrast-high-thresh-per: contrast(#555, #111111, #eeeeee, 60%);
|
||||
contrast-low-thresh-per: contrast(#555, #111111, #eeeeee, 10%);
|
||||
replace: replace("Hello, Mars.", "Mars\.", "World!");
|
||||
replace-captured: replace("This is a string.", "(string)\.$", "new $1.");
|
||||
replace-with-flags: replace("One + one = 4", "one", "2", "gi");
|
||||
replace-single-quoted: replace('foo-1', "1", "2");
|
||||
replace-escaped-string: replace(~"bar-1", "1", "2");
|
||||
replace-keyword: replace(baz-1, "1", "2");
|
||||
format: %("rgb(%d, %d, %d)", @r, 128, 64);
|
||||
format-string: %("hello %s", "world");
|
||||
format-multiple: %("hello %s %d", "earth", 2);
|
||||
format-url-encode: %('red is %A', #ff0000);
|
||||
format-url-encode: %("red is %A", #ff0000);
|
||||
format-single-quoted: %('hello %s', "single world");
|
||||
format-escaped-string: %(~"hello %s", "escaped world");
|
||||
eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64));
|
||||
|
||||
unitless: unit(12px);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
@import (reference) url("import-once.less");
|
||||
@import (reference) url("css-3.less");
|
||||
@import (reference) url("media.less");
|
||||
/*
|
||||
The media statement above is invalid (no selector)
|
||||
We should ban invalid media queries with properties and no selector?
|
||||
*/
|
||||
@import (reference) url("import/import-reference.less");
|
||||
|
||||
.b {
|
||||
@@ -15,4 +11,11 @@
|
||||
|
||||
.visible:extend(.z all) {
|
||||
extend: test;
|
||||
}
|
||||
|
||||
.test-mediaq-import {
|
||||
.mixin-with-mediaq(340px);
|
||||
}
|
||||
|
||||
.class:extend(.class all) {
|
||||
}
|
||||
@@ -40,4 +40,12 @@
|
||||
pulled-in: yes;
|
||||
}
|
||||
/* comment pulled in */
|
||||
}
|
||||
@max-size: 450px;
|
||||
.mixin-with-mediaq(@num) {
|
||||
color: green;
|
||||
test: @num;
|
||||
@media (max-size: @max-size) {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@@ -76,4 +76,29 @@
|
||||
@subScopeOnly: 'inside';
|
||||
//use the mixin
|
||||
.mixinNoParam();
|
||||
}
|
||||
#parentSelectorScope {
|
||||
@col: white;
|
||||
& {
|
||||
@col: black;
|
||||
}
|
||||
prop: @col;
|
||||
& {
|
||||
@col: black;
|
||||
}
|
||||
}
|
||||
.test-empty-mixin() {
|
||||
}
|
||||
#parentSelectorScopeMixins {
|
||||
& {
|
||||
.test-empty-mixin() {
|
||||
should: never seee 1;
|
||||
}
|
||||
}
|
||||
.test-empty-mixin();
|
||||
& {
|
||||
.test-empty-mixin() {
|
||||
should: never seee 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user