Update code style

This commit is contained in:
Andrey Sitnik
2018-07-16 08:36:10 -07:00
parent cd16eaf572
commit e7e04e40c6
60 changed files with 8570 additions and 8483 deletions
+1 -4
View File
@@ -2,11 +2,8 @@ root = true
[*]
indent_style = space
indent_size = 4
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{json,yml}]
indent_size = 2
+47 -47
View File
@@ -172,28 +172,28 @@ PostCSS 可以转化样式到任意语法,不仅仅是 CSS。
```js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader'
}
]
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader'
}
]
}
}
]
}
}
```
@@ -201,10 +201,10 @@ module.exports = {
```js
module.exports = {
plugins: [
require('precss'),
require('autoprefixer')
]
plugins: [
require('precss'),
require('autoprefixer')
]
}
```
@@ -215,16 +215,16 @@ module.exports = {
使用 [`gulp-postcss`] 和 [`gulp-sourcemaps`].
```js
gulp.task('css', function () {
var postcss = require('gulp-postcss');
var sourcemaps = require('gulp-sourcemaps');
gulp.task('css', () => {
const postcss = require('gulp-postcss')
const sourcemaps = require('gulp-sourcemaps')
return gulp.src('src/**/*.css')
.pipe( sourcemaps.init() )
.pipe( postcss([ require('precss'), require('autoprefixer') ]) )
.pipe( sourcemaps.write('.') )
.pipe( gulp.dest('build/') );
});
return gulp.src('src/**/*.css')
.pipe( sourcemaps.init() )
.pipe( postcss([ require('precss'), require('autoprefixer') ]) )
.pipe( sourcemaps.write('.') )
.pipe( gulp.dest('build/') )
})
```
[`gulp-sourcemaps`]: https://github.com/floridoo/gulp-sourcemaps
@@ -249,10 +249,10 @@ postcss --use autoprefixer -c options.json -o main.css css/*.css
你可以用 [`postcss-js`] 然后转换样式对象。
```js
var postcss = require('postcss-js');
var prefixer = postcss.sync([ require('autoprefixer') ]);
var postcss = require('postcss-js')
var prefixer = postcss.sync([ require('autoprefixer') ])
prefixer({ display: 'flex' }); //=> { display: ['-webkit-box', '-webkit-flex', '-ms-flexbox', 'flex'] }
prefixer({ display: 'flex' }) //=> { display: ['-webkit-box', '-webkit-flex', '-ms-flexbox', 'flex'] }
```
[`postcss-js`]: https://github.com/postcss/postcss-js
@@ -279,19 +279,19 @@ prefixer({ display: 'flex' }); //=> { display: ['-webkit-box', '-webkit-flex', '
对于其它的应用环境,你可以使用 JS API:
```js
const fs = require('fs');
const postcss = require('postcss');
const precss = require('precss');
const autoprefixer = require('autoprefixer');
const autoprefixer = require('autoprefixer')
const postcss = require('postcss')
const precss = require('precss')
const fs = require('fs')
fs.readFile('src/app.css', (err, css) => {
postcss([precss, autoprefixer])
.process(css, { from: 'src/app.css', to: 'dest/app.css' })
.then(result => {
fs.writeFile('dest/app.css', result.css);
if ( result.map ) fs.writeFile('dest/app.css.map', result.map);
});
});
postcss([precss, autoprefixer])
.process(css, { from: 'src/app.css', to: 'dest/app.css' })
.then(result => {
fs.writeFile('dest/app.css', result.css)
if ( result.map ) fs.writeFile('dest/app.css.map', result.map)
})
})
```
阅读 [PostCSS API 文档] 获取更多有关 JS API 的信息.
+49 -49
View File
@@ -193,28 +193,28 @@ Use [`postcss-loader`] in `webpack.config.js`:
```js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader'
}
]
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader'
}
]
}
}
]
}
}
```
@@ -222,10 +222,10 @@ Then create `postcss.config.js`:
```js
module.exports = {
plugins: [
require('precss'),
require('autoprefixer')
]
plugins: [
require('precss'),
require('autoprefixer')
]
}
```
@@ -236,16 +236,16 @@ module.exports = {
Use [`gulp-postcss`] and [`gulp-sourcemaps`].
```js
gulp.task('css', function () {
var postcss = require('gulp-postcss');
var sourcemaps = require('gulp-sourcemaps');
gulp.task('css', () => {
const postcss = require('gulp-postcss')
const sourcemaps = require('gulp-sourcemaps')
return gulp.src('src/**/*.css')
.pipe( sourcemaps.init() )
.pipe( postcss([ require('precss'), require('autoprefixer') ]) )
.pipe( sourcemaps.write('.') )
.pipe( gulp.dest('build/') );
});
return gulp.src('src/**/*.css')
.pipe( sourcemaps.init() )
.pipe( postcss([ require('precss'), require('autoprefixer') ]) )
.pipe( sourcemaps.write('.') )
.pipe( gulp.dest('build/') )
})
```
[`gulp-sourcemaps`]: https://github.com/floridoo/gulp-sourcemaps
@@ -272,10 +272,10 @@ To apply PostCSS plugins to React Inline Styles, JSS, Radium
and other [CSS-in-JS], you can use [`postcss-js`] and transforms style objects.
```js
var postcss = require('postcss-js');
var prefixer = postcss.sync([ require('autoprefixer') ]);
var postcss = require('postcss-js')
var prefixer = postcss.sync([ require('autoprefixer') ])
prefixer({ display: 'flex' }); //=> { display: ['-webkit-box', '-webkit-flex', '-ms-flexbox', 'flex'] }
prefixer({ display: 'flex' }) //=> { display: ['-webkit-box', '-webkit-flex', '-ms-flexbox', 'flex'] }
```
[`postcss-js`]: https://github.com/postcss/postcss-js
@@ -302,21 +302,21 @@ prefixer({ display: 'flex' }); //=> { display: ['-webkit-box', '-webkit-flex', '
For other environments, you can use the JS API:
```js
const fs = require('fs');
const postcss = require('postcss');
const precss = require('precss');
const autoprefixer = require('autoprefixer');
const autoprefixer = require('autoprefixer')
const postcss = require('postcss')
const precss = require('precss')
const fs = require('fs')
fs.readFile('src/app.css', (err, css) => {
postcss([precss, autoprefixer])
.process(css, { from: 'src/app.css', to: 'dest/app.css' })
.then(result => {
fs.writeFile('dest/app.css', result.css, () => true);
if ( result.map ) {
fs.writeFile('dest/app.css.map', result.map, () => true);
}
});
});
postcss([precss, autoprefixer])
.process(css, { from: 'src/app.css', to: 'dest/app.css' })
.then(result => {
fs.writeFile('dest/app.css', result.css, () => true)
if ( result.map ) {
fs.writeFile('dest/app.css.map', result.map, () => true)
}
})
})
```
Read the [PostCSS API documentation] for more details about the JS API.
+1 -1
View File
@@ -121,7 +121,7 @@ So now lets look more closely on structures that play main role in PostCSS workf
// Next two numbers also optional and represent end position for multichar tokens like this one. Numbers follow same rule as was described above
1, 10
];
]
```
There are many patterns how tokenization could be done, PostCSS motto is performance and simplicity. Tokenization is complex computing operation and take large amount of syntax analysis time ( ~90% ), that why PostCSS' Tokenizer looks dirty but it was optimized for speed. Any high-level constructs like classes could dramatically slow down tokenizer.
+26 -26
View File
@@ -57,11 +57,11 @@ By wrapping your function in this method,
you are hooking into a common plugin API:
```js
module.exports = postcss.plugin('plugin-name', function (opts) {
return function (root, result) {
// Plugin code
};
});
module.exports = postcss.plugin('plugin-name', opts => {
return (root, result) => {
// Plugin code
}
})
```
## 2. Processing
@@ -78,19 +78,19 @@ different environments. You should test in (at least) Node.js [active LTS](https
For example, use `fs.writeFile` instead of `fs.writeFileSync`:
```js
postcss.plugin('plugin-sprite', function (opts) {
return function (root, result) {
postcss.plugin('plugin-sprite', opts => {
return (root, result) => {
return new Promise(function (resolve, reject) {
var sprite = makeSprite();
fs.writeFile(opts.file, function (err) {
if ( err ) return reject(err);
resolve();
})
});
return new Promise((resolve, reject) => {
const sprite = makeSprite()
fs.writeFile(opts.file, sprite, err => {
if (err) return reject(err)
resolve()
})
})
};
});
}
})
```
### 2.3. Set `node.source` for new nodes
@@ -102,18 +102,18 @@ So if you add new declaration based on some existing declaration, you should
clone the existing declaration in order to save that original `source`.
```js
if ( needPrefix(decl.prop) ) {
decl.cloneBefore({ prop: '-webkit-' + decl.prop });
if (needPrefix(decl.prop)) {
decl.cloneBefore({ prop: '-webkit-' + decl.prop })
}
```
You can also set `source` directly, copying from some existing node:
```js
if ( decl.prop === 'animation' ) {
var keyframe = createAnimationByName(decl.value);
keyframes.source = decl.source;
decl.root().append(keyframes);
if (decl.prop === 'animation') {
const keyframe = createAnimationByName(decl.value)
keyframes.source = decl.source
decl.root().append(keyframes)
}
```
@@ -134,8 +134,8 @@ in a mixin plugin) you should use `node.error` to create an error
that includes source position:
```js
if ( typeof mixins[name] === 'undefined' ) {
throw decl.error('Unknown mixin ' + name, { plugin: 'postcss-mixins' });
if (typeof mixins[name] === 'undefined') {
throw decl.error('Unknown mixin ' + name, { plugin: 'postcss-mixins' })
}
```
@@ -145,8 +145,8 @@ Do not print warnings with `console.log` or `console.warn`,
because some PostCSS runner may not allow console output.
```js
if ( outdated(decl.prop) ) {
result.warn(decl.prop + ' is outdated', { node: decl });
if (outdated(decl.prop)) {
result.warn(decl.prop + ' is outdated', { node: decl })
}
```
+21 -21
View File
@@ -23,12 +23,12 @@ it can support plugins which accept a function, such as [`postcss-assets`]:
```js
module.exports = [
require('postcss-assets')({
cachebuster: function (file) {
return fs.statSync(file).mtime.getTime().toString(16);
}
})
];
require('postcss-assets')({
cachebuster: function (file) {
return fs.statSync(file).mtime.getTime().toString(16)
}
})
]
```
[`postcss-assets`]: https://github.com/borodean/postcss-assets
@@ -43,7 +43,7 @@ writing to disk (for example, a gulp transform), you should set both options
to point to the same file:
```js
processor.process({ from: file.path, to: file.path });
processor.process({ from: file.path, to: file.path })
```
### 2.2. Use only the asynchronous API
@@ -53,8 +53,8 @@ The synchronous API is provided only for debugging, is slower,
and can’t work with asynchronous plugins.
```js
processor.process(opts).then(function (result) {
// processing is finished
processor.process(opts).then(result => {
// processing is finished
});
```
@@ -75,13 +75,13 @@ as the runner can be used by developers who are not familiar with JavaScript.
Instead, handle such errors gracefully:
```js
processor.process(opts).catch(function (error) {
if ( error.name === 'CssSyntaxError' ) {
process.stderr.write(error.message + error.showSourceCode());
} else {
throw error;
}
});
processor.process(opts).catch(error => {
if (error.name === 'CssSyntaxError') {
process.stderr.write(error.message + error.showSourceCode())
} else {
throw error
}
})
```
### 3.2. Display `result.warnings()`
@@ -89,9 +89,9 @@ processor.process(opts).catch(function (error) {
PostCSS runners must output warnings from `result.warnings()`:
```js
result.warnings().forEach(function (warn) {
process.stderr.write(warn.toString());
});
result.warnings().forEach(warn => {
process.stderr.write(warn.toString())
})
```
See also [postcss-log-warnings] and [postcss-messages] plugins.
@@ -106,8 +106,8 @@ PostCSS runners must provide an option to save the source map in a different
file:
```js
if ( result.map ) {
fs.writeFile(opts.to + '.map', result.map.toString());
if (result.map) {
fs.writeFile(opts.to + '.map', result.map.toString())
}
```
+7 -7
View File
@@ -8,12 +8,12 @@ Limit a plugin's local stylesheet context using [`postcss-plugin-context`]:
```pcss
.css-example.is-test-for-css4-browsers {
color: gray(255, 50%);
color: gray(255, 50%);
}
@context cssnext {
.css-example.is-fallback-for-all-browsers {
color: gray(255, 50%);
}
.css-example.is-fallback-for-all-browsers {
color: gray(255, 50%);
}
}
```
@@ -23,7 +23,7 @@ Or enable plugins directly in CSS using [`postcss-use`]:
@use autoprefixer(browsers: ['last 2 versions']);
:fullscreen a {
display: flex
display: flex;
}
```
@@ -50,7 +50,9 @@ Or enable plugins directly in CSS using [`postcss-use`]:
* [`postcss-hamster`] for vertical rhythm, typography, modular scale functions.
* [`postcss-preset-env`] lets you convert modern CSS into something most browsers can understand, determining the polyfills you need based on your targeted browsers or runtime environments.
[`postcss-preset-env`]: https://github.com/jonathantneal/postcss-preset-env/
[`postcss-utilities`]: https://github.com/ismamz/postcss-utilities
[`postcss-hamster`]: https://github.com/h0tc0d3/postcss-hamster
[`stylelint`]: https://github.com/stylelint/stylelint
[`rucksack`]: http://simplaio.github.io/rucksack
[`cssnano`]: http://cssnano.co/
@@ -59,8 +61,6 @@ Or enable plugins directly in CSS using [`postcss-use`]:
[`precss`]: https://github.com/jonathantneal/precss
[`oldie`]: https://github.com/jonathantneal/oldie
[`atcss`]: https://github.com/morishitter/atcss
[`postcss-hamster`]: https://github.com/h0tc0d3/postcss-hamster
[`postcss-preset-env`]: https://github.com/jonathantneal/postcss-preset-env/
## Future CSS Syntax
+11 -11
View File
@@ -13,17 +13,17 @@ If you don’t want the map inlined, you can set `map.inline: false`.
```js
processor
.process(css, {
from: 'app.sass.css',
to: 'app.css',
map: { inline: false },
})
.then(function (result) {
result.map //=> '{ "version":3,
// "file":"app.css",
// "sources":["app.sass"],
// "mappings":"AAAA,KAAI" }'
});
.process(css, {
from: 'app.sass.css',
to: 'app.css',
map: { inline: false }
})
.then(result => {
result.map //=> '{ "version":3,
// "file":"app.css",
// "sources":["app.sass"],
// "mappings":"AAAA,KAAI" }'
})
```
If PostCSS finds source maps from a previous transformation,
+23 -23
View File
@@ -24,9 +24,9 @@ functions:
```js
module.exports = {
parse: require('./parse'),
stringify: require('./stringify')
};
parse: require('./parse'),
stringify: require('./stringify')
}
```
[SCSS]: https://github.com/postcss/postcss-scss
@@ -41,13 +41,13 @@ The parser API is a function which receives a string & returns a [`Root`] node.
The second argument is a function which receives an object with PostCSS options.
```js
var postcss = require('postcss');
const postcss = require('postcss')
module.exports = function (css, opts) {
var root = postcss.root();
// Add other nodes to root
return root;
};
module.exports = function parse (css, opts) {
const root = postcss.root()
// Add other nodes to root
return root
}
```
[Safe Parser]: https://github.com/postcss/postcss-safe-parser
@@ -88,11 +88,11 @@ Second optimization is using character codes instead of strings.
```js
// Slow
string[i] === '{';
string[i] === '{'
// Fast
const OPEN_CURLY = 123; // `{'
string.charCodeAt(i) === OPEN_CURLY;
const OPEN_CURLY = 123 // `{'
string.charCodeAt(i) === OPEN_CURLY
```
Third optimization is “fast jumps”. If you find open quotes, you can find
@@ -100,12 +100,12 @@ next closing quote much faster by `indexOf`:
```js
// Simple jump
next = string.indexOf('"', currentPosition + 1);
next = string.indexOf('"', currentPosition + 1)
// Jump by RegExp
regexp.lastIndex = currentPosion + 1;
regexp.test(string);
next = regexp.lastIndex;
regexp.lastIndex = currentPosion + 1
regexp.test(string)
next = regexp.lastIndex
```
The parser can be a well written class. There is no need in copy-paste and
@@ -166,11 +166,11 @@ A Stringifier is a function which receives [`Root`] node and builder callback.
Then it calls builder with every node’s string and node instance.
```js
module.exports = function (root, builder) {
// Some magic
var string = decl.prop + ':' + decl.value + ';';
builder(string, decl);
// Some science
module.exports = function stringify (root, builder) {
// Some magic
const string = decl.prop + ':' + decl.value + ';'
builder(string, decl)
// Some science
};
```
@@ -201,9 +201,9 @@ For these cases, you should pass a third argument to builder function:
`'start'` or `'end'` string:
```js
this.builder(rule.selector + '{', rule, 'start');
this.builder(rule.selector + '{', rule, 'start')
// Stringify declarations inside
this.builder('}', rule, 'end');
this.builder('}', rule, 'end')
```
### Raw Values
+77 -79
View File
@@ -1,102 +1,100 @@
'use strict';
const gulp = require('gulp');
const gulp = require('gulp')
gulp.task('clean', () => {
let del = require('del');
return del(['lib/*.js', 'postcss.js', 'build/', 'api/']);
});
const del = require('del')
return del(['lib/*.js', 'postcss.js', 'build/', 'api/'])
})
// Build
gulp.task('compile', () => {
let sourcemaps = require('gulp-sourcemaps');
let changed = require('gulp-changed');
let babel = require('gulp-babel');
return gulp.src('lib/*.es6')
.pipe(changed('lib', { extension: '.js' }))
.pipe(sourcemaps.init())
.pipe(babel({
presets: [
[
'env',
{
targets: {
browsers: [
'last 2 version',
'not dead',
'not Explorer 11',
'not ExplorerMobile 11'
],
node: 6
},
loose: true
}
]
],
plugins: ['add-module-exports', 'precompile-charcodes']
}))
.pipe(sourcemaps.write())
.pipe(gulp.dest('lib'));
});
const sourcemaps = require('gulp-sourcemaps')
const changed = require('gulp-changed')
const babel = require('gulp-babel')
return gulp.src('lib/*.es6')
.pipe(changed('lib', { extension: '.js' }))
.pipe(sourcemaps.init())
.pipe(babel({
presets: [
[
'env',
{
targets: {
browsers: [
'last 2 version',
'not dead',
'not Explorer 11',
'not ExplorerMobile 11'
],
node: 6
},
loose: true
}
]
],
plugins: ['add-module-exports', 'precompile-charcodes']
}))
.pipe(sourcemaps.write())
.pipe(gulp.dest('lib'))
})
gulp.task('build:lib', ['compile'], () => {
return gulp.src(['lib/*.js', 'lib/*.d.ts']).pipe(gulp.dest('build/lib'));
});
return gulp.src(['lib/*.js', 'lib/*.d.ts']).pipe(gulp.dest('build/lib'))
})
gulp.task('build:package', () => {
const editor = require('gulp-json-editor');
return gulp.src('./package.json')
.pipe(editor((json) => {
delete json.babel;
delete json.scripts;
delete json.jest;
delete json.eslintConfig;
delete json['size-limit'];
delete json['pre-commit'];
delete json['lint-staged'];
delete json.devDependencies;
return json;
}))
.pipe(gulp.dest('build'));
});
const editor = require('gulp-json-editor')
return gulp.src('./package.json')
.pipe(editor(json => {
delete json.babel
delete json.scripts
delete json.jest
delete json.eslintConfig
delete json['size-limit']
delete json['pre-commit']
delete json['lint-staged']
delete json.devDependencies
return json
}))
.pipe(gulp.dest('build'))
})
gulp.task('build:docs', () => {
let ignore = require('fs').readFileSync('.npmignore').toString()
.trim().split(/\n+/)
.concat([
'package.json', '.npmignore', 'lib/*', 'test/*',
'node_modules/**/*', 'docs/api.md', 'docs/plugins.md',
'docs/writing-a-plugin.md'
]).map( i => '!' + i );
return gulp.src(['**/*'].concat(ignore))
.pipe(gulp.dest('build'));
});
const ignore = require('fs').readFileSync('.npmignore').toString()
.trim().split(/\n+/)
.concat([
'package.json', '.npmignore', 'lib/*', 'test/*',
'node_modules/**/*', 'docs/api.md', 'docs/plugins.md',
'docs/writing-a-plugin.md'
]).map(i => '!' + i)
return gulp.src(['**/*'].concat(ignore))
.pipe(gulp.dest('build'))
})
gulp.task('build', done => {
let runSequence = require('run-sequence');
runSequence('clean', ['build:lib', 'build:docs', 'build:package'], done);
});
const runSequence = require('run-sequence')
runSequence('clean', ['build:lib', 'build:docs', 'build:package'], done)
})
// Tests
gulp.task('integration', ['build'], done => {
let postcss = require('./build');
let real = require('postcss-parser-tests/real');
real(done, css => {
return postcss.parse(css).toResult({ map: { annotation: false } });
});
});
const postcss = require('./build')
const real = require('postcss-parser-tests/real')
real(done, css => {
return postcss.parse(css).toResult({ map: { annotation: false } })
})
})
gulp.task('version', ['build:lib'], () => {
let Processor = require('./lib/processor');
let instance = new Processor();
let pkg = require('./package');
if ( pkg.version !== instance.version ) {
throw new Error('Version in Processor is not equal to package.json');
}
});
const Processor = require('./lib/processor')
const instance = new Processor()
const pkg = require('./package')
if (pkg.version !== instance.version) {
throw new Error('Version in Processor is not equal to package.json')
}
})
// Common
gulp.task('default', ['version', 'integration']);
gulp.task('default', ['version', 'integration'])
+68 -69
View File
@@ -1,4 +1,4 @@
import Container from './container';
import Container from './container'
/**
* Represents an at-rule.
@@ -9,85 +9,84 @@ import Container from './container';
* @extends Container
*
* @example
* const root = postcss.parse('@charset "UTF-8"; @media print {}');
* const root = postcss.parse('@charset "UTF-8"; @media print {}')
*
* const charset = root.first;
* const charset = root.first
* charset.type //=> 'atrule'
* charset.nodes //=> undefined
*
* const media = root.last;
* const media = root.last
* media.nodes //=> []
*/
class AtRule extends Container {
constructor (defaults) {
super(defaults)
this.type = 'atrule'
}
constructor(defaults) {
super(defaults);
this.type = 'atrule';
}
append (...children) {
if (!this.nodes) this.nodes = []
return super.append(...children)
}
append(...children) {
if ( !this.nodes ) this.nodes = [];
return super.append(...children);
}
prepend (...children) {
if (!this.nodes) this.nodes = []
return super.prepend(...children)
}
prepend(...children) {
if ( !this.nodes ) this.nodes = [];
return super.prepend(...children);
}
/**
* @memberof AtRule#
* @member {string} name The at-rule’s name immediately follows the `@`.
*
* @example
* const root = postcss.parse('@media print {}')
* media.name //=> 'media'
* const media = root.first
*/
/**
* @memberof AtRule#
* @member {string} name - the at-rule’s name immediately follows the `@`
*
* @example
* const root = postcss.parse('@media print {}');
* media.name //=> 'media'
* const media = root.first;
*/
/**
* @memberof AtRule#
* @member {string} params The at-rule’s parameters, the values
* that follow the at-rule’s name but precede
* any {} block.
*
* @example
* const root = postcss.parse('@media print, screen {}')
* const media = root.first
* media.params //=> 'print, screen'
*/
/**
* @memberof AtRule#
* @member {string} params - the at-rule’s parameters, the values
* that follow the at-rule’s name but precede
* any {} block
*
* @example
* const root = postcss.parse('@media print, screen {}');
* const media = root.first;
* media.params //=> 'print, screen'
*/
/**
* @memberof AtRule#
* @member {object} raws - Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
* * `after`: the space symbols after the last child of the node
* to the end of the node.
* * `between`: the symbols between the property and value
* for declarations, selector and `{` for rules, or last parameter
* and `{` for at-rules.
* * `semicolon`: contains true if the last child has
* an (optional) semicolon.
* * `afterName`: the space between the at-rule name and its parameters.
*
* PostCSS cleans at-rule parameters from comments and extra spaces,
* but it stores origin content in raws properties.
* As such, if you don’t change a declaration’s value,
* PostCSS will use the raw value with comments.
*
* @example
* const root = postcss.parse(' @media\nprint {\n}')
* root.first.first.raws //=> { before: ' ',
* // between: ' ',
* // afterName: '\n',
* // after: '\n' }
*/
/**
* @memberof AtRule#
* @member {object} raws Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
* * `after`: the space symbols after the last child of the node
* to the end of the node.
* * `between`: the symbols between the property and value
* for declarations, selector and `{` for rules, or last parameter
* and `{` for at-rules.
* * `semicolon`: contains true if the last child has
* an (optional) semicolon.
* * `afterName`: the space between the at-rule name and its parameters.
*
* PostCSS cleans at-rule parameters from comments and extra spaces,
* but it stores origin content in raws properties.
* As such, if you don’t change a declaration’s value,
* PostCSS will use the raw value with comments.
*
* @example
* const root = postcss.parse(' @media\nprint {\n}')
* root.first.first.raws //=> { before: ' ',
* // between: ' ',
* // afterName: '\n',
* // after: '\n' }
*/
}
export default AtRule;
export default AtRule
+22 -23
View File
@@ -1,4 +1,4 @@
import Node from './node';
import Node from './node'
/**
* Represents a comment between declarations or statements (rule and at-rules).
@@ -9,29 +9,28 @@ import Node from './node';
* @extends Node
*/
class Comment extends Node {
constructor (defaults) {
super(defaults)
this.type = 'comment'
}
constructor(defaults) {
super(defaults);
this.type = 'comment';
}
/**
* @memberof Comment#
* @member {string} text The comment’s text.
*/
/**
* @memberof Comment#
* @member {string} text - the comment’s text
*/
/**
* @memberof Comment#
* @member {object} raws - Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node.
* * `left`: the space symbols between `/*` and the comment’s text.
* * `right`: the space symbols between the comment’s text.
*/
/**
* @memberof Comment#
* @member {object} raws Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node.
* * `left`: the space symbols between `/*` and the comment’s text.
* * `right`: the space symbols between the comment’s text.
*/
}
export default Comment;
export default Comment
+628 -634
View File
File diff suppressed because it is too large Load Diff
+198 -187
View File
@@ -1,7 +1,7 @@
import supportsColor from 'supports-color';
import chalk from 'chalk';
import supportsColor from 'supports-color'
import chalk from 'chalk'
import terminalHighlight from './terminal-highlight';
import terminalHighlight from './terminal-highlight'
/**
* The CSS parser throws this error for broken CSS.
@@ -21,213 +21,224 @@ import terminalHighlight from './terminal-highlight';
* try {
* postcss.parse('a{')
* } catch (error) {
* if ( error.name === 'CssSyntaxError' ) {
* if (error.name === 'CssSyntaxError') {
* error //=> CssSyntaxError
* }
* }
*
* @example
* // Raising error from plugin
* throw node.error('Unknown variable', { plugin: 'postcss-vars' });
* throw node.error('Unknown variable', { plugin: 'postcss-vars' })
*/
class CssSyntaxError {
/**
* @param {string} message Error message.
* @param {number} [line] Source line of the error.
* @param {number} [column] Source column of the error.
* @param {string} [source] Source code of the broken file.
* @param {string} [file] Absolute path to the broken file.
* @param {string} [plugin] PostCSS plugin name, if error came from plugin.
*/
constructor (message, line, column, source, file, plugin) {
/**
* @param {string} message - error message
* @param {number} [line] - source line of the error
* @param {number} [column] - source column of the error
* @param {string} [source] - source code of the broken file
* @param {string} [file] - absolute path to the broken file
* @param {string} [plugin] - PostCSS plugin name, if error came from plugin
*/
constructor(message, line, column, source, file, plugin) {
/**
* @member {string} - Always equal to `'CssSyntaxError'`. You should
* always check error type
* by `error.name === 'CssSyntaxError'` instead of
* `error instanceof CssSyntaxError`, because
* npm could have several PostCSS versions.
*
* @example
* if ( error.name === 'CssSyntaxError' ) {
* error //=> CssSyntaxError
* }
*/
this.name = 'CssSyntaxError';
/**
* @member {string} - Error message.
*
* @example
* error.message //=> 'Unclosed block'
*/
this.reason = message;
if ( file ) {
/**
* @member {string} - Absolute path to the broken file.
*
* @example
* error.file //=> 'a.sass'
* error.input.file //=> 'a.css'
*/
this.file = file;
}
if ( source ) {
/**
* @member {string} - Source code of the broken file.
*
* @example
* error.source //=> 'a { b {} }'
* error.input.column //=> 'a b { }'
*/
this.source = source;
}
if ( plugin ) {
/**
* @member {string} - Plugin name, if error came from plugin.
*
* @example
* error.plugin //=> 'postcss-vars'
*/
this.plugin = plugin;
}
if ( typeof line !== 'undefined' && typeof column !== 'undefined' ) {
/**
* @member {number} - Source line of the error.
*
* @example
* error.line //=> 2
* error.input.line //=> 4
*/
this.line = line;
/**
* @member {number} - Source column of the error.
*
* @example
* error.column //=> 1
* error.input.column //=> 4
*/
this.column = column;
}
this.setMessage();
if ( Error.captureStackTrace ) {
Error.captureStackTrace(this, CssSyntaxError);
}
}
setMessage() {
/**
* @member {string} - Full error text in the GNU error format
* with plugin, file, line and column.
*
* @example
* error.message //=> 'a.css:1:1: Unclosed block'
*/
this.message = this.plugin ? this.plugin + ': ' : '';
this.message += this.file ? this.file : '<css input>';
if ( typeof this.line !== 'undefined' ) {
this.message += ':' + this.line + ':' + this.column;
}
this.message += ': ' + this.reason;
}
/**
* Returns a few lines of CSS source that caused the error.
* Always equal to `'CssSyntaxError'`. You should always check error type
* by `error.name === 'CssSyntaxError'`
* instead of `error instanceof CssSyntaxError`,
* because npm could have several PostCSS versions.
*
* If the CSS has an input source map without `sourceContent`,
* this method will return an empty string.
*
* @param {boolean} [color] whether arrow will be colored red by terminal
* color codes. By default, PostCSS will detect
* color support by `process.stdout.isTTY`
* and `process.env.NODE_DISABLE_COLORS`.
* @type {string}
*
* @example
* error.showSourceCode() //=> " 4 | }
* // 5 | a {
* // > 6 | bad
* // | ^
* // 7 | }
* // 8 | b {"
*
* @return {string} few lines of CSS source that caused the error
* if (error.name === 'CssSyntaxError') {
* error //=> CssSyntaxError
* }
*/
showSourceCode(color) {
if ( !this.source ) return '';
let css = this.source;
if ( typeof color === 'undefined' ) color = supportsColor.stdout;
if ( color ) css = terminalHighlight(css);
let lines = css.split(/\r?\n/);
let start = Math.max(this.line - 3, 0);
let end = Math.min(this.line + 2, lines.length);
let maxWidth = String(end).length;
function mark(text) {
if ( color && chalk.red ) {
return chalk.red.bold(text);
} else {
return text;
}
}
function aside(text) {
if ( color && chalk.gray ) {
return chalk.gray(text);
} else {
return text;
}
}
return lines.slice(start, end).map( (line, index) => {
let number = start + 1 + index;
let gutter = ' ' + (' ' + number).slice(-maxWidth) + ' | ';
if ( number === this.line ) {
let spacing =
aside(gutter.replace(/\d/g, ' ')) +
line.slice(0, this.column - 1).replace(/[^\t]/g, ' ');
return mark('>') + aside(gutter) + line + '\n ' +
spacing + mark('^');
} else {
return ' ' + aside(gutter) + line;
}
}).join('\n');
}
this.name = 'CssSyntaxError'
/**
* Returns error position, message and source code of the broken part.
* Error message.
*
* @type {string}
*
* @example
* error.toString() //=> "CssSyntaxError: app.css:1:1: Unclosed block
* // > 1 | a {
* // | ^"
*
* @return {string} error position, message and source code
* error.message //=> 'Unclosed block'
*/
toString() {
let code = this.showSourceCode();
if ( code ) {
code = '\n\n' + code + '\n';
}
return this.name + ': ' + this.message + code;
this.reason = message
if (file) {
/**
* Absolute path to the broken file.
*
* @type {string}
*
* @example
* error.file //=> 'a.sass'
* error.input.file //=> 'a.css'
*/
this.file = file
}
if (source) {
/**
* Source code of the broken file.
*
* @type {string}
*
* @example
* error.source //=> 'a { b {} }'
* error.input.column //=> 'a b { }'
*/
this.source = source
}
if (plugin) {
/**
* Plugin name, if error came from plugin.
*
* @type {string}
*
* @example
* error.plugin //=> 'postcss-vars'
*/
this.plugin = plugin
}
if (typeof line !== 'undefined' && typeof column !== 'undefined') {
/**
* Source line of the error.
*
* @type {number}
*
* @example
* error.line //=> 2
* error.input.line //=> 4
*/
this.line = line
/**
* Source column of the error.
*
* @type {number}
*
* @example
* error.column //=> 1
* error.input.column //=> 4
*/
this.column = column
}
this.setMessage()
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CssSyntaxError)
}
}
setMessage () {
/**
* @memberof CssSyntaxError#
* @member {Input} input - Input object with PostCSS internal information
* about input file. If input has source map
* from previous tool, PostCSS will use origin
* (for example, Sass) source. You can use this
* object to get PostCSS input source.
* Full error text in the GNU error format
* with plugin, file, line and column.
*
* @type {string}
*
* @example
* error.input.file //=> 'a.css'
* error.file //=> 'a.sass'
* error.message //=> 'a.css:1:1: Unclosed block'
*/
this.message = this.plugin ? this.plugin + ': ' : ''
this.message += this.file ? this.file : '<css input>'
if (typeof this.line !== 'undefined') {
this.message += ':' + this.line + ':' + this.column
}
this.message += ': ' + this.reason
}
/**
* Returns a few lines of CSS source that caused the error.
*
* If the CSS has an input source map without `sourceContent`,
* this method will return an empty string.
*
* @param {boolean} [color] Whether arrow will be colored red by terminal
* color codes. By default, PostCSS will detect
* color support by `process.stdout.isTTY`
* and `process.env.NODE_DISABLE_COLORS`.
*
* @example
* error.showSourceCode() //=> " 4 | }
* // 5 | a {
* // > 6 | bad
* // | ^
* // 7 | }
* // 8 | b {"
*
* @return {string} Few lines of CSS source that caused the error.
*/
showSourceCode (color) {
if (!this.source) return ''
let css = this.source
if (typeof color === 'undefined') color = supportsColor.stdout
if (color) css = terminalHighlight(css)
const lines = css.split(/\r?\n/)
const start = Math.max(this.line - 3, 0)
const end = Math.min(this.line + 2, lines.length)
const maxWidth = String(end).length
function mark (text) {
if (color && chalk.red) {
return chalk.red.bold(text)
} else {
return text
}
}
function aside (text) {
if (color && chalk.gray) {
return chalk.gray(text)
} else {
return text
}
}
return lines.slice(start, end).map((line, index) => {
const number = start + 1 + index
const gutter = ' ' + (' ' + number).slice(-maxWidth) + ' | '
if (number === this.line) {
const spacing = aside(gutter.replace(/\d/g, ' ')) +
line.slice(0, this.column - 1).replace(/[^\t]/g, ' ')
return mark('>') + aside(gutter) + line + '\n ' + spacing + mark('^')
} else {
return ' ' + aside(gutter) + line
}
}).join('\n')
}
/**
* Returns error position, message and source code of the broken part.
*
* @example
* error.toString() //=> "CssSyntaxError: app.css:1:1: Unclosed block
* // > 1 | a {
* // | ^"
*
* @return {string} Error position, message and source code.
*/
toString () {
let code = this.showSourceCode()
if (code) {
code = '\n\n' + code + '\n'
}
return this.name + ': ' + this.message + code
}
/**
* @memberof CssSyntaxError#
* @member {Input} input Input object with PostCSS internal information
* about input file. If input has source map
* from previous tool, PostCSS will use origin
* (for example, Sass) source. You can use this
* object to get PostCSS input source.
*
* @example
* error.input.file //=> 'a.css'
* error.file //=> 'a.sass'
*/
}
export default CssSyntaxError;
export default CssSyntaxError
+60 -62
View File
@@ -1,4 +1,4 @@
import Node from './node';
import Node from './node'
/**
* Represents a CSS declaration.
@@ -6,74 +6,72 @@ import Node from './node';
* @extends Node
*
* @example
* const root = postcss.parse('a { color: black }');
* const decl = root.first.first;
* const root = postcss.parse('a { color: black }')
* const decl = root.first.first
* decl.type //=> 'decl'
* decl.toString() //=> ' color: black'
*/
class Declaration extends Node {
constructor (defaults) {
super(defaults)
this.type = 'decl'
}
constructor(defaults) {
super(defaults);
this.type = 'decl';
}
/**
* @memberof Declaration#
* @member {string} prop The declaration’s property name.
*
* @example
* const root = postcss.parse('a { color: black }')
* const decl = root.first.first
* decl.prop //=> 'color'
*/
/**
* @memberof Declaration#
* @member {string} prop - the declaration’s property name
*
* @example
* const root = postcss.parse('a { color: black }');
* const decl = root.first.first;
* decl.prop //=> 'color'
*/
/**
* @memberof Declaration#
* @member {string} value The declaration’s value.
*
* @example
* const root = postcss.parse('a { color: black }')
* const decl = root.first.first
* decl.value //=> 'black'
*/
/**
* @memberof Declaration#
* @member {string} value - the declaration’s value
*
* @example
* const root = postcss.parse('a { color: black }');
* const decl = root.first.first;
* decl.value //=> 'black'
*/
/**
* @memberof Declaration#
* @member {boolean} important - `true` if the declaration
* has an !important annotation.
*
* @example
* const root = postcss.parse('a { color: black !important; color: red }');
* root.first.first.important //=> true
* root.first.last.important //=> undefined
*/
/**
* @memberof Declaration#
* @member {object} raws - Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
* * `between`: the symbols between the property and value
* for declarations.
* * `important`: the content of the important statement,
* if it is not just `!important`.
*
* PostCSS cleans declaration from comments and extra spaces,
* but it stores origin content in raws properties.
* As such, if you don’t change a declaration’s value,
* PostCSS will use the raw value with comments.
*
* @example
* const root = postcss.parse('a {\n color:black\n}')
* root.first.first.raws //=> { before: '\n ', between: ':' }
*/
/**
* @memberof Declaration#
* @member {boolean} important `true` if the declaration
* has an !important annotation.
*
* @example
* const root = postcss.parse('a { color: black !important; color: red }')
* root.first.first.important //=> true
* root.first.last.important //=> undefined
*/
/**
* @memberof Declaration#
* @member {object} raws Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
* * `between`: the symbols between the property and value
* for declarations.
* * `important`: the content of the important statement,
* if it is not just `!important`.
*
* PostCSS cleans declaration from comments and extra spaces,
* but it stores origin content in raws properties.
* As such, if you don’t change a declaration’s value,
* PostCSS will use the raw value with comments.
*
* @example
* const root = postcss.parse('a {\n color:black\n}')
* root.first.first.raws //=> { before: '\n ', between: ':' }
*/
}
export default Declaration;
export default Declaration
+151 -144
View File
@@ -1,169 +1,176 @@
import CssSyntaxError from './css-syntax-error';
import PreviousMap from './previous-map';
import CssSyntaxError from './css-syntax-error'
import PreviousMap from './previous-map'
import path from 'path';
import path from 'path'
let sequence = 0;
let sequence = 0
/**
* Represents the source CSS.
*
* @example
* const root = postcss.parse(css, { from: file });
* const input = root.source.input;
* const root = postcss.parse(css, { from: file })
* const input = root.source.input
*/
class Input {
/**
* @param {string} css - input CSS source
* @param {object} [opts] - {@link Processor#process} options
*/
constructor(css, opts = { }) {
if ( css === null || typeof css === 'object' && !css.toString ) {
throw new Error(`PostCSS received ${ css } instead of CSS string`);
}
/**
* @member {string} - input CSS source
*
* @example
* const input = postcss.parse('a{}', { from: file }).input;
* input.css //=> "a{}";
*/
this.css = css.toString();
if ( this.css[0] === '\uFEFF' || this.css[0] === '\uFFFE' ) {
this.css = this.css.slice(1);
}
if ( opts.from ) {
if ( /^\w+:\/\//.test(opts.from) ) {
/**
* @member {string} - The absolute path to the CSS source file
* defined with the `from` option.
*
* @example
* const root = postcss.parse(css, { from: 'a.css' });
* root.source.input.file //=> '/home/ai/a.css'
*/
this.file = opts.from;
} else {
this.file = path.resolve(opts.from);
}
}
let map = new PreviousMap(this.css, opts);
if ( map.text ) {
/**
* @member {PreviousMap} - The input source map passed from
* a compilation step before PostCSS
* (for example, from Sass compiler).
*
* @example
* root.source.input.map.consumer().sources //=> ['a.sass']
*/
this.map = map;
let file = map.consumer().file;
if ( !this.file && file ) this.file = this.mapResolve(file);
}
if ( !this.file ) {
sequence += 1;
/**
* @member {string} - The unique ID of the CSS source. It will be
* created if `from` option is not provided
* (because PostCSS does not know the file path).
*
* @example
* const root = postcss.parse(css);
* root.source.input.file //=> undefined
* root.source.input.id //=> "<input css 1>"
*/
this.id = '<input css ' + sequence + '>';
}
if ( this.map ) this.map.file = this.from;
}
error(message, line, column, opts = { }) {
let result;
let origin = this.origin(line, column);
if ( origin ) {
result = new CssSyntaxError(message, origin.line, origin.column,
origin.source, origin.file, opts.plugin);
} else {
result = new CssSyntaxError(message, line, column,
this.css, this.file, opts.plugin);
}
result.input = { line, column, source: this.css };
if ( this.file ) result.input.file = this.file;
return result;
/**
* @param {string} css Input CSS source.
* @param {object} [opts] {@link Processor#process} options.
*/
constructor (css, opts = { }) {
if (css === null || (typeof css === 'object' && !css.toString)) {
throw new Error(`PostCSS received ${ css } instead of CSS string`)
}
/**
* Reads the input source map and returns a symbol position
* in the input source (e.g., in a Sass file that was compiled
* to CSS before being passed to PostCSS).
* Input CSS source
*
* @param {number} line - line in input CSS
* @param {number} column - column in input CSS
*
* @return {filePosition} position in input source
*
* @example
* root.source.input.origin(1, 1) //=> { file: 'a.css', line: 3, column: 1 }
*/
origin(line, column) {
if ( !this.map ) return false;
let consumer = this.map.consumer();
let from = consumer.originalPositionFor({ line, column });
if ( !from.source ) return false;
let result = {
file: this.mapResolve(from.source),
line: from.line,
column: from.column
};
let source = consumer.sourceContentFor(from.source);
if ( source ) result.source = source;
return result;
}
mapResolve(file) {
if ( /^\w+:\/\//.test(file) ) {
return file;
} else {
return path.resolve(this.map.consumer().sourceRoot || '.', file);
}
}
/**
* The CSS source identifier. Contains {@link Input#file} if the user
* set the `from` option, or {@link Input#id} if they did not.
* @type {string}
*
* @example
* const root = postcss.parse(css, { from: 'a.css' });
* root.source.input.from //=> "/home/ai/a.css"
*
* const root = postcss.parse(css);
* root.source.input.from //=> "<input css 1>"
* const input = postcss.parse('a{}', { from: file }).input
* input.css //=> "a{}"
*/
get from() {
return this.file || this.id;
this.css = css.toString()
if (this.css[0] === '\uFEFF' || this.css[0] === '\uFFFE') {
this.css = this.css.slice(1)
}
if (opts.from) {
if (/^\w+:\/\//.test(opts.from)) {
/**
* The absolute path to the CSS source file defined
* with the `from` option.
*
* @type {string}
*
* @example
* const root = postcss.parse(css, { from: 'a.css' })
* root.source.input.file //=> '/home/ai/a.css'
*/
this.file = opts.from
} else {
this.file = path.resolve(opts.from)
}
}
const map = new PreviousMap(this.css, opts)
if (map.text) {
/**
* The input source map passed from a compilation step before PostCSS
* (for example, from Sass compiler).
*
* @type {PreviousMap}
*
* @example
* root.source.input.map.consumer().sources //=> ['a.sass']
*/
this.map = map
const file = map.consumer().file
if (!this.file && file) this.file = this.mapResolve(file)
}
if (!this.file) {
sequence += 1
/**
* The unique ID of the CSS source. It will be created if `from` option
* is not provided (because PostCSS does not know the file path).
*
* @type {string}
*
* @example
* const root = postcss.parse(css)
* root.source.input.file //=> undefined
* root.source.input.id //=> "<input css 1>"
*/
this.id = '<input css ' + sequence + '>'
}
if (this.map) this.map.file = this.from
}
error (message, line, column, opts = { }) {
let result
const origin = this.origin(line, column)
if (origin) {
result = new CssSyntaxError(
message, origin.line, origin.column,
origin.source, origin.file, opts.plugin
)
} else {
result = new CssSyntaxError(
message, line, column, this.css, this.file, opts.plugin)
}
result.input = { line, column, source: this.css }
if (this.file) result.input.file = this.file
return result
}
/**
* Reads the input source map and returns a symbol position
* in the input source (e.g., in a Sass file that was compiled
* to CSS before being passed to PostCSS).
*
* @param {number} line Line in input CSS.
* @param {number} column Column in input CSS.
*
* @return {filePosition} Position in input source.
*
* @example
* root.source.input.origin(1, 1) //=> { file: 'a.css', line: 3, column: 1 }
*/
origin (line, column) {
if (!this.map) return false
const consumer = this.map.consumer()
const from = consumer.originalPositionFor({ line, column })
if (!from.source) return false
const result = {
file: this.mapResolve(from.source),
line: from.line,
column: from.column
}
const source = consumer.sourceContentFor(from.source)
if (source) result.source = source
return result
}
mapResolve (file) {
if (/^\w+:\/\//.test(file)) {
return file
} else {
return path.resolve(this.map.consumer().sourceRoot || '.', file)
}
}
/**
* The CSS source identifier. Contains {@link Input#file} if the user
* set the `from` option, or {@link Input#id} if they did not.
*
* @type {string}
*
* @example
* const root = postcss.parse(css, { from: 'a.css' })
* root.source.input.from //=> "/home/ai/a.css"
*
* const root = postcss.parse(css)
* root.source.input.from //=> "<input css 1>"
*/
get from () {
return this.file || this.id
}
}
export default Input;
export default Input
/**
* @typedef {object} filePosition
* @property {string} file - path to file
* @property {number} line - source line in file
* @property {number} column - source column in file
* @property {string} file Path to file.
* @property {number} line Source line in file.
* @property {number} column Source column in file.
*/
+328 -331
View File
@@ -1,11 +1,11 @@
import MapGenerator from './map-generator';
import stringify from './stringify';
import warnOnce from './warn-once';
import Result from './result';
import parse from './parse';
import MapGenerator from './map-generator'
import stringify from './stringify'
import warnOnce from './warn-once'
import Result from './result'
import parse from './parse'
function isPromise(obj) {
return typeof obj === 'object' && typeof obj.then === 'function';
function isPromise (obj) {
return typeof obj === 'object' && typeof obj.then === 'function'
}
/**
@@ -14,350 +14,347 @@ function isPromise(obj) {
* A `LazyResult` instance is returned by {@link Processor#process}.
*
* @example
* const lazy = postcss([cssnext]).process(css);
* const lazy = postcss([cssnext]).process(css)
*/
class LazyResult {
constructor (processor, css, opts) {
this.stringified = false
this.processed = false
constructor(processor, css, opts) {
this.stringified = false;
this.processed = false;
let root
if (typeof css === 'object' && css !== null && css.type === 'root') {
root = css
} else if (css instanceof LazyResult || css instanceof Result) {
root = css.root
if (css.map) {
if (typeof opts.map === 'undefined') opts.map = { }
if (!opts.map.inline) opts.map.inline = false
opts.map.prev = css.map
}
} else {
let parser = parse
if (opts.syntax) parser = opts.syntax.parse
if (opts.parser) parser = opts.parser
if (parser.parse) parser = parser.parse
let root;
if ( typeof css === 'object' && css !== null && css.type === 'root' ) {
root = css;
} else if ( css instanceof LazyResult || css instanceof Result ) {
root = css.root;
if ( css.map ) {
if ( typeof opts.map === 'undefined' ) opts.map = { };
if ( !opts.map.inline ) opts.map.inline = false;
opts.map.prev = css.map;
}
try {
root = parser(css, opts)
} catch (error) {
this.error = error
}
}
this.result = new Result(processor, root, opts)
}
/**
* Returns a {@link Processor} instance, which will be used
* for CSS transformations.
*
* @type {Processor}
*/
get processor () {
return this.result.processor
}
/**
* Options from the {@link Processor#process} call.
*
* @type {processOptions}
*/
get opts () {
return this.result.opts
}
/**
* Processes input CSS through synchronous plugins, converts `Root`
* to a CSS string and returns {@link Result#css}.
*
* This property will only work with synchronous plugins.
* If the processor contains any asynchronous plugins
* it will throw an error. This is why this method is only
* for debug purpose, you should always use {@link LazyResult#then}.
*
* @type {string}
* @see Result#css
*/
get css () {
return this.stringify().css
}
/**
* An alias for the `css` property. Use it with syntaxes
* that generate non-CSS output.
*
* This property will only work with synchronous plugins.
* If the processor contains any asynchronous plugins
* it will throw an error. This is why this method is only
* for debug purpose, you should always use {@link LazyResult#then}.
*
* @type {string}
* @see Result#content
*/
get content () {
return this.stringify().content
}
/**
* Processes input CSS through synchronous plugins
* and returns {@link Result#map}.
*
* This property will only work with synchronous plugins.
* If the processor contains any asynchronous plugins
* it will throw an error. This is why this method is only
* for debug purpose, you should always use {@link LazyResult#then}.
*
* @type {SourceMapGenerator}
* @see Result#map
*/
get map () {
return this.stringify().map
}
/**
* Processes input CSS through synchronous plugins
* and returns {@link Result#root}.
*
* This property will only work with synchronous plugins. If the processor
* contains any asynchronous plugins it will throw an error.
*
* This is why this method is only for debug purpose,
* you should always use {@link LazyResult#then}.
*
* @type {Root}
* @see Result#root
*/
get root () {
return this.sync().root
}
/**
* Processes input CSS through synchronous plugins
* and returns {@link Result#messages}.
*
* This property will only work with synchronous plugins. If the processor
* contains any asynchronous plugins it will throw an error.
*
* This is why this method is only for debug purpose,
* you should always use {@link LazyResult#then}.
*
* @type {Message[]}
* @see Result#messages
*/
get messages () {
return this.sync().messages
}
/**
* Processes input CSS through synchronous plugins
* and calls {@link Result#warnings()}.
*
* @return {Warning[]} Warnings from plugins.
*/
warnings () {
return this.sync().warnings()
}
/**
* Alias for the {@link LazyResult#css} property.
*
* @example
* lazy + '' === lazy.css
*
* @return {string} Output CSS.
*/
toString () {
return this.css
}
/**
* Processes input CSS through synchronous and asynchronous plugins
* and calls `onFulfilled` with a Result instance. If a plugin throws
* an error, the `onRejected` callback will be executed.
*
* It implements standard Promise API.
*
* @param {onFulfilled} onFulfilled Callback will be executed
* when all plugins will finish work.
* @param {onRejected} onRejected Callback will be executed on any error.
*
* @return {Promise} Promise API to make queue.
*
* @example
* postcss([cssnext]).process(css, { from: cssPath }).then(result => {
* console.log(result.css)
* })
*/
then (onFulfilled, onRejected) {
if (!('from' in this.opts)) {
warnOnce(
'Without `from` option PostCSS could generate wrong source map ' +
'and will not find Browserslist config. Set it to CSS file path ' +
'or to `undefined` to prevent this warning.'
)
}
return this.async().then(onFulfilled, onRejected)
}
/**
* Processes input CSS through synchronous and asynchronous plugins
* and calls onRejected for each error thrown in any plugin.
*
* It implements standard Promise API.
*
* @param {onRejected} onRejected Callback will be executed on any error.
*
* @return {Promise} Promise API to make queue.
*
* @example
* postcss([cssnext]).process(css).then(result => {
* console.log(result.css)
* }).catch(error => {
* console.error(error)
* })
*/
catch (onRejected) {
return this.async().catch(onRejected)
}
handleError (error, plugin) {
try {
this.error = error
if (error.name === 'CssSyntaxError' && !error.plugin) {
error.plugin = plugin.postcssPlugin
error.setMessage()
} else if (plugin.postcssVersion) {
const pluginName = plugin.postcssPlugin
const pluginVer = plugin.postcssVersion
const runtimeVer = this.result.processor.version
const a = pluginVer.split('.')
const b = runtimeVer.split('.')
if (a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1])) {
console.error(
'Unknown error from PostCSS plugin. Your current PostCSS version ' +
'is ' + runtimeVer + ', but ' + pluginName + ' uses ' +
pluginVer + '. Perhaps this is the source of the error below.'
)
}
}
} catch (err) {
if (console && console.error) console.error(err)
}
}
asyncTick (resolve, reject) {
if (this.plugin >= this.processor.plugins.length) {
this.processed = true
return resolve()
}
try {
const plugin = this.processor.plugins[this.plugin]
const promise = this.run(plugin)
this.plugin += 1
if (isPromise(promise)) {
promise.then(() => {
this.asyncTick(resolve, reject)
}).catch(error => {
this.handleError(error, plugin)
this.processed = true
reject(error)
})
} else {
this.asyncTick(resolve, reject)
}
} catch (error) {
this.processed = true
reject(error)
}
}
async () {
if (this.processed) {
return new Promise((resolve, reject) => {
if (this.error) {
reject(this.error)
} else {
let parser = parse;
if ( opts.syntax ) parser = opts.syntax.parse;
if ( opts.parser ) parser = opts.parser;
if ( parser.parse ) parser = parser.parse;
try {
root = parser(css, opts);
} catch (error) {
this.error = error;
}
resolve(this.stringify())
}
this.result = new Result(processor, root, opts);
})
}
if (this.processing) {
return this.processing
}
/**
* Returns a {@link Processor} instance, which will be used
* for CSS transformations.
* @type {Processor}
*/
get processor() {
return this.result.processor;
this.processing = new Promise((resolve, reject) => {
if (this.error) return reject(this.error)
this.plugin = 0
this.asyncTick(resolve, reject)
}).then(() => {
this.processed = true
return this.stringify()
})
return this.processing
}
sync () {
if (this.processed) return this.result
this.processed = true
if (this.processing) {
throw new Error(
'Use process(css).then(cb) to work with async plugins')
}
/**
* Options from the {@link Processor#process} call.
* @type {processOptions}
*/
get opts() {
return this.result.opts;
if (this.error) throw this.error
for (const plugin of this.result.processor.plugins) {
const promise = this.run(plugin)
if (isPromise(promise)) {
throw new Error(
'Use process(css).then(cb) to work with async plugins')
}
}
/**
* Processes input CSS through synchronous plugins, converts `Root`
* to a CSS string and returns {@link Result#css}.
*
* This property will only work with synchronous plugins.
* If the processor contains any asynchronous plugins
* it will throw an error. This is why this method is only
* for debug purpose, you should always use {@link LazyResult#then}.
*
* @type {string}
* @see Result#css
*/
get css() {
return this.stringify().css;
return this.result
}
run (plugin) {
this.result.lastPlugin = plugin
try {
return plugin(this.result.root, this.result)
} catch (error) {
this.handleError(error, plugin)
throw error
}
}
/**
* An alias for the `css` property. Use it with syntaxes
* that generate non-CSS output.
*
* This property will only work with synchronous plugins.
* If the processor contains any asynchronous plugins
* it will throw an error. This is why this method is only
* for debug purpose, you should always use {@link LazyResult#then}.
*
* @type {string}
* @see Result#content
*/
get content() {
return this.stringify().content;
}
stringify () {
if (this.stringified) return this.result
this.stringified = true
/**
* Processes input CSS through synchronous plugins
* and returns {@link Result#map}.
*
* This property will only work with synchronous plugins.
* If the processor contains any asynchronous plugins
* it will throw an error. This is why this method is only
* for debug purpose, you should always use {@link LazyResult#then}.
*
* @type {SourceMapGenerator}
* @see Result#map
*/
get map() {
return this.stringify().map;
}
this.sync()
/**
* Processes input CSS through synchronous plugins
* and returns {@link Result#root}.
*
* This property will only work with synchronous plugins. If the processor
* contains any asynchronous plugins it will throw an error.
*
* This is why this method is only for debug purpose,
* you should always use {@link LazyResult#then}.
*
* @type {Root}
* @see Result#root
*/
get root() {
return this.sync().root;
}
const opts = this.result.opts
let str = stringify
if (opts.syntax) str = opts.syntax.stringify
if (opts.stringifier) str = opts.stringifier
if (str.stringify) str = str.stringify
/**
* Processes input CSS through synchronous plugins
* and returns {@link Result#messages}.
*
* This property will only work with synchronous plugins. If the processor
* contains any asynchronous plugins it will throw an error.
*
* This is why this method is only for debug purpose,
* you should always use {@link LazyResult#then}.
*
* @type {Message[]}
* @see Result#messages
*/
get messages() {
return this.sync().messages;
}
/**
* Processes input CSS through synchronous plugins
* and calls {@link Result#warnings()}.
*
* @return {Warning[]} warnings from plugins
*/
warnings() {
return this.sync().warnings();
}
/**
* Alias for the {@link LazyResult#css} property.
*
* @example
* lazy + '' === lazy.css;
*
* @return {string} output CSS
*/
toString() {
return this.css;
}
/**
* Processes input CSS through synchronous and asynchronous plugins
* and calls `onFulfilled` with a Result instance. If a plugin throws
* an error, the `onRejected` callback will be executed.
*
* It implements standard Promise API.
*
* @param {onFulfilled} onFulfilled - callback will be executed
* when all plugins will finish work
* @param {onRejected} onRejected - callback will be executed on any error
*
* @return {Promise} Promise API to make queue
*
* @example
* postcss([cssnext]).process(css, { from: cssPath }).then(result => {
* console.log(result.css);
* });
*/
then(onFulfilled, onRejected) {
if (!('from' in this.opts)) {
warnOnce(
'Without `from` option PostCSS could generate wrong ' +
'source map and will not find Browserslist config. ' +
'Set it to CSS file path or to `undefined` to prevent ' +
'this warning.'
);
}
return this.async().then(onFulfilled, onRejected);
}
/**
* Processes input CSS through synchronous and asynchronous plugins
* and calls onRejected for each error thrown in any plugin.
*
* It implements standard Promise API.
*
* @param {onRejected} onRejected - callback will be executed on any error
*
* @return {Promise} Promise API to make queue
*
* @example
* postcss([cssnext]).process(css).then(result => {
* console.log(result.css);
* }).catch(error => {
* console.error(error);
* });
*/
catch(onRejected) {
return this.async().catch(onRejected);
}
handleError(error, plugin) {
try {
this.error = error;
if ( error.name === 'CssSyntaxError' && !error.plugin ) {
error.plugin = plugin.postcssPlugin;
error.setMessage();
} else if ( plugin.postcssVersion ) {
let pluginName = plugin.postcssPlugin;
let pluginVer = plugin.postcssVersion;
let runtimeVer = this.result.processor.version;
let a = pluginVer.split('.');
let b = runtimeVer.split('.');
if ( a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1]) ) {
console.error(
'Unknown error from PostCSS plugin. ' +
'Your current PostCSS version ' +
'is ' + runtimeVer + ', but ' + pluginName + ' ' +
'uses ' + pluginVer + '. Perhaps this is ' +
'the source of the error below.');
}
}
} catch (err) {
if ( console && console.error ) console.error(err);
}
}
asyncTick(resolve, reject) {
if ( this.plugin >= this.processor.plugins.length ) {
this.processed = true;
return resolve();
}
try {
let plugin = this.processor.plugins[this.plugin];
let promise = this.run(plugin);
this.plugin += 1;
if ( isPromise(promise) ) {
promise.then( () => {
this.asyncTick(resolve, reject);
}).catch( error => {
this.handleError(error, plugin);
this.processed = true;
reject(error);
});
} else {
this.asyncTick(resolve, reject);
}
} catch (error) {
this.processed = true;
reject(error);
}
}
async() {
if ( this.processed ) {
return new Promise( (resolve, reject) => {
if ( this.error ) {
reject(this.error);
} else {
resolve(this.stringify());
}
});
}
if ( this.processing ) {
return this.processing;
}
this.processing = new Promise( (resolve, reject) => {
if ( this.error ) return reject(this.error);
this.plugin = 0;
this.asyncTick(resolve, reject);
}).then( () => {
this.processed = true;
return this.stringify();
});
return this.processing;
}
sync() {
if ( this.processed ) return this.result;
this.processed = true;
if ( this.processing ) {
throw new Error(
'Use process(css).then(cb) to work with async plugins');
}
if ( this.error ) throw this.error;
for ( let plugin of this.result.processor.plugins ) {
let promise = this.run(plugin);
if ( isPromise(promise) ) {
throw new Error(
'Use process(css).then(cb) to work with async plugins');
}
}
return this.result;
}
run(plugin) {
this.result.lastPlugin = plugin;
try {
return plugin(this.result.root, this.result);
} catch (error) {
this.handleError(error, plugin);
throw error;
}
}
stringify() {
if ( this.stringified ) return this.result;
this.stringified = true;
this.sync();
let opts = this.result.opts;
let str = stringify;
if ( opts.syntax ) str = opts.syntax.stringify;
if ( opts.stringifier ) str = opts.stringifier;
if ( str.stringify ) str = str.stringify;
let map = new MapGenerator(str, this.result.root, this.result.opts);
let data = map.generate();
this.result.css = data[0];
this.result.map = data[1];
return this.result;
}
const map = new MapGenerator(str, this.result.root, this.result.opts)
const data = map.generate()
this.result.css = data[0]
this.result.map = data[1]
return this.result
}
}
export default LazyResult;
export default LazyResult
/**
* @callback onFulfilled
+73 -73
View File
@@ -3,88 +3,88 @@
* preserving parentheses and quotes.
*
* @example
* const list = postcss.list;
* const list = postcss.list
*
* @namespace list
*/
let list = {
const list = {
split(string, separators, last) {
let array = [];
let current = '';
let split = false;
split (string, separators, last) {
const array = []
let current = ''
let split = false
let func = 0;
let quote = false;
let escape = false;
let func = 0
let quote = false
let escape = false
for ( let i = 0; i < string.length; i++ ) {
let letter = string[i];
for (let i = 0; i < string.length; i++) {
const letter = string[i]
if ( quote ) {
if ( escape ) {
escape = false;
} else if ( letter === '\\' ) {
escape = true;
} else if ( letter === quote ) {
quote = false;
}
} else if ( letter === '"' || letter === '\'' ) {
quote = letter;
} else if ( letter === '(' ) {
func += 1;
} else if ( letter === ')' ) {
if ( func > 0 ) func -= 1;
} else if ( func === 0 ) {
if ( separators.indexOf(letter) !== -1 ) split = true;
}
if ( split ) {
if ( current !== '' ) array.push(current.trim());
current = '';
split = false;
} else {
current += letter;
}
if (quote) {
if (escape) {
escape = false
} else if (letter === '\\') {
escape = true
} else if (letter === quote) {
quote = false
}
} else if (letter === '"' || letter === '\'') {
quote = letter
} else if (letter === '(') {
func += 1
} else if (letter === ')') {
if (func > 0) func -= 1
} else if (func === 0) {
if (separators.indexOf(letter) !== -1) split = true
}
if ( last || current !== '' ) array.push(current.trim());
return array;
},
/**
* Safely splits space-separated values (such as those for `background`,
* `border-radius`, and other shorthand properties).
*
* @param {string} string - space-separated values
*
* @return {string[]} split values
*
* @example
* postcss.list.space('1px calc(10% + 1px)') //=> ['1px', 'calc(10% + 1px)']
*/
space(string) {
let spaces = [' ', '\n', '\t'];
return list.split(string, spaces);
},
/**
* Safely splits comma-separated values (such as those for `transition-*`
* and `background` properties).
*
* @param {string} string - comma-separated values
*
* @return {string[]} split values
*
* @example
* postcss.list.comma('black, linear-gradient(white, black)')
* //=> ['black', 'linear-gradient(white, black)']
*/
comma(string) {
let comma = ',';
return list.split(string, [comma], true);
if (split) {
if (current !== '') array.push(current.trim())
current = ''
split = false
} else {
current += letter
}
}
};
if (last || current !== '') array.push(current.trim())
return array
},
export default list;
/**
* Safely splits space-separated values (such as those for `background`,
* `border-radius`, and other shorthand properties).
*
* @param {string} string Space-separated values.
*
* @return {string[]} Split values.
*
* @example
* postcss.list.space('1px calc(10% + 1px)') //=> ['1px', 'calc(10% + 1px)']
*/
space (string) {
const spaces = [' ', '\n', '\t']
return list.split(string, spaces)
},
/**
* Safely splits comma-separated values (such as those for `transition-*`
* and `background` properties).
*
* @param {string} string Comma-separated values.
*
* @return {string[]} Split values.
*
* @example
* postcss.list.comma('black, linear-gradient(white, black)')
* //=> ['black', 'linear-gradient(white, black)']
*/
comma (string) {
const comma = ','
return list.split(string, [comma], true)
}
}
export default list
+248 -255
View File
@@ -1,279 +1,272 @@
import mozilla from 'source-map';
import path from 'path';
import mozilla from 'source-map'
import path from 'path'
export default class MapGenerator {
class MapGenerator {
constructor (stringify, root, opts) {
this.stringify = stringify
this.mapOpts = opts.map || { }
this.root = root
this.opts = opts
}
constructor(stringify, root, opts) {
this.stringify = stringify;
this.mapOpts = opts.map || { };
this.root = root;
this.opts = opts;
isMap () {
if (typeof this.opts.map !== 'undefined') {
return !!this.opts.map
} else {
return this.previous().length > 0
}
}
previous () {
if (!this.previousMaps) {
this.previousMaps = []
this.root.walk(node => {
if (node.source && node.source.input.map) {
const map = node.source.input.map
if (this.previousMaps.indexOf(map) === -1) {
this.previousMaps.push(map)
}
}
})
}
isMap() {
if ( typeof this.opts.map !== 'undefined' ) {
return !!this.opts.map;
} else {
return this.previous().length > 0;
}
return this.previousMaps
}
isInline () {
if (typeof this.mapOpts.inline !== 'undefined') {
return this.mapOpts.inline
}
previous() {
if ( !this.previousMaps ) {
this.previousMaps = [];
this.root.walk( node => {
if ( node.source && node.source.input.map ) {
let map = node.source.input.map;
if ( this.previousMaps.indexOf(map) === -1 ) {
this.previousMaps.push(map);
}
}
});
}
return this.previousMaps;
const annotation = this.mapOpts.annotation
if (typeof annotation !== 'undefined' && annotation !== true) {
return false
}
isInline() {
if ( typeof this.mapOpts.inline !== 'undefined' ) {
return this.mapOpts.inline;
}
if (this.previous().length) {
return this.previous().some(i => i.inline)
} else {
return true
}
}
let annotation = this.mapOpts.annotation;
if ( typeof annotation !== 'undefined' && annotation !== true ) {
return false;
}
isSourcesContent () {
if (typeof this.mapOpts.sourcesContent !== 'undefined') {
return this.mapOpts.sourcesContent
}
if (this.previous().length) {
return this.previous().some(i => i.withContent())
} else {
return true
}
}
if ( this.previous().length ) {
return this.previous().some( i => i.inline );
} else {
return true;
clearAnnotation () {
if (this.mapOpts.annotation === false) return
let node
for (let i = this.root.nodes.length - 1; i >= 0; i--) {
node = this.root.nodes[i]
if (node.type !== 'comment') continue
if (node.text.indexOf('# sourceMappingURL=') === 0) {
this.root.removeChild(i)
}
}
}
setSourcesContent () {
const already = { }
this.root.walk(node => {
if (node.source) {
const from = node.source.input.from
if (from && !already[from]) {
already[from] = true
const relative = this.relative(from)
this.map.setSourceContent(relative, node.source.input.css)
}
}
})
}
applyPrevMaps () {
for (const prev of this.previous()) {
const from = this.relative(prev.file)
const root = prev.root || path.dirname(prev.file)
let map
if (this.mapOpts.sourcesContent === false) {
map = new mozilla.SourceMapConsumer(prev.text)
if (map.sourcesContent) {
map.sourcesContent = map.sourcesContent.map(() => null)
}
} else {
map = prev.consumer()
}
this.map.applySourceMap(map, from, this.relative(root))
}
}
isAnnotation () {
if (this.isInline()) {
return true
} else if (typeof this.mapOpts.annotation !== 'undefined') {
return this.mapOpts.annotation
} else if (this.previous().length) {
return this.previous().some(i => i.annotation)
} else {
return true
}
}
toBase64 (str) {
if (Buffer) {
return Buffer.from(str).toString('base64')
} else {
return window.btoa(unescape(encodeURIComponent(str)))
}
}
addAnnotation () {
let content
if (this.isInline()) {
content = 'data:application/json;base64,' +
this.toBase64(this.map.toString())
} else if (typeof this.mapOpts.annotation === 'string') {
content = this.mapOpts.annotation
} else {
content = this.outputFile() + '.map'
}
isSourcesContent() {
if ( typeof this.mapOpts.sourcesContent !== 'undefined' ) {
return this.mapOpts.sourcesContent;
}
if ( this.previous().length ) {
return this.previous().some( i => i.withContent() );
} else {
return true;
}
let eol = '\n'
if (this.css.indexOf('\r\n') !== -1) eol = '\r\n'
this.css += eol + '/*# sourceMappingURL=' + content + ' */'
}
outputFile () {
if (this.opts.to) {
return this.relative(this.opts.to)
} else if (this.opts.from) {
return this.relative(this.opts.from)
} else {
return 'to.css'
}
}
generateMap () {
this.generateString()
if (this.isSourcesContent()) this.setSourcesContent()
if (this.previous().length > 0) this.applyPrevMaps()
if (this.isAnnotation()) this.addAnnotation()
if (this.isInline()) {
return [this.css]
} else {
return [this.css, this.map]
}
}
relative (file) {
if (file.indexOf('<') === 0) return file
if (/^\w+:\/\//.test(file)) return file
let from = this.opts.to ? path.dirname(this.opts.to) : '.'
if (typeof this.mapOpts.annotation === 'string') {
from = path.dirname(path.resolve(from, this.mapOpts.annotation))
}
clearAnnotation() {
if ( this.mapOpts.annotation === false ) return;
file = path.relative(from, file)
if (path.sep === '\\') {
return file.replace(/\\/g, '/')
} else {
return file
}
}
let node;
for ( let i = this.root.nodes.length - 1; i >= 0; i-- ) {
node = this.root.nodes[i];
if ( node.type !== 'comment' ) continue;
if ( node.text.indexOf('# sourceMappingURL=') === 0 ) {
this.root.removeChild(i);
sourcePath (node) {
if (this.mapOpts.from) {
return this.mapOpts.from
} else {
return this.relative(node.source.input.from)
}
}
generateString () {
this.css = ''
this.map = new mozilla.SourceMapGenerator({ file: this.outputFile() })
let line = 1
let column = 1
let lines, last
this.stringify(this.root, (str, node, type) => {
this.css += str
if (node && type !== 'end') {
if (node.source && node.source.start) {
this.map.addMapping({
source: this.sourcePath(node),
generated: { line, column: column - 1 },
original: {
line: node.source.start.line,
column: node.source.start.column - 1
}
})
} else {
this.map.addMapping({
source: '<no source>',
original: { line: 1, column: 0 },
generated: { line, column: column - 1 }
})
}
}
}
setSourcesContent() {
let already = { };
this.root.walk( node => {
if ( node.source ) {
let from = node.source.input.from;
if ( from && !already[from] ) {
already[from] = true;
let relative = this.relative(from);
this.map.setSourceContent(relative, node.source.input.css);
}
lines = str.match(/\n/g)
if (lines) {
line += lines.length
last = str.lastIndexOf('\n')
column = str.length - last
} else {
column += str.length
}
if (node && type !== 'start') {
if (node.source && node.source.end) {
this.map.addMapping({
source: this.sourcePath(node),
generated: { line, column: column - 1 },
original: {
line: node.source.end.line,
column: node.source.end.column
}
});
}
applyPrevMaps() {
for ( let prev of this.previous() ) {
let from = this.relative(prev.file);
let root = prev.root || path.dirname(prev.file);
let map;
if ( this.mapOpts.sourcesContent === false ) {
map = new mozilla.SourceMapConsumer(prev.text);
if ( map.sourcesContent ) {
map.sourcesContent = map.sourcesContent.map( () => null );
}
} else {
map = prev.consumer();
}
this.map.applySourceMap(map, from, this.relative(root));
}
}
isAnnotation() {
if ( this.isInline() ) {
return true;
} else if ( typeof this.mapOpts.annotation !== 'undefined' ) {
return this.mapOpts.annotation;
} else if ( this.previous().length ) {
return this.previous().some( i => i.annotation );
})
} else {
return true;
this.map.addMapping({
source: '<no source>',
original: { line: 1, column: 0 },
generated: { line, column: column - 1 }
})
}
}
})
}
generate () {
this.clearAnnotation()
if (this.isMap()) {
return this.generateMap()
} else {
let result = ''
this.stringify(this.root, i => {
result += i
})
return [result]
}
toBase64(str) {
if ( Buffer ) {
if ( Buffer.from && Buffer.from !== Uint8Array.from ) {
return Buffer.from(str).toString('base64');
} else {
return new Buffer(str).toString('base64');
}
} else {
return window.btoa(unescape(encodeURIComponent(str)));
}
}
addAnnotation() {
let content;
if ( this.isInline() ) {
content = 'data:application/json;base64,' +
this.toBase64(this.map.toString());
} else if ( typeof this.mapOpts.annotation === 'string' ) {
content = this.mapOpts.annotation;
} else {
content = this.outputFile() + '.map';
}
let eol = '\n';
if ( this.css.indexOf('\r\n') !== -1 ) eol = '\r\n';
this.css += eol + '/*# sourceMappingURL=' + content + ' */';
}
outputFile() {
if ( this.opts.to ) {
return this.relative(this.opts.to);
} else if ( this.opts.from ) {
return this.relative(this.opts.from);
} else {
return 'to.css';
}
}
generateMap() {
this.generateString();
if ( this.isSourcesContent() ) this.setSourcesContent();
if ( this.previous().length > 0 ) this.applyPrevMaps();
if ( this.isAnnotation() ) this.addAnnotation();
if ( this.isInline() ) {
return [this.css];
} else {
return [this.css, this.map];
}
}
relative(file) {
if ( file.indexOf('<') === 0 ) return file;
if ( /^\w+:\/\//.test(file) ) return file;
let from = this.opts.to ? path.dirname(this.opts.to) : '.';
if ( typeof this.mapOpts.annotation === 'string' ) {
from = path.dirname( path.resolve(from, this.mapOpts.annotation) );
}
file = path.relative(from, file);
if ( path.sep === '\\' ) {
return file.replace(/\\/g, '/');
} else {
return file;
}
}
sourcePath(node) {
if ( this.mapOpts.from ) {
return this.mapOpts.from;
} else {
return this.relative(node.source.input.from);
}
}
generateString() {
this.css = '';
this.map = new mozilla.SourceMapGenerator({ file: this.outputFile() });
let line = 1;
let column = 1;
let lines, last;
this.stringify(this.root, (str, node, type) => {
this.css += str;
if ( node && type !== 'end' ) {
if ( node.source && node.source.start ) {
this.map.addMapping({
source: this.sourcePath(node),
generated: { line, column: column - 1 },
original: {
line: node.source.start.line,
column: node.source.start.column - 1
}
});
} else {
this.map.addMapping({
source: '<no source>',
original: { line: 1, column: 0 },
generated: { line, column: column - 1 }
});
}
}
lines = str.match(/\n/g);
if ( lines ) {
line += lines.length;
last = str.lastIndexOf('\n');
column = str.length - last;
} else {
column += str.length;
}
if ( node && type !== 'start' ) {
if ( node.source && node.source.end ) {
this.map.addMapping({
source: this.sourcePath(node),
generated: { line, column: column - 1 },
original: {
line: node.source.end.line,
column: node.source.end.column
}
});
} else {
this.map.addMapping({
source: '<no source>',
original: { line: 1, column: 0 },
generated: { line, column: column - 1 }
});
}
}
});
}
generate() {
this.clearAnnotation();
if ( this.isMap() ) {
return this.generateMap();
} else {
let result = '';
this.stringify(this.root, i => {
result += i;
});
return [result];
}
}
}
}
export default MapGenerator
+478 -479
View File
@@ -1,29 +1,29 @@
import CssSyntaxError from './css-syntax-error';
import Stringifier from './stringifier';
import stringify from './stringify';
import CssSyntaxError from './css-syntax-error'
import Stringifier from './stringifier'
import stringify from './stringify'
let cloneNode = function (obj, parent) {
let cloned = new obj.constructor();
function cloneNode (obj, parent) {
const cloned = new obj.constructor()
for ( let i in obj ) {
if ( !obj.hasOwnProperty(i) ) continue;
let value = obj[i];
let type = typeof value;
for (const i in obj) {
if (!obj.hasOwnProperty(i)) continue
let value = obj[i]
const type = typeof value
if ( i === 'parent' && type === 'object' ) {
if (parent) cloned[i] = parent;
} else if ( i === 'source' ) {
cloned[i] = value;
} else if ( value instanceof Array ) {
cloned[i] = value.map( j => cloneNode(j, cloned) );
} else {
if ( type === 'object' && value !== null ) value = cloneNode(value);
cloned[i] = value;
}
if (i === 'parent' && type === 'object') {
if (parent) cloned[i] = parent
} else if (i === 'source') {
cloned[i] = value
} else if (value instanceof Array) {
cloned[i] = value.map(j => cloneNode(j, cloned))
} else {
if (type === 'object' && value !== null) value = cloneNode(value)
cloned[i] = value
}
}
return cloned;
};
return cloned
}
/**
* All node classes inherit the following common methods.
@@ -31,483 +31,482 @@ let cloneNode = function (obj, parent) {
* @abstract
*/
class Node {
/**
* @param {object} [defaults] Value for node properties.
*/
constructor (defaults = { }) {
this.raws = { }
if (typeof defaults !== 'object' && typeof defaults !== 'undefined') {
throw new Error(
'PostCSS nodes constructor accepts object, not ' +
JSON.stringify(defaults)
)
}
for (const name in defaults) {
this[name] = defaults[name]
}
}
/**
* @param {object} [defaults] - value for node properties
*/
constructor(defaults = { }) {
this.raws = { };
if ( typeof defaults !== 'object' && typeof defaults !== 'undefined' ) {
throw new Error(
'PostCSS nodes constructor accepts object, not ' +
JSON.stringify(defaults));
}
for ( let name in defaults ) {
this[name] = defaults[name];
}
/**
* Returns a `CssSyntaxError` instance containing the original position
* of the node in the source, showing line and column numbers and also
* a small excerpt to facilitate debugging.
*
* If present, an input source map will be used to get the original position
* of the source, even from a previous compilation step
* (e.g., from Sass compilation).
*
* This method produces very useful error messages.
*
* @param {string} message Error description.
* @param {object} [opts] Options.
* @param {string} opts.plugin Plugin name that created this error.
* PostCSS will set it automatically.
* @param {string} opts.word A word inside a node’s string that should
* be highlighted as the source of the error.
* @param {number} opts.index An index inside a node’s string that should
* be highlighted as the source of the error.
*
* @return {CssSyntaxError} Error object to throw it.
*
* @example
* if (!variables[name]) {
* throw decl.error('Unknown variable ' + name, { word: name })
* // CssSyntaxError: postcss-vars:a.sass:4:3: Unknown variable $black
* // color: $black
* // a
* // ^
* // background: white
* }
*/
error (message, opts = { }) {
if (this.source) {
const pos = this.positionBy(opts)
return this.source.input.error(message, pos.line, pos.column, opts)
} else {
return new CssSyntaxError(message)
}
}
/**
* This method is provided as a convenience wrapper for {@link Result#warn}.
*
* @param {Result} result The {@link Result} instance
* that will receive the warning.
* @param {string} text Warning message.
* @param {object} [opts] Options
* @param {string} opts.plugin Plugin name that created this warning.
* PostCSS will set it automatically.
* @param {string} opts.word A word inside a node’s string that should
* be highlighted as the source of the warning.
* @param {number} opts.index An index inside a node’s string that should
* be highlighted as the source of the warning.
*
* @return {Warning} Created warning object.
*
* @example
* const plugin = postcss.plugin('postcss-deprecated', () => {
* return (root, result) => {
* root.walkDecls('bad', decl => {
* decl.warn(result, 'Deprecated property bad')
* })
* }
* })
*/
warn (result, text, opts) {
const data = { node: this }
for (const i in opts) data[i] = opts[i]
return result.warn(text, data)
}
/**
* Removes the node from its parent and cleans the parent properties
* from the node and its children.
*
* @example
* if (decl.prop.match(/^-webkit-/)) {
* decl.remove()
* }
*
* @return {Node} Node to make calls chain.
*/
remove () {
if (this.parent) {
this.parent.removeChild(this)
}
this.parent = undefined
return this
}
/**
* Returns a CSS string representing the node.
*
* @param {stringifier|syntax} [stringifier] A syntax to use
* in string generation.
*
* @return {string} CSS string of this node.
*
* @example
* postcss.rule({ selector: 'a' }).toString() //=> "a {}"
*/
toString (stringifier = stringify) {
if (stringifier.stringify) stringifier = stringifier.stringify
let result = ''
stringifier(this, i => {
result += i
})
return result
}
/**
* Returns a clone of the node.
*
* The resulting cloned node and its (cloned) children will have
* a clean parent and code style properties.
*
* @param {object} [overrides] New properties to override in the clone.
*
* @example
* const cloned = decl.clone({ prop: '-moz-' + decl.prop })
* cloned.raws.before //=> undefined
* cloned.parent //=> undefined
* cloned.toString() //=> -moz-transform: scale(0)
*
* @return {Node} Clone of the node.
*/
clone (overrides = { }) {
const cloned = cloneNode(this)
for (const name in overrides) {
cloned[name] = overrides[name]
}
return cloned
}
/**
* Shortcut to clone the node and insert the resulting cloned node
* before the current node.
*
* @param {object} [overrides] Mew properties to override in the clone.
*
* @example
* decl.cloneBefore({ prop: '-moz-' + decl.prop })
*
* @return {Node} New node
*/
cloneBefore (overrides = { }) {
const cloned = this.clone(overrides)
this.parent.insertBefore(this, cloned)
return cloned
}
/**
* Shortcut to clone the node and insert the resulting cloned node
* after the current node.
*
* @param {object} [overrides] New properties to override in the clone.
*
* @return {Node} New node.
*/
cloneAfter (overrides = { }) {
const cloned = this.clone(overrides)
this.parent.insertAfter(this, cloned)
return cloned
}
/**
* Inserts node(s) before the current node and removes the current node.
*
* @param {...Node} nodes Mode(s) to replace current one.
*
* @example
* if (atrule.name === 'mixin') {
* atrule.replaceWith(mixinRules[atrule.params])
* }
*
* @return {Node} Current node to methods chain.
*/
replaceWith (...nodes) {
if (this.parent) {
for (const node of nodes) {
this.parent.insertBefore(this, node)
}
this.remove()
}
/**
* Returns a CssSyntaxError instance containing the original position
* of the node in the source, showing line and column numbers and also
* a small excerpt to facilitate debugging.
*
* If present, an input source map will be used to get the original position
* of the source, even from a previous compilation step
* (e.g., from Sass compilation).
*
* This method produces very useful error messages.
*
* @param {string} message - error description
* @param {object} [opts] - options
* @param {string} opts.plugin - plugin name that created this error.
* PostCSS will set it automatically.
* @param {string} opts.word - a word inside a node’s string that should
* be highlighted as the source of the error
* @param {number} opts.index - an index inside a node’s string that should
* be highlighted as the source of the error
*
* @return {CssSyntaxError} error object to throw it
*
* @example
* if ( !variables[name] ) {
* throw decl.error('Unknown variable ' + name, { word: name });
* // CssSyntaxError: postcss-vars:a.sass:4:3: Unknown variable $black
* // color: $black
* // a
* // ^
* // background: white
* }
*/
error(message, opts = { }) {
if ( this.source ) {
let pos = this.positionBy(opts);
return this.source.input.error(message, pos.line, pos.column, opts);
} else {
return new CssSyntaxError(message);
}
return this
}
/**
* Returns the next child of the node’s parent.
* Returns `undefined` if the current node is the last child.
*
* @return {Node|undefined} Next node.
*
* @example
* if (comment.text === 'delete next') {
* const next = comment.next()
* if (next) {
* next.remove()
* }
* }
*/
next () {
if (!this.parent) return undefined
const index = this.parent.index(this)
return this.parent.nodes[index + 1]
}
/**
* Returns the previous child of the node’s parent.
* Returns `undefined` if the current node is the first child.
*
* @return {Node|undefined} Previous node.
*
* @example
* const annotation = decl.prev()
* if (annotation.type === 'comment') {
* readAnnotation(annotation.text)
* }
*/
prev () {
if (!this.parent) return undefined
const index = this.parent.index(this)
return this.parent.nodes[index - 1]
}
/**
* Insert new node before current node to current node’s parent.
*
* Just alias for `node.parent.insertBefore(node, add)`.
*
* @param {Node|object|string|Node[]} add New node.
*
* @return {Node} This node for methods chain.
*
* @example
* decl.before('content: ""')
*/
before (add) {
this.parent.insertBefore(this, add)
return this
}
/**
* Insert new node after current node to current node’s parent.
*
* Just alias for `node.parent.insertAfter(node, add)`.
*
* @param {Node|object|string|Node[]} add New node.
*
* @return {Node} This node for methods chain.
*
* @example
* decl.after('color: black')
*/
after (add) {
this.parent.insertAfter(this, add)
return this
}
toJSON () {
const fixed = { }
for (const name in this) {
if (!this.hasOwnProperty(name)) continue
if (name === 'parent') continue
const value = this[name]
if (value instanceof Array) {
fixed[name] = value.map(i => {
if (typeof i === 'object' && i.toJSON) {
return i.toJSON()
} else {
return i
}
})
} else if (typeof value === 'object' && value.toJSON) {
fixed[name] = value.toJSON()
} else {
fixed[name] = value
}
}
/**
* This method is provided as a convenience wrapper for {@link Result#warn}.
*
* @param {Result} result - the {@link Result} instance
* that will receive the warning
* @param {string} text - warning message
* @param {object} [opts] - options
* @param {string} opts.plugin - plugin name that created this warning.
* PostCSS will set it automatically.
* @param {string} opts.word - a word inside a node’s string that should
* be highlighted as the source of the warning
* @param {number} opts.index - an index inside a node’s string that should
* be highlighted as the source of the warning
*
* @return {Warning} created warning object
*
* @example
* const plugin = postcss.plugin('postcss-deprecated', () => {
* return (root, result) => {
* root.walkDecls('bad', decl => {
* decl.warn(result, 'Deprecated property bad');
* });
* };
* });
*/
warn(result, text, opts) {
let data = { node: this };
for ( let i in opts ) data[i] = opts[i];
return result.warn(text, data);
return fixed
}
/**
* Returns a {@link Node#raws} value. If the node is missing
* the code style property (because the node was manually built or cloned),
* PostCSS will try to autodetect the code style property by looking
* at other nodes in the tree.
*
* @param {string} prop Name of code style property.
* @param {string} [defaultType] Name of default value, it can be missed
* if the value is the same as prop.
*
* @example
* const root = postcss.parse('a { background: white }')
* root.nodes[0].append({ prop: 'color', value: 'black' })
* root.nodes[0].nodes[1].raws.before //=> undefined
* root.nodes[0].nodes[1].raw('before') //=> ' '
*
* @return {string} Code style value.
*/
raw (prop, defaultType) {
const str = new Stringifier()
return str.raw(this, prop, defaultType)
}
/**
* Finds the Root instance of the node’s tree.
*
* @example
* root.nodes[0].nodes[0].root() === root
*
* @return {Root} Root parent.
*/
root () {
let result = this
while (result.parent) result = result.parent
return result
}
cleanRaws (keepBetween) {
delete this.raws.before
delete this.raws.after
if (!keepBetween) delete this.raws.between
}
positionInside (index) {
const string = this.toString()
let column = this.source.start.column
let line = this.source.start.line
for (let i = 0; i < index; i++) {
if (string[i] === '\n') {
column = 1
line += 1
} else {
column += 1
}
}
/**
* Removes the node from its parent and cleans the parent properties
* from the node and its children.
*
* @example
* if ( decl.prop.match(/^-webkit-/) ) {
* decl.remove();
* }
*
* @return {Node} node to make calls chain
*/
remove() {
if ( this.parent ) {
this.parent.removeChild(this);
}
this.parent = undefined;
return this;
return { line, column }
}
positionBy (opts) {
let pos = this.source.start
if (opts.index) {
pos = this.positionInside(opts.index)
} else if (opts.word) {
const index = this.toString().indexOf(opts.word)
if (index !== -1) pos = this.positionInside(index)
}
return pos
}
/**
* Returns a CSS string representing the node.
*
* @param {stringifier|syntax} [stringifier] - a syntax to use
* in string generation
*
* @return {string} CSS string of this node
*
* @example
* postcss.rule({ selector: 'a' }).toString() //=> "a {}"
*/
toString(stringifier = stringify) {
if ( stringifier.stringify ) stringifier = stringifier.stringify;
let result = '';
stringifier(this, i => {
result += i;
});
return result;
}
/**
* @memberof Node#
* @member {string} type String representing the node’s type.
* Possible values are `root`, `atrule`, `rule`,
* `decl`, or `comment`.
*
* @example
* postcss.decl({ prop: 'color', value: 'black' }).type //=> 'decl'
*/
/**
* Returns a clone of the node.
*
* The resulting cloned node and its (cloned) children will have
* a clean parent and code style properties.
*
* @param {object} [overrides] - new properties to override in the clone.
*
* @example
* const cloned = decl.clone({ prop: '-moz-' + decl.prop });
* cloned.raws.before //=> undefined
* cloned.parent //=> undefined
* cloned.toString() //=> -moz-transform: scale(0)
*
* @return {Node} clone of the node
*/
clone(overrides = { }) {
let cloned = cloneNode(this);
for ( let name in overrides ) {
cloned[name] = overrides[name];
}
return cloned;
}
/**
* @memberof Node#
* @member {Container} parent The node’s parent node.
*
* @example
* root.nodes[0].parent === root
*/
/**
* Shortcut to clone the node and insert the resulting cloned node
* before the current node.
*
* @param {object} [overrides] - new properties to override in the clone.
*
* @example
* decl.cloneBefore({ prop: '-moz-' + decl.prop });
*
* @return {Node} - new node
*/
cloneBefore(overrides = { }) {
let cloned = this.clone(overrides);
this.parent.insertBefore(this, cloned);
return cloned;
}
/**
* Shortcut to clone the node and insert the resulting cloned node
* after the current node.
*
* @param {object} [overrides] - new properties to override in the clone.
*
* @return {Node} - new node
*/
cloneAfter(overrides = { }) {
let cloned = this.clone(overrides);
this.parent.insertAfter(this, cloned);
return cloned;
}
/**
* Inserts node(s) before the current node and removes the current node.
*
* @param {...Node} nodes - node(s) to replace current one
*
* @example
* if ( atrule.name == 'mixin' ) {
* atrule.replaceWith(mixinRules[atrule.params]);
* }
*
* @return {Node} current node to methods chain
*/
replaceWith(...nodes) {
if (this.parent) {
for (let node of nodes) {
this.parent.insertBefore(this, node);
}
this.remove();
}
return this;
}
/**
* Returns the next child of the node’s parent.
* Returns `undefined` if the current node is the last child.
*
* @return {Node|undefined} next node
*
* @example
* if ( comment.text === 'delete next' ) {
* const next = comment.next();
* if ( next ) {
* next.remove();
* }
* }
*/
next() {
if ( !this.parent ) return undefined;
let index = this.parent.index(this);
return this.parent.nodes[index + 1];
}
/**
* Returns the previous child of the node’s parent.
* Returns `undefined` if the current node is the first child.
*
* @return {Node|undefined} previous node
*
* @example
* const annotation = decl.prev();
* if ( annotation.type == 'comment' ) {
* readAnnotation(annotation.text);
* }
*/
prev() {
if ( !this.parent ) return undefined;
let index = this.parent.index(this);
return this.parent.nodes[index - 1];
}
/**
* Insert new node before current node to current node’s parent.
*
* Just alias for `node.parent.insertBefore(node, add)`.
*
* @param {Node|object|string|Node[]} add - new node
*
* @return {Node} this node for methods chain.
*
* @example
* decl.before('content: ""');
*/
before(add) {
this.parent.insertBefore(this, add);
return this;
}
/**
* Insert new node after current node to current node’s parent.
*
* Just alias for `node.parent.insertAfter(node, add)`.
*
* @param {Node|object|string|Node[]} add - new node
*
* @return {Node} this node for methods chain.
*
* @example
* decl.after('color: black');
*/
after(add) {
this.parent.insertAfter(this, add);
return this;
}
toJSON() {
let fixed = { };
for ( let name in this ) {
if ( !this.hasOwnProperty(name) ) continue;
if ( name === 'parent' ) continue;
let value = this[name];
if ( value instanceof Array ) {
fixed[name] = value.map( i => {
if ( typeof i === 'object' && i.toJSON ) {
return i.toJSON();
} else {
return i;
}
});
} else if ( typeof value === 'object' && value.toJSON ) {
fixed[name] = value.toJSON();
} else {
fixed[name] = value;
}
}
return fixed;
}
/**
* Returns a {@link Node#raws} value. If the node is missing
* the code style property (because the node was manually built or cloned),
* PostCSS will try to autodetect the code style property by looking
* at other nodes in the tree.
*
* @param {string} prop - name of code style property
* @param {string} [defaultType] - name of default value, it can be missed
* if the value is the same as prop
*
* @example
* const root = postcss.parse('a { background: white }');
* root.nodes[0].append({ prop: 'color', value: 'black' });
* root.nodes[0].nodes[1].raws.before //=> undefined
* root.nodes[0].nodes[1].raw('before') //=> ' '
*
* @return {string} code style value
*/
raw(prop, defaultType) {
let str = new Stringifier();
return str.raw(this, prop, defaultType);
}
/**
* Finds the Root instance of the node’s tree.
*
* @example
* root.nodes[0].nodes[0].root() === root
*
* @return {Root} root parent
*/
root() {
let result = this;
while ( result.parent ) result = result.parent;
return result;
}
cleanRaws(keepBetween) {
delete this.raws.before;
delete this.raws.after;
if ( !keepBetween ) delete this.raws.between;
}
positionInside(index) {
let string = this.toString();
let column = this.source.start.column;
let line = this.source.start.line;
for ( let i = 0; i < index; i++ ) {
if ( string[i] === '\n' ) {
column = 1;
line += 1;
} else {
column += 1;
}
}
return { line, column };
}
positionBy(opts) {
let pos = this.source.start;
if ( opts.index ) {
pos = this.positionInside(opts.index);
} else if ( opts.word ) {
let index = this.toString().indexOf(opts.word);
if ( index !== -1 ) pos = this.positionInside(index);
}
return pos;
}
/**
* @memberof Node#
* @member {string} type - String representing the node’s type.
* Possible values are `root`, `atrule`, `rule`,
* `decl`, or `comment`.
*
* @example
* postcss.decl({ prop: 'color', value: 'black' }).type //=> 'decl'
*/
/**
* @memberof Node#
* @member {Container} parent - the node’s parent node.
*
* @example
* root.nodes[0].parent == root;
*/
/**
* @memberof Node#
* @member {source} source - the input source of the node
*
* The property is used in source map generation.
*
* If you create a node manually (e.g., with `postcss.decl()`),
* that node will not have a `source` property and will be absent
* from the source map. For this reason, the plugin developer should
* consider cloning nodes to create new ones (in which case the new node’s
* source will reference the original, cloned node) or setting
* the `source` property manually.
*
* ```js
* // Bad
* const prefixed = postcss.decl({
* prop: '-moz-' + decl.prop,
* value: decl.value
* });
*
* // Good
* const prefixed = decl.clone({ prop: '-moz-' + decl.prop });
* ```
*
* ```js
* if ( atrule.name == 'add-link' ) {
* const rule = postcss.rule({ selector: 'a', source: atrule.source });
* atrule.parent.insertBefore(atrule, rule);
* }
* ```
*
* @example
* decl.source.input.from //=> '/home/ai/a.sass'
* decl.source.start //=> { line: 10, column: 2 }
* decl.source.end //=> { line: 10, column: 12 }
*/
/**
* @memberof Node#
* @member {object} raws - Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
* * `after`: the space symbols after the last child of the node
* to the end of the node.
* * `between`: the symbols between the property and value
* for declarations, selector and `{` for rules, or last parameter
* and `{` for at-rules.
* * `semicolon`: contains true if the last child has
* an (optional) semicolon.
* * `afterName`: the space between the at-rule name and its parameters.
* * `left`: the space symbols between `/*` and the comment’s text.
* * `right`: the space symbols between the comment’s text
* and <code>*&#47;</code>.
* * `important`: the content of the important statement,
* if it is not just `!important`.
*
* PostCSS cleans selectors, declaration values and at-rule parameters
* from comments and extra spaces, but it stores origin content in raws
* properties. As such, if you don’t change a declaration’s value,
* PostCSS will use the raw value with comments.
*
* @example
* const root = postcss.parse('a {\n color:black\n}')
* root.first.first.raws //=> { before: '\n ', between: ':' }
*/
/**
* @memberof Node#
* @member {source} source The input source of the node.
*
* The property is used in source map generation.
*
* If you create a node manually (e.g., with `postcss.decl()`),
* that node will not have a `source` property and will be absent
* from the source map. For this reason, the plugin developer should
* consider cloning nodes to create new ones (in which case the new node’s
* source will reference the original, cloned node) or setting
* the `source` property manually.
*
* ```js
* // Bad
* const prefixed = postcss.decl({
* prop: '-moz-' + decl.prop,
* value: decl.value
* })
*
* // Good
* const prefixed = decl.clone({ prop: '-moz-' + decl.prop })
* ```
*
* ```js
* if (atrule.name === 'add-link') {
* const rule = postcss.rule({ selector: 'a', source: atrule.source })
* atrule.parent.insertBefore(atrule, rule)
* }
* ```
*
* @example
* decl.source.input.from //=> '/home/ai/a.sass'
* decl.source.start //=> { line: 10, column: 2 }
* decl.source.end //=> { line: 10, column: 12 }
*/
/**
* @memberof Node#
* @member {object} raws Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
* * `after`: the space symbols after the last child of the node
* to the end of the node.
* * `between`: the symbols between the property and value
* for declarations, selector and `{` for rules, or last parameter
* and `{` for at-rules.
* * `semicolon`: contains true if the last child has
* an (optional) semicolon.
* * `afterName`: the space between the at-rule name and its parameters.
* * `left`: the space symbols between `/*` and the comment’s text.
* * `right`: the space symbols between the comment’s text
* and <code>*&#47;</code>.
* * `important`: the content of the important statement,
* if it is not just `!important`.
*
* PostCSS cleans selectors, declaration values and at-rule parameters
* from comments and extra spaces, but it stores origin content in raws
* properties. As such, if you don’t change a declaration’s value,
* PostCSS will use the raw value with comments.
*
* @example
* const root = postcss.parse('a {\n color:black\n}')
* root.first.first.raws //=> { before: '\n ', between: ':' }
*/
}
export default Node;
export default Node
/**
* @typedef {object} position
* @property {number} line - source line in file
* @property {number} column - source column in file
* @property {number} line Source line in file.
* @property {number} column Source column in file.
*/
/**
* @typedef {object} source
* @property {Input} input - {@link Input} with input file
* @property {position} start - The starting position of the node’s source
* @property {position} end - The ending position of the node’s source
* @property {Input} input {@link Input} with input file
* @property {position} start The starting position of the node’s source.
* @property {position} end The ending position of the node’s source.
*/
+27 -30
View File
@@ -1,34 +1,31 @@
import Parser from './parser';
import Input from './input';
import Parser from './parser'
import Input from './input'
export default function parse(css, opts) {
if ( opts && opts.safe ) {
throw new Error('Option safe was removed. ' +
'Use parser: require("postcss-safe-parser")');
function parse (css, opts) {
const input = new Input(css, opts)
const parser = new Parser(input)
try {
parser.parse()
} catch (e) {
if (e.name === 'CssSyntaxError' && opts && opts.from) {
if (/\.scss$/i.test(opts.from)) {
e.message += '\nYou tried to parse SCSS with ' +
'the standard CSS parser; ' +
'try again with the postcss-scss parser'
} else if (/\.sass/i.test(opts.from)) {
e.message += '\nYou tried to parse Sass with ' +
'the standard CSS parser; ' +
'try again with the postcss-sass parser'
} else if (/\.less$/i.test(opts.from)) {
e.message += '\nYou tried to parse Less with ' +
'the standard CSS parser; ' +
'try again with the postcss-less parser'
}
}
throw e
}
let input = new Input(css, opts);
let parser = new Parser(input);
try {
parser.parse();
} catch (e) {
if ( e.name === 'CssSyntaxError' && opts && opts.from ) {
if ( /\.scss$/i.test(opts.from) ) {
e.message += '\nYou tried to parse SCSS with ' +
'the standard CSS parser; ' +
'try again with the postcss-scss parser';
} else if ( /\.sass/i.test(opts.from) ) {
e.message += '\nYou tried to parse Sass with ' +
'the standard CSS parser; ' +
'try again with the postcss-sass parser';
} else if ( /\.less$/i.test(opts.from) ) {
e.message += '\nYou tried to parse Less with ' +
'the standard CSS parser; ' +
'try again with the postcss-less parser';
}
}
throw e;
}
return parser.root;
return parser.root
}
export default parse
+487 -499
View File
File diff suppressed because it is too large Load Diff
+1231 -1231
View File
File diff suppressed because it is too large Load Diff
+101 -97
View File
@@ -1,37 +1,37 @@
import Declaration from './declaration';
import Processor from './processor';
import stringify from './stringify';
import Comment from './comment';
import AtRule from './at-rule';
import vendor from './vendor';
import parse from './parse';
import list from './list';
import Rule from './rule';
import Root from './root';
import Declaration from './declaration'
import Processor from './processor'
import stringify from './stringify'
import Comment from './comment'
import AtRule from './at-rule'
import vendor from './vendor'
import parse from './parse'
import list from './list'
import Rule from './rule'
import Root from './root'
/**
* Create a new {@link Processor} instance that will apply `plugins`
* as CSS processors.
*
* @param {Array.<Plugin|pluginFunction>|Processor} plugins - PostCSS
* plugins. See {@link Processor#use} for plugin format.
* @param {Array.<Plugin|pluginFunction>|Processor} plugins PostCSS plugins.
* See {@link Processor#use} for plugin format.
*
* @return {Processor} Processor to process multiple CSS
* @return {Processor} Processor to process multiple CSS.
*
* @example
* import postcss from 'postcss';
* import postcss from 'postcss'
*
* postcss(plugins).process(css, { from, to }).then(result => {
* console.log(result.css);
* });
* console.log(result.css)
* })
*
* @namespace postcss
*/
function postcss(...plugins) {
if ( plugins.length === 1 && Array.isArray(plugins[0]) ) {
plugins = plugins[0];
}
return new Processor(plugins);
function postcss (...plugins) {
if (plugins.length === 1 && Array.isArray(plugins[0])) {
plugins = plugins[0]
}
return new Processor(plugins)
}
/**
@@ -41,9 +41,9 @@ function postcss(...plugins) {
* version of the plugin.
*
* ```js
* const processor = postcss([replace]);
* processor.plugins[0].postcssPlugin //=> 'postcss-replace'
* processor.plugins[0].postcssVersion //=> '5.1.0'
* const processor = postcss([replace])
* processor.plugins[0].postcssPlugin //=> 'postcss-replace'
* processor.plugins[0].postcssVersion //=> '6.0.0'
* ```
*
* The plugin function receives 2 arguments: {@link Root}
@@ -54,18 +54,18 @@ function postcss(...plugins) {
* ```js
* const cleaner = postcss.plugin('postcss-cleaner', () => {
* return (root, result) => {
* result.root = postcss.root();
* };
* });
* result.root = postcss.root()
* }
* })
* ```
*
* As a convenience, plugins also expose a `process` method so that you can use
* them as standalone tools.
*
* ```js
* cleaner.process(css, processOpts, pluginOpts);
* cleaner.process(css, processOpts, pluginOpts)
* // This is equivalent to:
* postcss([ cleaner(pluginOpts) ]).process(css, processOpts);
* postcss([ cleaner(pluginOpts) ]).process(css, processOpts)
* ```
*
* Asynchronous plugins should return a `Promise` instance.
@@ -75,12 +75,12 @@ function postcss(...plugins) {
* return (root, result) => {
* return new Promise( (resolve, reject) => {
* fs.readFile('base.css', (base) => {
* root.prepend(base);
* resolve();
* });
* });
* };
* });
* root.prepend(base)
* resolve()
* })
* })
* }
* })
* ```
*
* Add warnings using the {@link Node#warn} method.
@@ -90,152 +90,156 @@ function postcss(...plugins) {
* postcss.plugin('postcss-caniuse-test', () => {
* return (root, result) => {
* root.walkDecls(decl => {
* if ( !caniuse.support(decl.prop) ) {
* decl.warn(result, 'Some browsers do not support ' + decl.prop);
* if (!caniuse.support(decl.prop)) {
* decl.warn(result, 'Some browsers do not support ' + decl.prop)
* }
* });
* };
* });
* })
* }
* })
* ```
*
* @param {string} name - PostCSS plugin name. Same as in `name`
* property in `package.json`. It will be saved
* in `plugin.postcssPlugin` property.
* @param {function} initializer - will receive plugin options
* and should return {@link pluginFunction}
* @param {string} name PostCSS plugin name. Same as in `name`
* property in `package.json`. It will be saved
* in `plugin.postcssPlugin` property.
* @param {function} initializer Will receive plugin options
* and should return {@link pluginFunction}
*
* @return {Plugin} PostCSS plugin
* @return {Plugin} PostCSS plugin.
*/
postcss.plugin = function plugin(name, initializer) {
let creator = function (...args) {
let transformer = initializer(...args);
transformer.postcssPlugin = name;
transformer.postcssVersion = (new Processor()).version;
return transformer;
};
postcss.plugin = function plugin (name, initializer) {
function creator (...args) {
const transformer = initializer(...args)
transformer.postcssPlugin = name
transformer.postcssVersion = (new Processor()).version
return transformer
}
let cache;
Object.defineProperty(creator, 'postcss', {
get() {
if ( !cache ) cache = creator();
return cache;
}
});
let cache
Object.defineProperty(creator, 'postcss', {
get () {
if (!cache) cache = creator()
return cache
}
})
creator.process = function (css, processOpts, pluginOpts) {
return postcss([ creator(pluginOpts) ]).process(css, processOpts);
};
creator.process = function (css, processOpts, pluginOpts) {
return postcss([creator(pluginOpts)]).process(css, processOpts)
}
return creator;
};
return creator
}
/**
* Default function to convert a node tree into a CSS string.
*
* @param {Node} node - start node for stringifing. Usually {@link Root}.
* @param {builder} builder - function to concatenate CSS from node’s parts
* or generate string and source map
* @param {Node} node Start node for stringifing. Usually {@link Root}.
* @param {builder} builder Function to concatenate CSS from node’s parts
* or generate string and source map.
*
* @return {void}
*
* @function
*/
postcss.stringify = stringify;
postcss.stringify = stringify
/**
* Parses source css and returns a new {@link Root} node,
* which contains the source CSS nodes.
*
* @param {string|toString} css - string with input CSS or any object
* with toString() method, like a Buffer
* @param {processOptions} [opts] - options with only `from` and `map` keys
* @param {string|toString} css String with input CSS or any object
* with toString() method, like a Buffer
* @param {processOptions} [opts] Options with only `from` and `map` keys.
*
* @return {Root} PostCSS AST
* @return {Root} PostCSS AST.
*
* @example
* // Simple CSS concatenation with source map support
* const root1 = postcss.parse(css1, { from: file1 });
* const root2 = postcss.parse(css2, { from: file2 });
* root1.append(root2).toResult().css;
* const root1 = postcss.parse(css1, { from: file1 })
* const root2 = postcss.parse(css2, { from: file2 })
* root1.append(root2).toResult().css
*
* @function
*/
postcss.parse = parse;
postcss.parse = parse
/**
* @member {vendor} - Contains the {@link vendor} module.
* Contains the {@link vendor} module.
*
* @type {vendor}
*
* @example
* postcss.vendor.unprefixed('-moz-tab') //=> ['tab']
*/
postcss.vendor = vendor;
postcss.vendor = vendor
/**
* @member {list} - Contains the {@link list} module.
* Contains the {@link list} module.
*
* @member {list}
*
* @example
* postcss.list.space('5px calc(10% + 5px)') //=> ['5px', 'calc(10% + 5px)']
*/
postcss.list = list;
postcss.list = list
/**
* Creates a new {@link Comment} node.
*
* @param {object} [defaults] - properties for the new node.
* @param {object} [defaults] Properties for the new node.
*
* @return {Comment} new Comment node
* @return {Comment} New comment node
*
* @example
* postcss.comment({ text: 'test' })
*/
postcss.comment = defaults => new Comment(defaults);
postcss.comment = defaults => new Comment(defaults)
/**
* Creates a new {@link AtRule} node.
*
* @param {object} [defaults] - properties for the new node.
* @param {object} [defaults] Properties for the new node.
*
* @return {AtRule} new AtRule node
* @return {AtRule} new at-rule node
*
* @example
* postcss.atRule({ name: 'charset' }).toString() //=> "@charset"
*/
postcss.atRule = defaults => new AtRule(defaults);
postcss.atRule = defaults => new AtRule(defaults)
/**
* Creates a new {@link Declaration} node.
*
* @param {object} [defaults] - properties for the new node.
* @param {object} [defaults] Properties for the new node.
*
* @return {Declaration} new Declaration node
* @return {Declaration} new declaration node
*
* @example
* postcss.decl({ prop: 'color', value: 'red' }).toString() //=> "color: red"
*/
postcss.decl = defaults => new Declaration(defaults);
postcss.decl = defaults => new Declaration(defaults)
/**
* Creates a new {@link Rule} node.
*
* @param {object} [defaults] - properties for the new node.
* @param {object} [defaults] Properties for the new node.
*
* @return {Rule} new Rule node
* @return {Rule} new rule node
*
* @example
* postcss.rule({ selector: 'a' }).toString() //=> "a {\n}"
*/
postcss.rule = defaults => new Rule(defaults);
postcss.rule = defaults => new Rule(defaults)
/**
* Creates a new {@link Root} node.
*
* @param {object} [defaults] - properties for the new node.
* @param {object} [defaults] Properties for the new node.
*
* @return {Root} new Root node
* @return {Root} new root node.
*
* @example
* postcss.root({ after: '\n' }).toString() //=> "\n"
*/
postcss.root = defaults => new Root(defaults);
postcss.root = defaults => new Root(defaults)
export default postcss;
export default postcss
+111 -122
View File
@@ -1,17 +1,13 @@
import mozilla from 'source-map';
import path from 'path';
import fs from 'fs';
import mozilla from 'source-map'
import path from 'path'
import fs from 'fs'
function fromBase64(str) {
if ( Buffer ) {
if ( Buffer.from && Buffer.from !== Uint8Array.from ) {
return Buffer.from(str, 'base64').toString();
} else {
return new Buffer(str, 'base64').toString();
}
} else {
return window.atob(str);
}
function fromBase64 (str) {
if (Buffer) {
return Buffer.from(str, 'base64').toString()
} else {
return window.atob(str)
}
}
/**
@@ -22,129 +18,122 @@ function fromBase64(str) {
* near input file (according `from` option).
*
* @example
* const root = postcss.parse(css, { from: 'a.sass.css' });
* const root = postcss.parse(css, { from: 'a.sass.css' })
* root.input.map //=> PreviousMap
*/
class PreviousMap {
/**
* @param {string} css Input CSS source.
* @param {processOptions} [opts] {@link Processor#process} options.
*/
constructor (css, opts) {
this.loadAnnotation(css)
/**
* @param {string} css - input CSS source
* @param {processOptions} [opts] - {@link Processor#process} options
*/
constructor(css, opts) {
this.loadAnnotation(css);
/**
* @member {boolean} - Was source map inlined by data-uri to input CSS.
*/
this.inline = this.startWith(this.annotation, 'data:');
let prev = opts.map ? opts.map.prev : undefined;
let text = this.loadMap(opts.from, prev);
if ( text ) this.text = text;
}
/**
* Create a instance of `SourceMapGenerator` class
* from the `source-map` library to work with source map information.
* Was source map inlined by data-uri to input CSS.
*
* It is lazy method, so it will create object only on first call
* and then it will use cache.
*
* @return {SourceMapGenerator} object with source map information
* @type {boolean}
*/
consumer() {
if ( !this.consumerCache ) {
this.consumerCache = new mozilla.SourceMapConsumer(this.text);
}
return this.consumerCache;
this.inline = this.startWith(this.annotation, 'data:')
const prev = opts.map ? opts.map.prev : undefined
const text = this.loadMap(opts.from, prev)
if (text) this.text = text
}
/**
* Create a instance of `SourceMapGenerator` class
* from the `source-map` library to work with source map information.
*
* It is lazy method, so it will create object only on first call
* and then it will use cache.
*
* @return {SourceMapGenerator} Object with source map information.
*/
consumer () {
if (!this.consumerCache) {
this.consumerCache = new mozilla.SourceMapConsumer(this.text)
}
return this.consumerCache
}
/**
* Does source map contains `sourcesContent` with input source text.
*
* @return {boolean} Is `sourcesContent` present
*/
withContent() {
return !!(this.consumer().sourcesContent &&
this.consumer().sourcesContent.length > 0);
/**
* Does source map contains `sourcesContent` with input source text.
*
* @return {boolean} Is `sourcesContent` present.
*/
withContent () {
return !!(this.consumer().sourcesContent &&
this.consumer().sourcesContent.length > 0)
}
startWith (string, start) {
if (!string) return false
return string.substr(0, start.length) === start
}
loadAnnotation (css) {
const match = css.match(/\/\*\s*# sourceMappingURL=(.*)\s*\*\//)
if (match) this.annotation = match[1].trim()
}
decodeInline (text) {
const baseCharsetUri = /^data:application\/json;charset=utf-?8;base64,/
const baseUri = /^data:application\/json;base64,/
const uri = 'data:application/json,'
if (this.startWith(text, uri)) {
return decodeURIComponent(text.substr(uri.length))
} else if (baseCharsetUri.test(text) || baseUri.test(text)) {
return fromBase64(text.substr(RegExp.lastMatch.length))
} else {
const encoding = text.match(/data:application\/json;([^,]+),/)[1]
throw new Error('Unsupported source map encoding ' + encoding)
}
}
startWith(string, start) {
if ( !string ) return false;
return string.substr(0, start.length) === start;
}
loadAnnotation(css) {
let match = css.match(/\/\*\s*# sourceMappingURL=(.*)\s*\*\//);
if ( match ) this.annotation = match[1].trim();
}
decodeInline(text) {
// data:application/json;charset=utf-8;base64,
// data:application/json;charset=utf8;base64,
// data:application/json;base64,
let baseUri = /^data:application\/json;(?:charset=utf-?8;)?base64,/;
let uri = 'data:application/json,';
if ( this.startWith(text, uri) ) {
return decodeURIComponent( text.substr(uri.length) );
} else if ( baseUri.test(text) ) {
return fromBase64(text.substr(RegExp.lastMatch.length));
loadMap (file, prev) {
if (prev === false) return false
if (prev) {
if (typeof prev === 'string') {
return prev
} else if (typeof prev === 'function') {
const prevPath = prev(file)
if (prevPath && fs.existsSync && fs.existsSync(prevPath)) {
return fs.readFileSync(prevPath, 'utf-8').toString().trim()
} else {
let encoding = text.match(/data:application\/json;([^,]+),/)[1];
throw new Error('Unsupported source map encoding ' + encoding);
throw new Error(
'Unable to load previous source map: ' + prevPath.toString())
}
} else if (prev instanceof mozilla.SourceMapConsumer) {
return mozilla.SourceMapGenerator.fromSourceMap(prev).toString()
} else if (prev instanceof mozilla.SourceMapGenerator) {
return prev.toString()
} else if (this.isMap(prev)) {
return JSON.stringify(prev)
} else {
throw new Error(
'Unsupported previous source map format: ' + prev.toString())
}
} else if (this.inline) {
return this.decodeInline(this.annotation)
} else if (this.annotation) {
let map = this.annotation
if (file) map = path.join(path.dirname(file), map)
this.root = path.dirname(map)
if (fs.existsSync && fs.existsSync(map)) {
return fs.readFileSync(map, 'utf-8').toString().trim()
} else {
return false
}
}
}
loadMap(file, prev) {
if ( prev === false ) return false;
if ( prev ) {
if ( typeof prev === 'string' ) {
return prev;
} else if ( typeof prev === 'function' ) {
let prevPath = prev(file);
if ( prevPath && fs.existsSync && fs.existsSync(prevPath) ) {
return fs.readFileSync(prevPath, 'utf-8').toString().trim();
} else {
throw new Error('Unable to load previous source map: ' +
prevPath.toString());
}
} else if ( prev instanceof mozilla.SourceMapConsumer ) {
return mozilla.SourceMapGenerator
.fromSourceMap(prev).toString();
} else if ( prev instanceof mozilla.SourceMapGenerator ) {
return prev.toString();
} else if ( this.isMap(prev) ) {
return JSON.stringify(prev);
} else {
throw new Error('Unsupported previous source map format: ' +
prev.toString());
}
} else if ( this.inline ) {
return this.decodeInline(this.annotation);
} else if ( this.annotation ) {
let map = this.annotation;
if ( file ) map = path.join(path.dirname(file), map);
this.root = path.dirname(map);
if ( fs.existsSync && fs.existsSync(map) ) {
return fs.readFileSync(map, 'utf-8').toString().trim();
} else {
return false;
}
}
}
isMap(map) {
if ( typeof map !== 'object' ) return false;
return typeof map.mappings === 'string' ||
typeof map._mappings === 'string';
}
isMap (map) {
if (typeof map !== 'object') return false
return typeof map.mappings === 'string' || typeof map._mappings === 'string'
}
}
export default PreviousMap;
export default PreviousMap
+139 -138
View File
@@ -1,139 +1,141 @@
import LazyResult from './lazy-result';
import LazyResult from './lazy-result'
/**
* Contains plugins to process CSS. Create one `Processor` instance,
* initialize its plugins, and then use that instance on numerous CSS files.
*
* @example
* const processor = postcss([autoprefixer, precss]);
* processor.process(css1).then(result => console.log(result.css));
* processor.process(css2).then(result => console.log(result.css));
* const processor = postcss([autoprefixer, precss])
* processor.process(css1).then(result => console.log(result.css))
* processor.process(css2).then(result => console.log(result.css))
*/
class Processor {
/**
* @param {Array.<Plugin|pluginFunction>|Processor} plugins PostCSS plugins.
* See {@link Processor#use} for plugin format.
*/
constructor (plugins = []) {
/**
* @param {Array.<Plugin|pluginFunction>|Processor} plugins - PostCSS
* plugins. See {@link Processor#use} for plugin format.
*/
constructor(plugins = []) {
/**
* @member {string} - Current PostCSS version.
*
* @example
* if ( result.processor.version.split('.')[0] !== '6' ) {
* throw new Error('This plugin works only with PostCSS 6');
* }
*/
this.version = '6.0.23';
/**
* @member {pluginFunction[]} - Plugins added to this processor.
*
* @example
* const processor = postcss([autoprefixer, precss]);
* processor.plugins.length //=> 2
*/
this.plugins = this.normalize(plugins);
}
/**
* Adds a plugin to be used as a CSS processor.
* Current PostCSS version.
*
* PostCSS plugin can be in 4 formats:
* * A plugin created by {@link postcss.plugin} method.
* * A function. PostCSS will pass the function a @{link Root}
* as the first argument and current {@link Result} instance
* as the second.
* * An object with a `postcss` method. PostCSS will use that method
* as described in #2.
* * Another {@link Processor} instance. PostCSS will copy plugins
* from that instance into this one.
*
* Plugins can also be added by passing them as arguments when creating
* a `postcss` instance (see [`postcss(plugins)`]).
*
* Asynchronous plugins should return a `Promise` instance.
*
* @param {Plugin|pluginFunction|Processor} plugin - PostCSS plugin
* or {@link Processor}
* with plugins
* @type {string}
*
* @example
* const processor = postcss()
* .use(autoprefixer)
* .use(precss);
*
* @return {Processes} current processor to make methods chain
* if (result.processor.version.split('.')[0] !== '6') {
* throw new Error('This plugin works only with PostCSS 6')
* }
*/
use(plugin) {
this.plugins = this.plugins.concat(this.normalize([plugin]));
return this;
}
this.version = '6.0.23'
/**
* Plugins added to this processor.
*
* @type {pluginFunction[]}
*
* @example
* const processor = postcss([autoprefixer, precss])
* processor.plugins.length //=> 2
*/
this.plugins = this.normalize(plugins)
}
/**
* Adds a plugin to be used as a CSS processor.
*
* PostCSS plugin can be in 4 formats:
* * A plugin created by {@link postcss.plugin} method.
* * A function. PostCSS will pass the function a @{link Root}
* as the first argument and current {@link Result} instance
* as the second.
* * An object with a `postcss` method. PostCSS will use that method
* as described in #2.
* * Another {@link Processor} instance. PostCSS will copy plugins
* from that instance into this one.
*
* Plugins can also be added by passing them as arguments when creating
* a `postcss` instance (see [`postcss(plugins)`]).
*
* Asynchronous plugins should return a `Promise` instance.
*
* @param {Plugin|pluginFunction|Processor} plugin PostCSS plugin
* or {@link Processor}
* with plugins.
*
* @example
* const processor = postcss()
* .use(autoprefixer)
* .use(precss)
*
* @return {Processes} Current processor to make methods chain.
*/
use (plugin) {
this.plugins = this.plugins.concat(this.normalize([plugin]))
return this
}
/**
* Parses source CSS and returns a {@link LazyResult} Promise proxy.
* Because some plugins can be asynchronous it doesn’t make
* any transformations. Transformations will be applied
* in the {@link LazyResult} methods.
*
* @param {string|toString|Result} css - String with input CSS or
* any object with a `toString()`
* method, like a Buffer.
* Optionally, send a {@link Result}
* instance and the processor will
* take the {@link Root} from it.
* @param {processOptions} [opts] - options
* @param {string|toString|Result} css String with input CSS or any object
* with a `toString()` method,
* like a Buffer. Optionally, send
* a {@link Result} instance
* and the processor will take
* the {@link Root} from it.
* @param {processOptions} [opts] Options.
*
* @return {LazyResult} Promise proxy
* @return {LazyResult} Promise proxy.
*
* @example
* processor.process(css, { from: 'a.css', to: 'a.out.css' })
* .then(result => {
* console.log(result.css);
* });
* console.log(result.css)
* })
*/
process(css, opts = { }) {
return new LazyResult(this, css, opts);
process (css, opts = { }) {
return new LazyResult(this, css, opts)
}
normalize (plugins) {
let normalized = []
for (let i of plugins) {
if (i.postcss) i = i.postcss
if (typeof i === 'object' && Array.isArray(i.plugins)) {
normalized = normalized.concat(i.plugins)
} else if (typeof i === 'function') {
normalized.push(i)
} else if (typeof i === 'object' && (i.parse || i.stringify)) {
throw new Error(
'PostCSS syntaxes cannot be used as plugins. Instead, please use ' +
'one of the syntax/parser/stringifier options as outlined ' +
'in your PostCSS runner documentation.'
)
} else {
throw new Error(i + ' is not a PostCSS plugin')
}
}
normalize(plugins) {
let normalized = [];
for ( let i of plugins ) {
if ( i.postcss ) i = i.postcss;
if ( typeof i === 'object' && Array.isArray(i.plugins) ) {
normalized = normalized.concat(i.plugins);
} else if ( typeof i === 'function' ) {
normalized.push(i);
} else if ( typeof i === 'object' && (i.parse || i.stringify) ) {
throw new Error('PostCSS syntaxes cannot be used as plugins. ' +
'Instead, please use one of the ' +
'syntax/parser/stringifier options as ' +
'outlined in your PostCSS ' +
'runner documentation.');
} else {
throw new Error(i + ' is not a PostCSS plugin');
}
}
return normalized;
}
return normalized
}
}
export default Processor;
export default Processor
/**
* @callback builder
* @param {string} part - part of generated CSS connected to this node
* @param {Node} node - AST node
* @param {"start"|"end"} [type] - node’s part type
* @param {string} part Part of generated CSS connected to this node.
* @param {Node} node AST node.
* @param {"start"|"end"} [type] Node’s part type.
*/
/**
* @callback parser
*
* @param {string|toString} css - string with input CSS or any object
* with toString() method, like a Buffer
* @param {processOptions} [opts] - options with only `from` and `map` keys
* @param {string|toString} css String with input CSS or any object
* with toString() method, like a Buffer.
* @param {processOptions} [opts] Options with only `from` and `map` keys.
*
* @return {Root} PostCSS AST
*/
@@ -141,17 +143,17 @@ export default Processor;
/**
* @callback stringifier
*
* @param {Node} node - start node for stringifing. Usually {@link Root}.
* @param {builder} builder - function to concatenate CSS from node’s parts
* or generate string and source map
* @param {Node} node Start node for stringifing. Usually {@link Root}.
* @param {builder} builder Function to concatenate CSS from node’s parts
* or generate string and source map.
*
* @return {void}
*/
/**
* @typedef {object} syntax
* @property {parser} parse - function to generate AST by string
* @property {stringifier} stringify - function to generate string by AST
* @property {parser} parse Function to generate AST by string.
* @property {stringifier} stringify Function to generate string by AST.
*/
/**
@@ -161,45 +163,44 @@ export default Processor;
/**
* @callback pluginFunction
* @param {Root} root - parsed input CSS
* @param {Result} result - result to set warnings or check other plugins
* @param {Root} root Parsed input CSS.
* @param {Result} result Result to set warnings or check other plugins.
*/
/**
* @typedef {object} Plugin
* @property {function} postcss - PostCSS plugin function
* @property {function} postcss PostCSS plugin function.
*/
/**
* @typedef {object} processOptions
* @property {string} from - the path of the CSS source file.
* You should always set `from`,
* because it is used in source map
* generation and syntax error messages.
* @property {string} to - the path where you’ll put the output
* CSS file. You should always set `to`
* to generate correct source maps.
* @property {parser} parser - function to generate AST by string
* @property {stringifier} stringifier - class to generate string by AST
* @property {syntax} syntax - object with `parse` and `stringify`
* @property {object} map - source map options
* @property {boolean} map.inline - does source map should
* be embedded in the output
* CSS as a base64-encoded
* comment
* @property {string|object|false|function} map.prev - source map content
* from a previous
* processing step
* (for example, Sass).
* PostCSS will try to find
* previous map
* automatically, so you
* could disable it by
* `false` value.
* @property {boolean} map.sourcesContent - does PostCSS should set
* the origin content to map
* @property {string|false} map.annotation - does PostCSS should set
* annotation comment to map
* @property {string} map.from - override `from` in map’s
* `sources`
* @property {string} from The path of the CSS source file.
* You should always set `from`,
* because it is used in source map
* generation and syntax error messages.
* @property {string} to The path where you’ll put the output
* CSS file. You should always set `to`
* to generate correct source maps.
* @property {parser} parser Function to generate AST by string.
* @property {stringifier} stringifier Class to generate string by AST.
* @property {syntax} syntax Object with `parse` and `stringify`.
* @property {object} map Source map options.
* @property {boolean} map.inline Does source map should
* be embedded in the output
* CSS as a base64-encoded
* comment.
* @property {string|object|false|function} map.prev Source map content
* from a previous
* processing step
* (for example, Sass).
* PostCSS will try to find
* previous map automatically,
* so you could disable it by
* `false` value.
* @property {boolean} map.sourcesContent Does PostCSS should set
* the origin content to map.
* @property {string|false} map.annotation Does PostCSS should set
* annotation comment to map.
* @property {string} map.from Override `from` in map’s
* sources`.
*/
+144 -139
View File
@@ -1,4 +1,4 @@
import Warning from './warning';
import Warning from './warning'
/**
* Provides the result of the PostCSS transformations.
@@ -7,167 +7,172 @@ import Warning from './warning';
* or {@link Root#toResult} methods.
*
* @example
* postcss([cssnext]).process(css).then(function (result) {
* console.log(result.css);
* });
* postcss([cssnext]).process(css).then(result => {
* console.log(result.css)
* })
*
* @example
* var result2 = postcss.parse(css).toResult();
* const result2 = postcss.parse(css).toResult()
*/
class Result {
/**
* @param {Processor} processor Processor used for this transformation.
* @param {Root} root Root node after all transformations.
* @param {processOptions} opts Options from the {@link Processor#process}
* or {@link Root#toResult}.
*/
constructor (processor, root, opts) {
/**
* @param {Processor} processor - processor used for this transformation.
* @param {Root} root - Root node after all transformations.
* @param {processOptions} opts - options from the {@link Processor#process}
* or {@link Root#toResult}
*/
constructor(processor, root, opts) {
/**
* @member {Processor} - The Processor instance used
* for this transformation.
*
* @example
* for ( let plugin of result.processor.plugins) {
* if ( plugin.postcssPlugin === 'postcss-bad' ) {
* throw 'postcss-good is incompatible with postcss-bad';
* }
* });
*/
this.processor = processor;
/**
* @member {Message[]} - Contains messages from plugins
* (e.g., warnings or custom messages).
* Each message should have type
* and plugin properties.
*
* @example
* postcss.plugin('postcss-min-browser', () => {
* return (root, result) => {
* var browsers = detectMinBrowsersByCanIUse(root);
* result.messages.push({
* type: 'min-browser',
* plugin: 'postcss-min-browser',
* browsers: browsers
* });
* };
* });
*/
this.messages = [];
/**
* @member {Root} - Root node after all transformations.
*
* @example
* root.toResult().root == root;
*/
this.root = root;
/**
* @member {processOptions} - Options from the {@link Processor#process}
* or {@link Root#toResult} call
* that produced this Result instance.
*
* @example
* root.toResult(opts).opts == opts;
*/
this.opts = opts;
/**
* @member {string} - A CSS string representing of {@link Result#root}.
*
* @example
* postcss.parse('a{}').toResult().css //=> "a{}"
*/
this.css = undefined;
/**
* @member {SourceMapGenerator} - An instance of `SourceMapGenerator`
* class from the `source-map` library,
* representing changes
* to the {@link Result#root} instance.
*
* @example
* result.map.toJSON() //=> { version: 3, file: 'a.css', … }
*
* @example
* if ( result.map ) {
* fs.writeFileSync(result.opts.to + '.map', result.map.toString());
* }
*/
this.map = undefined;
}
/**
* Returns for @{link Result#css} content.
* The Processor instance used for this transformation.
*
* @type {Processor}
*
* @example
* result + '' === result.css
*
* @return {string} string representing of {@link Result#root}
* for (const plugin of result.processor.plugins) {
* if (plugin.postcssPlugin === 'postcss-bad') {
* throw 'postcss-good is incompatible with postcss-bad'
* }
* })
*/
toString() {
return this.css;
this.processor = processor
/**
* Contains messages from plugins (e.g., warnings or custom messages).
* Each message should have type and plugin properties.
*
* @type {Message[]}
*
* @example
* postcss.plugin('postcss-min-browser', () => {
* return (root, result) => {
* const browsers = detectMinBrowsersByCanIUse(root)
* result.messages.push({
* type: 'min-browser',
* plugin: 'postcss-min-browser',
* browsers
* })
* }
* })
*/
this.messages = []
/**
* Root node after all transformations.
*
* @type {Root}
*
* @example
* root.toResult().root === root
*/
this.root = root
/**
* Options from the {@link Processor#process} or {@link Root#toResult} call
* that produced this Result instance.
*
* @type {processOptions}
*
* @example
* root.toResult(opts).opts === opts
*/
this.opts = opts
/**
* A CSS string representing of {@link Result#root}.
*
* @type {string}
*
* @example
* postcss.parse('a{}').toResult().css //=> "a{}"
*/
this.css = undefined
/**
* An instance of `SourceMapGenerator` class from the `source-map` library,
* representing changes to the {@link Result#root} instance.
*
* @type {SourceMapGenerator}
*
* @example
* result.map.toJSON() //=> { version: 3, file: 'a.css', … }
*
* @example
* if (result.map) {
* fs.writeFileSync(result.opts.to + '.map', result.map.toString())
* }
*/
this.map = undefined
}
/**
* Returns for @{link Result#css} content.
*
* @example
* result + '' === result.css
*
* @return {string} String representing of {@link Result#root}.
*/
toString () {
return this.css
}
/**
* Creates an instance of {@link Warning} and adds it
* to {@link Result#messages}.
*
* @param {string} text Warning message.
* @param {Object} [opts] Warning options.
* @param {Node} opts.node CSS node that caused the warning.
* @param {string} opts.word Word in CSS source that caused the warning.
* @param {number} opts.index Index in CSS node string that caused
* the warning.
* @param {string} opts.plugin Name of the plugin that created
* this warning. {@link Result#warn} fills
* this property automatically.
*
* @return {Warning} Created warning.
*/
warn (text, opts = { }) {
if (!opts.plugin) {
if (this.lastPlugin && this.lastPlugin.postcssPlugin) {
opts.plugin = this.lastPlugin.postcssPlugin
}
}
/**
* Creates an instance of {@link Warning} and adds it
* to {@link Result#messages}.
*
* @param {string} text - warning message
* @param {Object} [opts] - warning options
* @param {Node} opts.node - CSS node that caused the warning
* @param {string} opts.word - word in CSS source that caused the warning
* @param {number} opts.index - index in CSS node string that caused
* the warning
* @param {string} opts.plugin - name of the plugin that created
* this warning. {@link Result#warn} fills
* this property automatically.
*
* @return {Warning} created warning
*/
warn(text, opts = { }) {
if ( !opts.plugin ) {
if ( this.lastPlugin && this.lastPlugin.postcssPlugin ) {
opts.plugin = this.lastPlugin.postcssPlugin;
}
}
const warning = new Warning(text, opts)
this.messages.push(warning)
let warning = new Warning(text, opts);
this.messages.push(warning);
return warning
}
return warning;
}
/**
/**
* Returns warnings from plugins. Filters {@link Warning} instances
* from {@link Result#messages}.
*
* @example
* result.warnings().forEach(warn => {
* console.warn(warn.toString());
* });
* console.warn(warn.toString())
* })
*
* @return {Warning[]} warnings from plugins
* @return {Warning[]} Warnings from plugins.
*/
warnings() {
return this.messages.filter( i => i.type === 'warning' );
}
/**
* An alias for the {@link Result#css} property.
* Use it with syntaxes that generate non-CSS output.
* @type {string}
*
* @example
* result.css === result.content;
*/
get content() {
return this.css;
}
warnings () {
return this.messages.filter(i => i.type === 'warning')
}
/**
* An alias for the {@link Result#css} property.
* Use it with syntaxes that generate non-CSS output.
*
* @type {string}
*
* @example
* result.css === result.content
*/
get content () {
return this.css
}
}
export default Result;
export default Result
/**
* @typedef {object} Message
* @property {string} type - message type
* @property {string} plugin - source PostCSS plugin name
* @property {string} type Message type.
* @property {string} plugin Source PostCSS plugin name.
*/
+64 -66
View File
@@ -1,4 +1,4 @@
import Container from './container';
import Container from './container'
/**
* Represents a CSS file and contains all its parsed nodes.
@@ -6,85 +6,83 @@ import Container from './container';
* @extends Container
*
* @example
* const root = postcss.parse('a{color:black} b{z-index:2}');
* const root = postcss.parse('a{color:black} b{z-index:2}')
* root.type //=> 'root'
* root.nodes.length //=> 2
*/
class Root extends Container {
constructor (defaults) {
super(defaults)
this.type = 'root'
if (!this.nodes) this.nodes = []
}
constructor(defaults) {
super(defaults);
this.type = 'root';
if ( !this.nodes ) this.nodes = [];
removeChild (child, ignore) {
const index = this.index(child)
if (!ignore && index === 0 && this.nodes.length > 1) {
this.nodes[1].raws.before = this.nodes[index].raws.before
}
removeChild(child, ignore) {
const index = this.index(child);
return super.removeChild(child)
}
if ( !ignore && index === 0 && this.nodes.length > 1 ) {
this.nodes[1].raws.before = this.nodes[index].raws.before;
normalize (child, sample, type) {
const nodes = super.normalize(child)
if (sample) {
if (type === 'prepend') {
if (this.nodes.length > 1) {
sample.raws.before = this.nodes[1].raws.before
} else {
delete sample.raws.before
}
return super.removeChild(child);
}
normalize(child, sample, type) {
let nodes = super.normalize(child);
if ( sample ) {
if ( type === 'prepend' ) {
if ( this.nodes.length > 1 ) {
sample.raws.before = this.nodes[1].raws.before;
} else {
delete sample.raws.before;
}
} else if ( this.first !== sample ) {
for ( let node of nodes ) {
node.raws.before = sample.raws.before;
}
}
} else if (this.first !== sample) {
for (const node of nodes) {
node.raws.before = sample.raws.before
}
return nodes;
}
}
/**
* Returns a {@link Result} instance representing the root’s CSS.
*
* @param {processOptions} [opts] - options with only `to` and `map` keys
*
* @return {Result} result with current root’s CSS
*
* @example
* const root1 = postcss.parse(css1, { from: 'a.css' });
* const root2 = postcss.parse(css2, { from: 'b.css' });
* root1.append(root2);
* const result = root1.toResult({ to: 'all.css', map: true });
*/
toResult(opts = { }) {
let LazyResult = require('./lazy-result');
let Processor = require('./processor');
return nodes
}
let lazy = new LazyResult(new Processor(), this, opts);
return lazy.stringify();
}
/**
* Returns a {@link Result} instance representing the root’s CSS.
*
* @param {processOptions} [opts] Options with only `to` and `map` keys.
*
* @return {Result} Result with current root’s CSS.
*
* @example
* const root1 = postcss.parse(css1, { from: 'a.css' })
* const root2 = postcss.parse(css2, { from: 'b.css' })
* root1.append(root2)
* const result = root1.toResult({ to: 'all.css', map: true })
*/
toResult (opts = { }) {
const LazyResult = require('./lazy-result')
const Processor = require('./processor')
/**
* @memberof Root#
* @member {object} raws - Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `after`: the space symbols after the last child to the end of file.
* * `semicolon`: is the last child has an (optional) semicolon.
*
* @example
* postcss.parse('a {}\n').raws //=> { after: '\n' }
* postcss.parse('a {}').raws //=> { after: '' }
*/
const lazy = new LazyResult(new Processor(), this, opts)
return lazy.stringify()
}
/**
* @memberof Root#
* @member {object} raws Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `after`: the space symbols after the last child to the end of file.
* * `semicolon`: is the last child has an (optional) semicolon.
*
* @example
* postcss.parse('a {}\n').raws //=> { after: '\n' }
* postcss.parse('a {}').raws //=> { after: '' }
*/
}
export default Root;
export default Root
+72 -74
View File
@@ -1,5 +1,5 @@
import Container from './container';
import list from './list';
import Container from './container'
import list from './list'
/**
* Represents a CSS rule: a selector followed by a declaration block.
@@ -7,85 +7,83 @@ import list from './list';
* @extends Container
*
* @example
* const root = postcss.parse('a{}');
* const rule = root.first;
* const root = postcss.parse('a{}')
* const rule = root.first
* rule.type //=> 'rule'
* rule.toString() //=> 'a{}'
*/
class Rule extends Container {
constructor (defaults) {
super(defaults)
this.type = 'rule'
if (!this.nodes) this.nodes = []
}
constructor(defaults) {
super(defaults);
this.type = 'rule';
if ( !this.nodes ) this.nodes = [];
}
/**
* An array containing the rule’s individual selectors.
* Groups of selectors are split at commas.
*
* @type {string[]}
*
* @example
* const root = postcss.parse('a, b { }')
* const rule = root.first
*
* rule.selector //=> 'a, b'
* rule.selectors //=> ['a', 'b']
*
* rule.selectors = ['a', 'strong']
* rule.selector //=> 'a, strong'
*/
get selectors () {
return list.comma(this.selector)
}
/**
* An array containing the rule’s individual selectors.
* Groups of selectors are split at commas.
*
* @type {string[]}
*
* @example
* const root = postcss.parse('a, b { }');
* const rule = root.first;
*
* rule.selector //=> 'a, b'
* rule.selectors //=> ['a', 'b']
*
* rule.selectors = ['a', 'strong'];
* rule.selector //=> 'a, strong'
*/
get selectors() {
return list.comma(this.selector);
}
set selectors (values) {
const match = this.selector ? this.selector.match(/,\s*/) : null
const sep = match ? match[0] : ',' + this.raw('between', 'beforeOpen')
this.selector = values.join(sep)
}
set selectors(values) {
let match = this.selector ? this.selector.match(/,\s*/) : null;
let sep = match ? match[0] : ',' + this.raw('between', 'beforeOpen');
this.selector = values.join(sep);
}
/**
* @memberof Rule#
* @member {string} selector - the rule’s full selector represented
* as a string
*
* @example
* const root = postcss.parse('a, b { }');
* const rule = root.first;
* rule.selector //=> 'a, b'
*/
/**
* @memberof Rule#
* @member {object} raws - Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
* * `after`: the space symbols after the last child of the node
* to the end of the node.
* * `between`: the symbols between the property and value
* for declarations, selector and `{` for rules, or last parameter
* and `{` for at-rules.
* * `semicolon`: contains `true` if the last child has
* an (optional) semicolon.
* * `ownSemicolon`: contains `true` if there is semicolon after rule.
*
* PostCSS cleans selectors from comments and extra spaces,
* but it stores origin content in raws properties.
* As such, if you don’t change a declaration’s value,
* PostCSS will use the raw value with comments.
*
* @example
* const root = postcss.parse('a {\n color:black\n}')
* root.first.first.raws //=> { before: '', between: ' ', after: '\n' }
*/
/**
* @memberof Rule#
* @member {string} selector The rule’s full selector represented
* as a string.
*
* @example
* const root = postcss.parse('a, b { }')
* const rule = root.first
* rule.selector //=> 'a, b'
*/
/**
* @memberof Rule#
* @member {object} raws Information to generate byte-to-byte equal
* node string as it was in the origin input.
*
* Every parser saves its own properties,
* but the default CSS parser uses:
*
* * `before`: the space symbols before the node. It also stores `*`
* and `_` symbols before the declaration (IE hack).
* * `after`: the space symbols after the last child of the node
* to the end of the node.
* * `between`: the symbols between the property and value
* for declarations, selector and `{` for rules, or last parameter
* and `{` for at-rules.
* * `semicolon`: contains `true` if the last child has
* an (optional) semicolon.
* * `ownSemicolon`: contains `true` if there is semicolon after rule.
*
* PostCSS cleans selectors from comments and extra spaces,
* but it stores origin content in raws properties.
* As such, if you don’t change a declaration’s value,
* PostCSS will use the raw value with comments.
*
* @example
* const root = postcss.parse('a {\n color:black\n}')
* root.first.first.raws //=> { before: '', between: ' ', after: '\n' }
*/
}
export default Rule;
export default Rule
+296 -298
View File
@@ -1,332 +1,330 @@
const defaultRaw = {
colon: ': ',
indent: ' ',
beforeDecl: '\n',
beforeRule: '\n',
beforeOpen: ' ',
beforeClose: '\n',
beforeComment: '\n',
after: '\n',
emptyBody: '',
commentLeft: ' ',
commentRight: ' '
};
const DEFAULT_RAW = {
colon: ': ',
indent: ' ',
beforeDecl: '\n',
beforeRule: '\n',
beforeOpen: ' ',
beforeClose: '\n',
beforeComment: '\n',
after: '\n',
emptyBody: '',
commentLeft: ' ',
commentRight: ' '
}
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1);
function capitalize (str) {
return str[0].toUpperCase() + str.slice(1)
}
class Stringifier {
constructor (builder) {
this.builder = builder
}
constructor(builder) {
this.builder = builder;
stringify (node, semicolon) {
this[node.type](node, semicolon)
}
root (node) {
this.body(node)
if (node.raws.after) this.builder(node.raws.after)
}
comment (node) {
const left = this.raw(node, 'left', 'commentLeft')
const right = this.raw(node, 'right', 'commentRight')
this.builder('/*' + left + node.text + right + '*/', node)
}
decl (node, semicolon) {
const between = this.raw(node, 'between', 'colon')
let string = node.prop + between + this.rawValue(node, 'value')
if (node.important) {
string += node.raws.important || ' !important'
}
stringify(node, semicolon) {
this[node.type](node, semicolon);
if (semicolon) string += ';'
this.builder(string, node)
}
rule (node) {
this.block(node, this.rawValue(node, 'selector'))
if (node.raws.ownSemicolon) {
this.builder(node.raws.ownSemicolon, node, 'end')
}
}
atrule (node, semicolon) {
let name = '@' + node.name
const params = node.params ? this.rawValue(node, 'params') : ''
if (typeof node.raws.afterName !== 'undefined') {
name += node.raws.afterName
} else if (params) {
name += ' '
}
root(node) {
this.body(node);
if ( node.raws.after ) this.builder(node.raws.after);
if (node.nodes) {
this.block(node, name + params)
} else {
const end = (node.raws.between || '') + (semicolon ? ';' : '')
this.builder(name + params + end, node)
}
}
body (node) {
let last = node.nodes.length - 1
while (last > 0) {
if (node.nodes[last].type !== 'comment') break
last -= 1
}
comment(node) {
let left = this.raw(node, 'left', 'commentLeft');
let right = this.raw(node, 'right', 'commentRight');
this.builder('/*' + left + node.text + right + '*/', node);
const semicolon = this.raw(node, 'semicolon')
for (let i = 0; i < node.nodes.length; i++) {
const child = node.nodes[i]
const before = this.raw(child, 'before')
if (before) this.builder(before)
this.stringify(child, last !== i || semicolon)
}
}
block (node, start) {
const between = this.raw(node, 'between', 'beforeOpen')
this.builder(start + between + '{', node, 'start')
let after
if (node.nodes && node.nodes.length) {
this.body(node)
after = this.raw(node, 'after')
} else {
after = this.raw(node, 'after', 'emptyBody')
}
decl(node, semicolon) {
let between = this.raw(node, 'between', 'colon');
let string = node.prop + between + this.rawValue(node, 'value');
if (after) this.builder(after)
this.builder('}', node, 'end')
}
if ( node.important ) {
string += node.raws.important || ' !important';
raw (node, own, detect) {
let value
if (!detect) detect = own
// Already had
if (own) {
value = node.raws[own]
if (typeof value !== 'undefined') return value
}
const parent = node.parent
// Hack for first rule in CSS
if (detect === 'before') {
if (!parent || (parent.type === 'root' && parent.first === node)) {
return ''
}
}
// Floating child without parent
if (!parent) return DEFAULT_RAW[detect]
// Detect style by other nodes
const root = node.root()
if (!root.rawCache) root.rawCache = { }
if (typeof root.rawCache[detect] !== 'undefined') {
return root.rawCache[detect]
}
if (detect === 'before' || detect === 'after') {
return this.beforeAfter(node, detect)
} else {
const method = 'raw' + capitalize(detect)
if (this[method]) {
value = this[method](root, node)
} else {
root.walk(i => {
value = i.raws[own]
if (typeof value !== 'undefined') return false
})
}
}
if (typeof value === 'undefined') value = DEFAULT_RAW[detect]
root.rawCache[detect] = value
return value
}
rawSemicolon (root) {
let value
root.walk(i => {
if (i.nodes && i.nodes.length && i.last.type === 'decl') {
value = i.raws.semicolon
if (typeof value !== 'undefined') return false
}
})
return value
}
rawEmptyBody (root) {
let value
root.walk(i => {
if (i.nodes && i.nodes.length === 0) {
value = i.raws.after
if (typeof value !== 'undefined') return false
}
})
return value
}
rawIndent (root) {
if (root.raws.indent) return root.raws.indent
let value
root.walk(i => {
const p = i.parent
if (p && p !== root && p.parent && p.parent === root) {
if (typeof i.raws.before !== 'undefined') {
const parts = i.raws.before.split('\n')
value = parts[parts.length - 1]
value = value.replace(/[^\s]/g, '')
return false
}
}
})
return value
}
if ( semicolon ) string += ';';
this.builder(string, node);
}
rule(node) {
this.block(node, this.rawValue(node, 'selector'));
if ( node.raws.ownSemicolon ) {
this.builder(node.raws.ownSemicolon, node, 'end');
rawBeforeComment (root, node) {
let value
root.walkComments(i => {
if (typeof i.raws.before !== 'undefined') {
value = i.raws.before
if (value.indexOf('\n') !== -1) {
value = value.replace(/[^\n]+$/, '')
}
return false
}
})
if (typeof value === 'undefined') {
value = this.raw(node, null, 'beforeDecl')
} else if (value) {
value = value.replace(/[^\s]/g, '')
}
return value
}
atrule(node, semicolon) {
let name = '@' + node.name;
let params = node.params ? this.rawValue(node, 'params') : '';
if ( typeof node.raws.afterName !== 'undefined' ) {
name += node.raws.afterName;
} else if ( params ) {
name += ' ';
rawBeforeDecl (root, node) {
let value
root.walkDecls(i => {
if (typeof i.raws.before !== 'undefined') {
value = i.raws.before
if (value.indexOf('\n') !== -1) {
value = value.replace(/[^\n]+$/, '')
}
return false
}
})
if (typeof value === 'undefined') {
value = this.raw(node, null, 'beforeRule')
} else if (value) {
value = value.replace(/[^\s]/g, '')
}
return value
}
if ( node.nodes ) {
this.block(node, name + params);
} else {
let end = (node.raws.between || '') + (semicolon ? ';' : '');
this.builder(name + params + end, node);
rawBeforeRule (root) {
let value
root.walk(i => {
if (i.nodes && (i.parent !== root || root.first !== i)) {
if (typeof i.raws.before !== 'undefined') {
value = i.raws.before
if (value.indexOf('\n') !== -1) {
value = value.replace(/[^\n]+$/, '')
}
return false
}
}
}
})
if (value) value = value.replace(/[^\s]/g, '')
return value
}
body(node) {
let last = node.nodes.length - 1;
while ( last > 0 ) {
if ( node.nodes[last].type !== 'comment' ) break;
last -= 1;
rawBeforeClose (root) {
let value
root.walk(i => {
if (i.nodes && i.nodes.length > 0) {
if (typeof i.raws.after !== 'undefined') {
value = i.raws.after
if (value.indexOf('\n') !== -1) {
value = value.replace(/[^\n]+$/, '')
}
return false
}
}
})
if (value) value = value.replace(/[^\s]/g, '')
return value
}
let semicolon = this.raw(node, 'semicolon');
for ( let i = 0; i < node.nodes.length; i++ ) {
let child = node.nodes[i];
let before = this.raw(child, 'before');
if ( before ) this.builder(before);
this.stringify(child, last !== i || semicolon);
}
rawBeforeOpen (root) {
let value
root.walk(i => {
if (i.type !== 'decl') {
value = i.raws.between
if (typeof value !== 'undefined') return false
}
})
return value
}
rawColon (root) {
let value
root.walkDecls(i => {
if (typeof i.raws.between !== 'undefined') {
value = i.raws.between.replace(/[^\s:]/g, '')
return false
}
})
return value
}
beforeAfter (node, detect) {
let value
if (node.type === 'decl') {
value = this.raw(node, null, 'beforeDecl')
} else if (node.type === 'comment') {
value = this.raw(node, null, 'beforeComment')
} else if (detect === 'before') {
value = this.raw(node, null, 'beforeRule')
} else {
value = this.raw(node, null, 'beforeClose')
}
block(node, start) {
let between = this.raw(node, 'between', 'beforeOpen');
this.builder(start + between + '{', node, 'start');
let after;
if ( node.nodes && node.nodes.length ) {
this.body(node);
after = this.raw(node, 'after');
} else {
after = this.raw(node, 'after', 'emptyBody');
}
if ( after ) this.builder(after);
this.builder('}', node, 'end');
let buf = node.parent
let depth = 0
while (buf && buf.type !== 'root') {
depth += 1
buf = buf.parent
}
raw(node, own, detect) {
let value;
if ( !detect ) detect = own;
// Already had
if ( own ) {
value = node.raws[own];
if ( typeof value !== 'undefined' ) return value;
}
let parent = node.parent;
// Hack for first rule in CSS
if ( detect === 'before' ) {
if ( !parent || parent.type === 'root' && parent.first === node ) {
return '';
}
}
// Floating child without parent
if ( !parent ) return defaultRaw[detect];
// Detect style by other nodes
let root = node.root();
if ( !root.rawCache ) root.rawCache = { };
if ( typeof root.rawCache[detect] !== 'undefined' ) {
return root.rawCache[detect];
}
if ( detect === 'before' || detect === 'after' ) {
return this.beforeAfter(node, detect);
} else {
let method = 'raw' + capitalize(detect);
if ( this[method] ) {
value = this[method](root, node);
} else {
root.walk( i => {
value = i.raws[own];
if ( typeof value !== 'undefined' ) return false;
});
}
}
if ( typeof value === 'undefined' ) value = defaultRaw[detect];
root.rawCache[detect] = value;
return value;
if (value.indexOf('\n') !== -1) {
const indent = this.raw(node, null, 'indent')
if (indent.length) {
for (let step = 0; step < depth; step++) value += indent
}
}
rawSemicolon(root) {
let value;
root.walk( i => {
if ( i.nodes && i.nodes.length && i.last.type === 'decl' ) {
value = i.raws.semicolon;
if ( typeof value !== 'undefined' ) return false;
}
});
return value;
return value
}
rawValue (node, prop) {
const value = node[prop]
const raw = node.raws[prop]
if (raw && raw.value === value) {
return raw.raw
} else {
return value
}
rawEmptyBody(root) {
let value;
root.walk( i => {
if ( i.nodes && i.nodes.length === 0 ) {
value = i.raws.after;
if ( typeof value !== 'undefined' ) return false;
}
});
return value;
}
rawIndent(root) {
if ( root.raws.indent ) return root.raws.indent;
let value;
root.walk( i => {
let p = i.parent;
if ( p && p !== root && p.parent && p.parent === root ) {
if ( typeof i.raws.before !== 'undefined' ) {
let parts = i.raws.before.split('\n');
value = parts[parts.length - 1];
value = value.replace(/[^\s]/g, '');
return false;
}
}
});
return value;
}
rawBeforeComment(root, node) {
let value;
root.walkComments( i => {
if ( typeof i.raws.before !== 'undefined' ) {
value = i.raws.before;
if ( value.indexOf('\n') !== -1 ) {
value = value.replace(/[^\n]+$/, '');
}
return false;
}
});
if ( typeof value === 'undefined' ) {
value = this.raw(node, null, 'beforeDecl');
} else if ( value ) {
value = value.replace(/[^\s]/g, '');
}
return value;
}
rawBeforeDecl(root, node) {
let value;
root.walkDecls( i => {
if ( typeof i.raws.before !== 'undefined' ) {
value = i.raws.before;
if ( value.indexOf('\n') !== -1 ) {
value = value.replace(/[^\n]+$/, '');
}
return false;
}
});
if ( typeof value === 'undefined' ) {
value = this.raw(node, null, 'beforeRule');
} else if ( value ) {
value = value.replace(/[^\s]/g, '');
}
return value;
}
rawBeforeRule(root) {
let value;
root.walk( i => {
if ( i.nodes && (i.parent !== root || root.first !== i) ) {
if ( typeof i.raws.before !== 'undefined' ) {
value = i.raws.before;
if ( value.indexOf('\n') !== -1 ) {
value = value.replace(/[^\n]+$/, '');
}
return false;
}
}
});
if ( value ) value = value.replace(/[^\s]/g, '');
return value;
}
rawBeforeClose(root) {
let value;
root.walk( i => {
if ( i.nodes && i.nodes.length > 0 ) {
if ( typeof i.raws.after !== 'undefined' ) {
value = i.raws.after;
if ( value.indexOf('\n') !== -1 ) {
value = value.replace(/[^\n]+$/, '');
}
return false;
}
}
});
if ( value ) value = value.replace(/[^\s]/g, '');
return value;
}
rawBeforeOpen(root) {
let value;
root.walk( i => {
if ( i.type !== 'decl' ) {
value = i.raws.between;
if ( typeof value !== 'undefined' ) return false;
}
});
return value;
}
rawColon(root) {
let value;
root.walkDecls( i => {
if ( typeof i.raws.between !== 'undefined' ) {
value = i.raws.between.replace(/[^\s:]/g, '');
return false;
}
});
return value;
}
beforeAfter(node, detect) {
let value;
if ( node.type === 'decl' ) {
value = this.raw(node, null, 'beforeDecl');
} else if ( node.type === 'comment' ) {
value = this.raw(node, null, 'beforeComment');
} else if ( detect === 'before' ) {
value = this.raw(node, null, 'beforeRule');
} else {
value = this.raw(node, null, 'beforeClose');
}
let buf = node.parent;
let depth = 0;
while ( buf && buf.type !== 'root' ) {
depth += 1;
buf = buf.parent;
}
if ( value.indexOf('\n') !== -1 ) {
let indent = this.raw(node, null, 'indent');
if ( indent.length ) {
for ( let step = 0; step < depth; step++ ) value += indent;
}
}
return value;
}
rawValue(node, prop) {
let value = node[prop];
let raw = node.raws[prop];
if ( raw && raw.value === value ) {
return raw.raw;
} else {
return value;
}
}
}
}
export default Stringifier;
export default Stringifier
+6 -4
View File
@@ -1,6 +1,8 @@
import Stringifier from './stringifier';
import Stringifier from './stringifier'
export default function stringify(node, builder) {
let str = new Stringifier(builder);
str.stringify(node);
function stringify (node, builder) {
const str = new Stringifier(builder)
str.stringify(node)
}
export default stringify
+52 -52
View File
@@ -1,60 +1,60 @@
import chalk from 'chalk';
import chalk from 'chalk'
import tokenizer from './tokenize';
import Input from './input';
import tokenizer from './tokenize'
import Input from './input'
const HIGHLIGHT_THEME = {
'brackets': chalk.cyan,
'at-word': chalk.cyan,
'call': chalk.cyan,
'comment': chalk.gray,
'string': chalk.green,
'class': chalk.yellow,
'hash': chalk.magenta,
'(': chalk.cyan,
')': chalk.cyan,
'{': chalk.yellow,
'}': chalk.yellow,
'[': chalk.yellow,
']': chalk.yellow,
':': chalk.yellow,
';': chalk.yellow
};
function getTokenType([type, value], processor) {
if ( type === 'word' ) {
if ( value[0] === '.' ) {
return 'class';
}
if ( value[0] === '#' ) {
return 'hash';
}
}
if ( !processor.endOfFile() ) {
let next = processor.nextToken();
processor.back(next);
if ( next[0] === 'brackets' || next[0] === '(' ) return 'call';
}
return type;
'brackets': chalk.cyan,
'at-word': chalk.cyan,
'comment': chalk.gray,
'string': chalk.green,
'class': chalk.yellow,
'call': chalk.cyan,
'hash': chalk.magenta,
'(': chalk.cyan,
')': chalk.cyan,
'{': chalk.yellow,
'}': chalk.yellow,
'[': chalk.yellow,
']': chalk.yellow,
':': chalk.yellow,
';': chalk.yellow
}
function terminalHighlight(css) {
let processor = tokenizer(new Input(css), { ignoreErrors: true });
let result = '';
while ( !processor.endOfFile() ) {
let token = processor.nextToken();
let color = HIGHLIGHT_THEME[getTokenType(token, processor)];
if ( color ) {
result += token[1].split(/\r?\n/)
.map( i => color(i) )
.join('\n');
} else {
result += token[1];
}
function getTokenType ([type, value], processor) {
if (type === 'word') {
if (value[0] === '.') {
return 'class'
}
return result;
if (value[0] === '#') {
return 'hash'
}
}
if (!processor.endOfFile()) {
const next = processor.nextToken()
processor.back(next)
if (next[0] === 'brackets' || next[0] === '(') return 'call'
}
return type
}
export default terminalHighlight;
function terminalHighlight (css) {
const processor = tokenizer(new Input(css), { ignoreErrors: true })
let result = ''
while (!processor.endOfFile()) {
const token = processor.nextToken()
const color = HIGHLIGHT_THEME[getTokenType(token, processor)]
if (color) {
result += token[1].split(/\r?\n/)
.map(i => color(i))
.join('\n')
} else {
result += token[1]
}
}
return result
}
export default terminalHighlight
+306 -299
View File
@@ -1,322 +1,329 @@
const SINGLE_QUOTE = '\''.charCodeAt(0);
const DOUBLE_QUOTE = '"'.charCodeAt(0);
const BACKSLASH = '\\'.charCodeAt(0);
const SLASH = '/'.charCodeAt(0);
const NEWLINE = '\n'.charCodeAt(0);
const SPACE = ' '.charCodeAt(0);
const FEED = '\f'.charCodeAt(0);
const TAB = '\t'.charCodeAt(0);
const CR = '\r'.charCodeAt(0);
const OPEN_SQUARE = '['.charCodeAt(0);
const CLOSE_SQUARE = ']'.charCodeAt(0);
const OPEN_PARENTHESES = '('.charCodeAt(0);
const CLOSE_PARENTHESES = ')'.charCodeAt(0);
const OPEN_CURLY = '{'.charCodeAt(0);
const CLOSE_CURLY = '}'.charCodeAt(0);
const SEMICOLON = ';'.charCodeAt(0);
const ASTERISK = '*'.charCodeAt(0);
const COLON = ':'.charCodeAt(0);
const AT = '@'.charCodeAt(0);
const SINGLE_QUOTE = '\''.charCodeAt(0)
const DOUBLE_QUOTE = '"'.charCodeAt(0)
const BACKSLASH = '\\'.charCodeAt(0)
const SLASH = '/'.charCodeAt(0)
const NEWLINE = '\n'.charCodeAt(0)
const SPACE = ' '.charCodeAt(0)
const FEED = '\f'.charCodeAt(0)
const TAB = '\t'.charCodeAt(0)
const CR = '\r'.charCodeAt(0)
const OPEN_SQUARE = '['.charCodeAt(0)
const CLOSE_SQUARE = ']'.charCodeAt(0)
const OPEN_PARENTHESES = '('.charCodeAt(0)
const CLOSE_PARENTHESES = ')'.charCodeAt(0)
const OPEN_CURLY = '{'.charCodeAt(0)
const CLOSE_CURLY = '}'.charCodeAt(0)
const SEMICOLON = ';'.charCodeAt(0)
const ASTERISK = '*'.charCodeAt(0)
const COLON = ':'.charCodeAt(0)
const AT = '@'.charCodeAt(0)
const RE_AT_END = /[ \n\t\r\f\{\}\(\)'"\\;/\[\]#]/g;
const RE_WORD_END = /[ \n\t\r\f\(\)\{\}:;@!'"\\\]\[#]|\/(?=\*)/g;
const RE_BAD_BRACKET = /.[\\\/\("'\n]/;
const RE_HEX_ESCAPE = /[a-f0-9]/i;
const RE_AT_END = /[ \n\t\r\f{}()'"\\;/[\]#]/g
const RE_WORD_END = /[ \n\t\r\f(){}:;@!'"\\\][#]|\/(?=\*)/g
const RE_BAD_BRACKET = /.[\\/("'\n]/
const RE_HEX_ESCAPE = /[a-f0-9]/i
export default function tokenizer(input, options = {}) {
let css = input.css.valueOf();
let ignore = options.ignoreErrors;
export default function tokenizer (input, options = {}) {
const css = input.css.valueOf()
const ignore = options.ignoreErrors
let code, next, quote, lines, last, content, escape,
nextLine, nextOffset, escaped, escapePos, prev, n, currentToken;
let code, next, quote, lines, last, content, escape
let nextLine, nextOffset, escaped, escapePos, prev, n, currentToken
let length = css.length;
let offset = -1;
let line = 1;
let pos = 0;
let buffer = [];
let returned = [];
const length = css.length
let offset = -1
let line = 1
let pos = 0
const buffer = []
const returned = []
function unclosed(what) {
throw input.error('Unclosed ' + what, line, pos - offset);
function unclosed (what) {
throw input.error('Unclosed ' + what, line, pos - offset)
}
function endOfFile () {
return returned.length === 0 && pos >= length
}
function nextToken () {
if (returned.length) return returned.pop()
if (pos >= length) return
code = css.charCodeAt(pos)
if (
code === NEWLINE || code === FEED ||
(code === CR && css.charCodeAt(pos + 1) !== NEWLINE)
) {
offset = pos
line += 1
}
function endOfFile() {
return returned.length === 0 && pos >= length;
}
switch (code) {
case NEWLINE:
case SPACE:
case TAB:
case CR:
case FEED:
next = pos
do {
next += 1
code = css.charCodeAt(next)
if (code === NEWLINE) {
offset = next
line += 1
}
} while (
code === SPACE ||
code === NEWLINE ||
code === TAB ||
code === CR ||
code === FEED
)
function nextToken() {
if ( returned.length ) return returned.pop();
if ( pos >= length ) return;
currentToken = ['space', css.slice(pos, next)]
pos = next - 1
break
code = css.charCodeAt(pos);
if ( code === NEWLINE || code === FEED ||
code === CR && css.charCodeAt(pos + 1) !== NEWLINE ) {
offset = pos;
line += 1;
case OPEN_SQUARE:
currentToken = ['[', '[', line, pos - offset]
break
case CLOSE_SQUARE:
currentToken = [']', ']', line, pos - offset]
break
case OPEN_CURLY:
currentToken = ['{', '{', line, pos - offset]
break
case CLOSE_CURLY:
currentToken = ['}', '}', line, pos - offset]
break
case COLON:
currentToken = [':', ':', line, pos - offset]
break
case SEMICOLON:
currentToken = [';', ';', line, pos - offset]
break
case OPEN_PARENTHESES:
prev = buffer.length ? buffer.pop()[1] : ''
n = css.charCodeAt(pos + 1)
if (
prev === 'url' &&
n !== SINGLE_QUOTE && n !== DOUBLE_QUOTE &&
n !== SPACE && n !== NEWLINE && n !== TAB &&
n !== FEED && n !== CR
) {
next = pos
do {
escaped = false
next = css.indexOf(')', next + 1)
if (next === -1) {
if (ignore) {
next = pos
break
} else {
unclosed('bracket')
}
}
escapePos = next
while (css.charCodeAt(escapePos - 1) === BACKSLASH) {
escapePos -= 1
escaped = !escaped
}
} while (escaped)
currentToken = ['brackets', css.slice(pos, next + 1),
line, pos - offset,
line, next - offset
]
pos = next
} else {
next = css.indexOf(')', pos + 1)
content = css.slice(pos, next + 1)
if (next === -1 || RE_BAD_BRACKET.test(content)) {
currentToken = ['(', '(', line, pos - offset]
} else {
currentToken = ['brackets', content,
line, pos - offset,
line, next - offset
]
pos = next
}
}
switch ( code ) {
case NEWLINE:
case SPACE:
case TAB:
case CR:
case FEED:
next = pos;
do {
next += 1;
code = css.charCodeAt(next);
if ( code === NEWLINE ) {
offset = next;
line += 1;
}
} while ( code === SPACE ||
code === NEWLINE ||
code === TAB ||
code === CR ||
code === FEED );
break
currentToken = ['space', css.slice(pos, next)];
pos = next - 1;
break;
case OPEN_SQUARE:
currentToken = ['[', '[', line, pos - offset];
break;
case CLOSE_SQUARE:
currentToken = [']', ']', line, pos - offset];
break;
case OPEN_CURLY:
currentToken = ['{', '{', line, pos - offset];
break;
case CLOSE_CURLY:
currentToken = ['}', '}', line, pos - offset];
break;
case COLON:
currentToken = [':', ':', line, pos - offset];
break;
case SEMICOLON:
currentToken = [';', ';', line, pos - offset];
break;
case OPEN_PARENTHESES:
prev = buffer.length ? buffer.pop()[1] : '';
n = css.charCodeAt(pos + 1);
if ( prev === 'url' &&
n !== SINGLE_QUOTE && n !== DOUBLE_QUOTE &&
n !== SPACE && n !== NEWLINE && n !== TAB &&
n !== FEED && n !== CR ) {
next = pos;
do {
escaped = false;
next = css.indexOf(')', next + 1);
if ( next === -1 ) {
if ( ignore ) {
next = pos;
break;
} else {
unclosed('bracket');
}
}
escapePos = next;
while ( css.charCodeAt(escapePos - 1) === BACKSLASH ) {
escapePos -= 1;
escaped = !escaped;
}
} while ( escaped );
currentToken = ['brackets', css.slice(pos, next + 1),
line, pos - offset,
line, next - offset
];
pos = next;
case CLOSE_PARENTHESES:
currentToken = [')', ')', line, pos - offset]
break
case SINGLE_QUOTE:
case DOUBLE_QUOTE:
quote = code === SINGLE_QUOTE ? '\'' : '"'
next = pos
do {
escaped = false
next = css.indexOf(quote, next + 1)
if (next === -1) {
if (ignore) {
next = pos + 1
break
} else {
next = css.indexOf(')', pos + 1);
content = css.slice(pos, next + 1);
if ( next === -1 || RE_BAD_BRACKET.test(content) ) {
currentToken = ['(', '(', line, pos - offset];
} else {
currentToken = ['brackets', content,
line, pos - offset,
line, next - offset
];
pos = next;
}
unclosed('string')
}
}
escapePos = next
while (css.charCodeAt(escapePos - 1) === BACKSLASH) {
escapePos -= 1
escaped = !escaped
}
} while (escaped)
break;
content = css.slice(pos, next + 1)
lines = content.split('\n')
last = lines.length - 1
case CLOSE_PARENTHESES:
currentToken = [')', ')', line, pos - offset];
break;
case SINGLE_QUOTE:
case DOUBLE_QUOTE:
quote = code === SINGLE_QUOTE ? '\'' : '"';
next = pos;
do {
escaped = false;
next = css.indexOf(quote, next + 1);
if ( next === -1 ) {
if ( ignore ) {
next = pos + 1;
break;
} else {
unclosed('string');
}
}
escapePos = next;
while ( css.charCodeAt(escapePos - 1) === BACKSLASH ) {
escapePos -= 1;
escaped = !escaped;
}
} while ( escaped );
content = css.slice(pos, next + 1);
lines = content.split('\n');
last = lines.length - 1;
if ( last > 0 ) {
nextLine = line + last;
nextOffset = next - lines[last].length;
} else {
nextLine = line;
nextOffset = offset;
}
currentToken = ['string', css.slice(pos, next + 1),
line, pos - offset,
nextLine, next - nextOffset
];
offset = nextOffset;
line = nextLine;
pos = next;
break;
case AT:
RE_AT_END.lastIndex = pos + 1;
RE_AT_END.test(css);
if ( RE_AT_END.lastIndex === 0 ) {
next = css.length - 1;
} else {
next = RE_AT_END.lastIndex - 2;
}
currentToken = ['at-word', css.slice(pos, next + 1),
line, pos - offset,
line, next - offset
];
pos = next;
break;
case BACKSLASH:
next = pos;
escape = true;
while ( css.charCodeAt(next + 1) === BACKSLASH ) {
next += 1;
escape = !escape;
}
code = css.charCodeAt(next + 1);
if ( escape && (code !== SLASH &&
code !== SPACE &&
code !== NEWLINE &&
code !== TAB &&
code !== CR &&
code !== FEED ) ) {
next += 1;
if ( RE_HEX_ESCAPE.test(css.charAt(next)) ) {
while ( RE_HEX_ESCAPE.test(css.charAt(next + 1)) ) {
next += 1;
}
if ( css.charCodeAt(next + 1) === SPACE ) {
next += 1;
}
}
}
currentToken = ['word', css.slice(pos, next + 1),
line, pos - offset,
line, next - offset
];
pos = next;
break;
default:
if ( code === SLASH && css.charCodeAt(pos + 1) === ASTERISK ) {
next = css.indexOf('*/', pos + 2) + 1;
if ( next === 0 ) {
if ( ignore ) {
next = css.length;
} else {
unclosed('comment');
}
}
content = css.slice(pos, next + 1);
lines = content.split('\n');
last = lines.length - 1;
if ( last > 0 ) {
nextLine = line + last;
nextOffset = next - lines[last].length;
} else {
nextLine = line;
nextOffset = offset;
}
currentToken = ['comment', content,
line, pos - offset,
nextLine, next - nextOffset
];
offset = nextOffset;
line = nextLine;
pos = next;
} else {
RE_WORD_END.lastIndex = pos + 1;
RE_WORD_END.test(css);
if ( RE_WORD_END.lastIndex === 0 ) {
next = css.length - 1;
} else {
next = RE_WORD_END.lastIndex - 2;
}
currentToken = ['word', css.slice(pos, next + 1),
line, pos - offset,
line, next - offset
];
buffer.push(currentToken);
pos = next;
}
break;
if (last > 0) {
nextLine = line + last
nextOffset = next - lines[last].length
} else {
nextLine = line
nextOffset = offset
}
pos++;
return currentToken;
currentToken = ['string', css.slice(pos, next + 1),
line, pos - offset,
nextLine, next - nextOffset
]
offset = nextOffset
line = nextLine
pos = next
break
case AT:
RE_AT_END.lastIndex = pos + 1
RE_AT_END.test(css)
if (RE_AT_END.lastIndex === 0) {
next = css.length - 1
} else {
next = RE_AT_END.lastIndex - 2
}
currentToken = ['at-word', css.slice(pos, next + 1),
line, pos - offset,
line, next - offset
]
pos = next
break
case BACKSLASH:
next = pos
escape = true
while (css.charCodeAt(next + 1) === BACKSLASH) {
next += 1
escape = !escape
}
code = css.charCodeAt(next + 1)
if (
escape &&
code !== SLASH &&
code !== SPACE &&
code !== NEWLINE &&
code !== TAB &&
code !== CR &&
code !== FEED
) {
next += 1
if (RE_HEX_ESCAPE.test(css.charAt(next))) {
while (RE_HEX_ESCAPE.test(css.charAt(next + 1))) {
next += 1
}
if (css.charCodeAt(next + 1) === SPACE) {
next += 1
}
}
}
currentToken = ['word', css.slice(pos, next + 1),
line, pos - offset,
line, next - offset
]
pos = next
break
default:
if (code === SLASH && css.charCodeAt(pos + 1) === ASTERISK) {
next = css.indexOf('*/', pos + 2) + 1
if (next === 0) {
if (ignore) {
next = css.length
} else {
unclosed('comment')
}
}
content = css.slice(pos, next + 1)
lines = content.split('\n')
last = lines.length - 1
if (last > 0) {
nextLine = line + last
nextOffset = next - lines[last].length
} else {
nextLine = line
nextOffset = offset
}
currentToken = ['comment', content,
line, pos - offset,
nextLine, next - nextOffset
]
offset = nextOffset
line = nextLine
pos = next
} else {
RE_WORD_END.lastIndex = pos + 1
RE_WORD_END.test(css)
if (RE_WORD_END.lastIndex === 0) {
next = css.length - 1
} else {
next = RE_WORD_END.lastIndex - 2
}
currentToken = ['word', css.slice(pos, next + 1),
line, pos - offset,
line, next - offset
]
buffer.push(currentToken)
pos = next
}
break
}
function back(token) {
returned.push(token);
}
pos++
return currentToken
}
return {
back,
nextToken,
endOfFile
};
function back (token) {
returned.push(token)
}
return {
back,
nextToken,
endOfFile
}
}
+29 -29
View File
@@ -2,46 +2,46 @@
* Contains helpers for working with vendor prefixes.
*
* @example
* const vendor = postcss.vendor;
* const vendor = postcss.vendor
*
* @namespace vendor
*/
let vendor = {
const vendor = {
/**
* Returns the vendor prefix extracted from an input string.
*
* @param {string} prop - string with or without vendor prefix
*
* @return {string} vendor prefix or empty string
*
* @example
* postcss.vendor.prefix('-moz-tab-size') //=> '-moz-'
* postcss.vendor.prefix('tab-size') //=> ''
*/
prefix(prop) {
let match = prop.match(/^(-\w+-)/);
if ( match ) {
return match[0];
} else {
return '';
}
},
/**
* Returns the vendor prefix extracted from an input string.
*
* @param {string} prop String with or without vendor prefix.
*
* @return {string} vendor prefix or empty string
*
* @example
* postcss.vendor.prefix('-moz-tab-size') //=> '-moz-'
* postcss.vendor.prefix('tab-size') //=> ''
*/
prefix (prop) {
const match = prop.match(/^(-\w+-)/)
if (match) {
return match[0]
} else {
return ''
}
},
/**
/**
* Returns the input string stripped of its vendor prefix.
*
* @param {string} prop - string with or without vendor prefix
* @param {string} prop String with or without vendor prefix.
*
* @return {string} string name without vendor prefixes
* @return {string} String name without vendor prefixes.
*
* @example
* postcss.vendor.unprefixed('-moz-tab-size') //=> 'tab-size'
*/
unprefixed(prop) {
return prop.replace(/^-\w+-/, '');
}
unprefixed (prop) {
return prop.replace(/^-\w+-/, '')
}
};
}
export default vendor;
export default vendor
+7 -5
View File
@@ -1,8 +1,10 @@
let printed = { };
const printed = { }
export default function warnOnce(message) {
if ( printed[message] ) return;
printed[message] = true;
export default function warnOnce (message) {
if (printed[message]) return
printed[message] = true
if ( typeof console !== 'undefined' && console.warn ) console.warn(message);
if (typeof console !== 'undefined' && console.warn) {
console.warn(message)
}
}
+90 -88
View File
@@ -2,104 +2,106 @@
* Represents a plugin’s warning. It can be created using {@link Node#warn}.
*
* @example
* if ( decl.important ) {
* decl.warn(result, 'Avoid !important', { word: '!important' });
* if (decl.important) {
* decl.warn(result, 'Avoid !important', { word: '!important' })
* }
*/
class Warning {
/**
* @param {string} text Warning message.
* @param {Object} [opts] Warning options.
* @param {Node} opts.node CSS node that caused the warning.
* @param {string} opts.word Word in CSS source that caused the warning.
* @param {number} opts.index Index in CSS node string that caused
* the warning.
* @param {string} opts.plugin Name of the plugin that created
* this warning. {@link Result#warn} fills
* this property automatically.
*/
constructor (text, opts = { }) {
/**
* @param {string} text - warning message
* @param {Object} [opts] - warning options
* @param {Node} opts.node - CSS node that caused the warning
* @param {string} opts.word - word in CSS source that caused the warning
* @param {number} opts.index - index in CSS node string that caused
* the warning
* @param {string} opts.plugin - name of the plugin that created
* this warning. {@link Result#warn} fills
* this property automatically.
* Type to filter warnings from {@link Result#messages}.
* Always equal to `"warning"`.
*
* @type {string}
*
* @example
* const nonWarning = result.messages.filter(i => i.type !== 'warning')
*/
constructor(text, opts = { }) {
/**
* @member {string} - Type to filter warnings from
* {@link Result#messages}. Always equal
* to `"warning"`.
*
* @example
* const nonWarning = result.messages.filter(i => i.type !== 'warning')
*/
this.type = 'warning';
/**
* @member {string} - The warning message.
*
* @example
* warning.text //=> 'Try to avoid !important'
*/
this.text = text;
this.type = 'warning'
/**
* The warning message.
*
* @type {string}
*
* @example
* warning.text //=> 'Try to avoid !important'
*/
this.text = text
if ( opts.node && opts.node.source ) {
let pos = opts.node.positionBy(opts);
/**
* @member {number} - Line in the input file
* with this warning’s source
*
* @example
* warning.line //=> 5
*/
this.line = pos.line;
/**
* @member {number} - Column in the input file
* with this warning’s source.
*
* @example
* warning.column //=> 6
*/
this.column = pos.column;
}
for ( let opt in opts ) this[opt] = opts[opt];
if (opts.node && opts.node.source) {
const pos = opts.node.positionBy(opts)
/**
* Line in the input file with this warning’s source.
* @type {number}
*
* @example
* warning.line //=> 5
*/
this.line = pos.line
/**
* Column in the input file with this warning’s source.
*
* @type {number}
*
* @example
* warning.column //=> 6
*/
this.column = pos.column
}
/**
* Returns a warning position and message.
*
* @example
* warning.toString() //=> 'postcss-lint:a.css:10:14: Avoid !important'
*
* @return {string} warning position and message
*/
toString() {
if ( this.node ) {
return this.node.error(this.text, {
plugin: this.plugin,
index: this.index,
word: this.word
}).message;
} else if ( this.plugin ) {
return this.plugin + ': ' + this.text;
} else {
return this.text;
}
for (const opt in opts) this[opt] = opts[opt]
}
/**
* Returns a warning position and message.
*
* @example
* warning.toString() //=> 'postcss-lint:a.css:10:14: Avoid !important'
*
* @return {string} Warning position and message.
*/
toString () {
if (this.node) {
return this.node.error(this.text, {
plugin: this.plugin,
index: this.index,
word: this.word
}).message
} else if (this.plugin) {
return this.plugin + ': ' + this.text
} else {
return this.text
}
}
/**
* @memberof Warning#
* @member {string} plugin - The name of the plugin that created
* it will fill this property automatically.
* this warning. When you call {@link Node#warn}
*
* @example
* warning.plugin //=> 'postcss-important'
*/
/**
* @memberof Warning#
* @member {Node} node - Contains the CSS node that caused the warning.
*
* @example
* warning.node.toString() //=> 'color: white !important'
*/
/**
* @memberof Warning#
* @member {string} plugin The name of the plugin that created
* it will fill this property automatically.
* this warning. When you call {@link Node#warn}
*
* @example
* warning.plugin //=> 'postcss-important'
*/
/**
* @memberof Warning#
* @member {Node} node Contains the CSS node that caused the warning.
*
* @example
* warning.node.toString() //=> 'color: white !important'
*/
}
export default Warning;
export default Warning
+44 -8
View File
@@ -36,7 +36,14 @@
"docdash": "^0.4.0",
"eslint": "^4.19.1",
"eslint-ci": "^0.1.1",
"eslint-config-postcss": "^2.0.2",
"eslint-config-logux": "^21.0.0",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-jest": "^21.15.1",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.7.0",
"eslint-plugin-security": "^1.4.0",
"eslint-plugin-standard": "^3.1.0",
"fs-extra": "^5.0.0",
"gulp": "^3.9.1",
"gulp-babel": "^7.0.1",
@@ -90,16 +97,45 @@
},
"eslintConfig": {
"parser": "babel-eslint",
"extends": "eslint-config-postcss",
"parserOptions": {
"sourceType": "module"
},
"extends": "eslint-config-logux/node4",
"rules": {
"node/no-unsupported-features": [
"error",
{
"ignores": [
"modules"
]
}
],
"consistent-return": "off",
"valid-jsdoc": "error",
"complexity": "off",
"no-new": "off"
"global-require": "off",
"valid-jsdoc": "error"
},
"env": {
"browser": true,
"jest": true
}
"browser": true
},
"overrides": [
{
"files": [
"gulpfile.js"
],
"rules": {
"node/no-unpublished-require": "off",
"global-require": "off"
}
},
{
"files": [
"*.test.js"
],
"rules": {
"no-unused-expressions": "off",
"func-style": "off"
}
}
]
}
}
+28 -30
View File
@@ -1,47 +1,45 @@
'use strict';
const AtRule = require('../lib/at-rule');
const parse = require('../lib/parse');
const AtRule = require('../lib/at-rule')
const parse = require('../lib/parse')
it('initializes with properties', () => {
let rule = new AtRule({ name: 'encoding', params: '"utf-8"' });
const rule = new AtRule({ name: 'encoding', params: '"utf-8"' })
expect(rule.name).toEqual('encoding');
expect(rule.params).toEqual('"utf-8"');
expect(rule.name).toEqual('encoding')
expect(rule.params).toEqual('"utf-8"')
expect(rule.toString()).toEqual('@encoding "utf-8"');
});
expect(rule.toString()).toEqual('@encoding "utf-8"')
})
it('does not fall on childless at-rule', () => {
let rule = new AtRule();
expect(rule.each( i => i )).not.toBeDefined();
});
const rule = new AtRule()
expect(rule.each(i => i)).not.toBeDefined()
})
it('creates nodes property on prepend()', () => {
let rule = new AtRule();
expect(rule.nodes).not.toBeDefined();
const rule = new AtRule()
expect(rule.nodes).not.toBeDefined()
rule.prepend('color: black');
expect(rule.nodes.length).toEqual(1);
});
rule.prepend('color: black')
expect(rule.nodes).toHaveLength(1)
})
it('creates nodes property on append()', () => {
let rule = new AtRule();
expect(rule.nodes).not.toBeDefined();
const rule = new AtRule()
expect(rule.nodes).not.toBeDefined()
rule.append('color: black');
expect(rule.nodes.length).toEqual(1);
});
rule.append('color: black')
expect(rule.nodes).toHaveLength(1)
})
it('inserts default spaces', () => {
let rule = new AtRule({ name: 'page', params: 1, nodes: [] });
expect(rule.toString()).toEqual('@page 1 {}');
});
const rule = new AtRule({ name: 'page', params: 1, nodes: [] })
expect(rule.toString()).toEqual('@page 1 {}')
})
it('clone spaces from another at-rule', () => {
let root = parse('@page{}a{}');
let rule = new AtRule({ name: 'page', params: 1, nodes: [] });
root.append(rule);
const root = parse('@page{}a{}')
const rule = new AtRule({ name: 'page', params: 1, nodes: [] })
root.append(rule)
expect(rule.toString()).toEqual('@page 1{}');
});
expect(rule.toString()).toEqual('@page 1{}')
})
+22 -24
View File
@@ -1,30 +1,28 @@
'use strict';
jest.doMock('fs', () => ({ }))
jest.doMock('chalk', () => ({ }))
jest.doMock('supports-color', () => ({ }))
jest.doMock('fs', () => ({ }));
jest.doMock('chalk', () => ({ }));
jest.doMock('supports-color', () => ({ }));
const postcss = require('..');
const postcss = require('..')
it('shows code without chalk', () => {
let error;
try {
postcss.parse('a{');
} catch (e) {
if ( e.name === 'CssSyntaxError' ) {
error = e;
} else {
throw e;
}
let error
try {
postcss.parse('a{')
} catch (e) {
if (e.name === 'CssSyntaxError') {
error = e
} else {
throw e
}
expect(error.showSourceCode(true)).toEqual('> 1 | a{\n' +
' | ^');
});
}
expect(error.showSourceCode(true)).toEqual('> 1 | a{\n' +
' | ^')
})
it('generates source map without fs', () => {
expect(postcss([]).process('a{}', { from: 'a.css', map: true }).css)
.toEqual('a{}\n/*# sourceMappingURL=data:application/json;base64,' +
'eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImEuY3NzIl0sIm5hbWVzIjpbXSw' +
'ibWFwcGluZ3MiOiJBQUFBLEdBQUciLCJmaWxlIjoiYS5jc3MiLCJzb3VyY2' +
'VzQ29udGVudCI6WyJhe30iXX0= */');
});
expect(postcss([]).process('a{}', { from: 'a.css', map: true }).css)
.toEqual('a{}\n/*# sourceMappingURL=data:application/json;base64,' +
'eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImEuY3NzIl0sIm5hbWVzIjpbXSw' +
'ibWFwcGluZ3MiOiJBQUFBLEdBQUciLCJmaWxlIjoiYS5jc3MiLCJzb3VyY2' +
'VzQ29udGVudCI6WyJhe30iXX0= */')
})
+10 -12
View File
@@ -1,17 +1,15 @@
'use strict';
const Comment = require('../lib/comment');
const parse = require('../lib/parse');
const Comment = require('../lib/comment')
const parse = require('../lib/parse')
it('toString() inserts default spaces', () => {
let comment = new Comment({ text: 'hi' });
expect(comment.toString()).toEqual('/* hi */');
});
const comment = new Comment({ text: 'hi' })
expect(comment.toString()).toEqual('/* hi */')
})
it('toString() clones spaces from another comment', () => {
let root = parse('a{} /*hello*/');
let comment = new Comment({ text: 'world' });
root.append(comment);
const root = parse('a{} /*hello*/')
const comment = new Comment({ text: 'world' })
root.append(comment)
expect(root.toString()).toEqual('a{} /*hello*/ /*world*/');
});
expect(root.toString()).toEqual('a{} /*hello*/ /*world*/')
})
+504 -506
View File
File diff suppressed because it is too large Load Diff
+149 -151
View File
@@ -1,194 +1,192 @@
'use strict';
const CssSyntaxError = require('../lib/css-syntax-error')
const postcss = require('../lib/postcss')
const CssSyntaxError = require('../lib/css-syntax-error');
const postcss = require('../lib/postcss');
const stripAnsi = require('strip-ansi')
const Concat = require('concat-with-sourcemaps')
const chalk = require('chalk')
const path = require('path')
const stripAnsi = require('strip-ansi');
const Concat = require('concat-with-sourcemaps');
const chalk = require('chalk');
const path = require('path');
function parseError(css, opts) {
let error;
try {
postcss.parse(css, opts);
} catch (e) {
if ( e.name === 'CssSyntaxError' ) {
error = e;
} else {
throw e;
}
function parseError (css, opts) {
let error
try {
postcss.parse(css, opts)
} catch (e) {
if (e.name === 'CssSyntaxError') {
error = e
} else {
throw e
}
return error;
}
return error
}
it('saves source', () => {
let error = parseError('a {\n content: "\n}');
const error = parseError('a {\n content: "\n}')
expect(error instanceof CssSyntaxError).toBeTruthy();
expect(error.name).toEqual('CssSyntaxError');
expect(error.message).toEqual('<css input>:2:12: Unclosed string');
expect(error.reason).toEqual('Unclosed string');
expect(error.line).toEqual(2);
expect(error.column).toEqual(12);
expect(error.source).toEqual('a {\n content: "\n}');
expect(error instanceof CssSyntaxError).toBeTruthy()
expect(error.name).toEqual('CssSyntaxError')
expect(error.message).toEqual('<css input>:2:12: Unclosed string')
expect(error.reason).toEqual('Unclosed string')
expect(error.line).toEqual(2)
expect(error.column).toEqual(12)
expect(error.source).toEqual('a {\n content: "\n}')
expect(error.input).toEqual({
line: error.line,
column: error.column,
source: error.source
});
});
expect(error.input).toEqual({
line: error.line,
column: error.column,
source: error.source
})
})
it('has stack trace', () => {
expect(parseError('a {\n content: "\n}').stack)
.toMatch(/css-syntax-error\.test\.js/);
});
expect(parseError('a {\n content: "\n}').stack)
.toMatch(/css-syntax-error\.test\.js/)
})
it('highlights broken line with colors', () => {
let c = chalk;
expect(parseError('a {').showSourceCode(true)).toEqual(
c.red.bold('>') + c.gray(' 1 | ') + 'a ' + c.yellow('{') + '\n ' +
c.gray(' | ') + c.red.bold('^'));
});
const c = chalk
expect(parseError('a {').showSourceCode(true)).toEqual(
c.red.bold('>') + c.gray(' 1 | ') + 'a ' + c.yellow('{') + '\n ' +
c.gray(' | ') + c.red.bold('^'))
})
it('highlights broken line', () => {
expect(parseError('a {\n content: "\n}').showSourceCode(false))
.toEqual(' 1 | a {\n' +
'> 2 | content: "\n' +
' | ^\n' +
' 3 | }');
});
expect(parseError('a {\n content: "\n}').showSourceCode(false))
.toEqual(' 1 | a {\n' +
'> 2 | content: "\n' +
' | ^\n' +
' 3 | }')
})
it('highlights broken line, when indented with tabs', () => {
expect(parseError('a {\n\t \t content:\t"\n}').showSourceCode(false))
.toEqual(' 1 | a {\n' +
'> 2 | \t \t content:\t"\n' +
' | \t \t \t^\n' +
' 3 | }');
});
expect(parseError('a {\n\t \t content:\t"\n}').showSourceCode(false))
.toEqual(' 1 | a {\n' +
'> 2 | \t \t content:\t"\n' +
' | \t \t \t^\n' +
' 3 | }')
})
it('highlights small code example', () => {
expect(parseError('a {').showSourceCode(false))
.toEqual('> 1 | a {\n' +
' | ^');
});
expect(parseError('a {').showSourceCode(false))
.toEqual('> 1 | a {\n' +
' | ^')
})
it('add leading space for line numbers', () => {
let css = '\n\n\n\n\n\n\na {\n content: "\n}\n\n\n';
expect(parseError(css).showSourceCode(false))
.toEqual(' 7 | \n' +
' 8 | a {\n' +
'> 9 | content: "\n' +
' | ^\n' +
' 10 | }\n' +
' 11 | ');
});
const css = '\n\n\n\n\n\n\na {\n content: "\n}\n\n\n'
expect(parseError(css).showSourceCode(false))
.toEqual(' 7 | \n' +
' 8 | a {\n' +
'> 9 | content: "\n' +
' | ^\n' +
' 10 | }\n' +
' 11 | ')
})
it('prints with highlight', () => {
expect(stripAnsi(parseError('a {').toString()))
.toEqual('CssSyntaxError: <css input>:1:1: Unclosed block\n' +
'\n' +
'> 1 | a {\n' +
' | ^\n');
});
expect(stripAnsi(parseError('a {').toString()))
.toEqual('CssSyntaxError: <css input>:1:1: Unclosed block\n' +
'\n' +
'> 1 | a {\n' +
' | ^\n')
})
it('misses highlights without source content', () => {
let error = parseError('a {');
error.source = null;
expect(error.toString())
.toEqual('CssSyntaxError: <css input>:1:1: Unclosed block');
});
const error = parseError('a {')
error.source = null
expect(error.toString())
.toEqual('CssSyntaxError: <css input>:1:1: Unclosed block')
})
it('misses position without source', () => {
let decl = postcss.decl({ prop: 'color', value: 'black' });
let error = decl.error('Test');
expect(error.toString()).toEqual('CssSyntaxError: <css input>: Test');
});
const decl = postcss.decl({ prop: 'color', value: 'black' })
const error = decl.error('Test')
expect(error.toString()).toEqual('CssSyntaxError: <css input>: Test')
})
it('uses source map', () => {
let concat = new Concat(true, 'all.css');
concat.add('a.css', 'a { }\n');
concat.add('b.css', '\nb {\n');
const concat = new Concat(true, 'all.css')
concat.add('a.css', 'a { }\n')
concat.add('b.css', '\nb {\n')
let error = parseError(concat.content, {
from: 'build/all.css',
map: { prev: concat.sourceMap }
});
const error = parseError(concat.content, {
from: 'build/all.css',
map: { prev: concat.sourceMap }
})
expect(error.file).toEqual(path.resolve('b.css'));
expect(error.line).toEqual(2);
expect(error.source).not.toBeDefined();
expect(error.file).toEqual(path.resolve('b.css'))
expect(error.line).toEqual(2)
expect(error.source).not.toBeDefined()
expect(error.input).toEqual({
file: path.resolve('build/all.css'),
line: 3,
column: 1,
source: 'a { }\n\nb {\n'
});
});
expect(error.input).toEqual({
file: path.resolve('build/all.css'),
line: 3,
column: 1,
source: 'a { }\n\nb {\n'
})
})
it('shows origin source', () => {
let input = postcss().process('a{}', {
from: '/a.css',
to: '/b.css',
map: { inline: false }
});
let error = parseError('a{', {
from: '/b.css',
to: '/c.css',
map: { prev: input.map }
});
expect(error.source).toEqual('a{}');
});
const input = postcss().process('a{}', {
from: '/a.css',
to: '/b.css',
map: { inline: false }
})
const error = parseError('a{', {
from: '/b.css',
to: '/c.css',
map: { prev: input.map }
})
expect(error.source).toEqual('a{}')
})
it('does not uses wrong source map', () => {
let error = parseError('a { }\nb {', {
from: 'build/all.css',
map: {
prev: {
version: 3,
file: 'build/all.css',
sources: ['a.css', 'b.css'],
mappings: 'A'
}
}
});
expect(error.file).toEqual(path.resolve('build/all.css'));
});
const error = parseError('a { }\nb {', {
from: 'build/all.css',
map: {
prev: {
version: 3,
file: 'build/all.css',
sources: ['a.css', 'b.css'],
mappings: 'A'
}
}
})
expect(error.file).toEqual(path.resolve('build/all.css'))
})
it('set source plugin', () => {
let error = postcss.parse('a{}').first.error('Error', { plugin: 'PL' });
expect(error.plugin).toEqual('PL');
expect(error.toString())
.toMatch(/^CssSyntaxError: PL: <css input>:1:1: Error/);
});
const error = postcss.parse('a{}').first.error('Error', { plugin: 'PL' })
expect(error.plugin).toEqual('PL')
expect(error.toString())
.toMatch(/^CssSyntaxError: PL: <css input>:1:1: Error/)
})
it('set source plugin automatically', () => {
let plugin = postcss.plugin('test-plugin', () => {
return css => {
throw css.first.error('Error');
};
});
const plugin = postcss.plugin('test-plugin', () => {
return css => {
throw css.first.error('Error')
}
})
return postcss([plugin]).process('a{}').catch( error => {
if ( error.name !== 'CssSyntaxError' ) throw error;
expect(error.plugin).toEqual('test-plugin');
expect(error.toString()).toMatch(/test-plugin/);
});
});
return postcss([plugin]).process('a{}').catch(error => {
if (error.name !== 'CssSyntaxError') throw error
expect(error.plugin).toEqual('test-plugin')
expect(error.toString()).toMatch(/test-plugin/)
})
})
it('set plugin automatically in async', () => {
let plugin = postcss.plugin('async-plugin', () => {
return css => {
return new Promise( (resolve, reject) => {
reject(css.first.error('Error'));
});
};
});
const plugin = postcss.plugin('async-plugin', () => {
return css => {
return new Promise((resolve, reject) => {
reject(css.first.error('Error'))
})
}
})
return postcss([plugin]).process('a{}').catch( error => {
if ( error.name !== 'CssSyntaxError' ) throw error;
expect(error.plugin).toEqual('async-plugin');
});
});
return postcss([plugin]).process('a{}').catch(error => {
if (error.name !== 'CssSyntaxError') throw error
expect(error.plugin).toEqual('async-plugin')
})
})
+21 -23
View File
@@ -1,31 +1,29 @@
'use strict';
const Declaration = require('../lib/declaration');
const parse = require('../lib/parse');
const Rule = require('../lib/rule');
const Declaration = require('../lib/declaration')
const parse = require('../lib/parse')
const Rule = require('../lib/rule')
it('initializes with properties', () => {
let decl = new Declaration({ prop: 'color', value: 'black' });
expect(decl.prop).toEqual('color');
expect(decl.value).toEqual('black');
});
const decl = new Declaration({ prop: 'color', value: 'black' })
expect(decl.prop).toEqual('color')
expect(decl.value).toEqual('black')
})
it('returns boolean important', () => {
let decl = new Declaration({ prop: 'color', value: 'black' });
decl.important = true;
expect(decl.toString()).toEqual('color: black !important');
});
const decl = new Declaration({ prop: 'color', value: 'black' })
decl.important = true
expect(decl.toString()).toEqual('color: black !important')
})
it('inserts default spaces', () => {
let decl = new Declaration({ prop: 'color', value: 'black' });
let rule = new Rule({ selector: 'a' });
rule.append(decl);
expect(rule.toString()).toEqual('a {\n color: black\n}');
});
const decl = new Declaration({ prop: 'color', value: 'black' })
const rule = new Rule({ selector: 'a' })
rule.append(decl)
expect(rule.toString()).toEqual('a {\n color: black\n}')
})
it('clones spaces from another declaration', () => {
let root = parse('a{color:black}');
let decl = new Declaration({ prop: 'margin', value: '0' });
root.first.append(decl);
expect(root.toString()).toEqual('a{color:black;margin:0}');
});
const root = parse('a{color:black}')
const decl = new Declaration({ prop: 'margin', value: '0' })
root.first.append(decl)
expect(root.toString()).toEqual('a{color:black;margin:0}')
})
+32 -34
View File
@@ -1,54 +1,52 @@
'use strict';
const LazyResult = require('../lib/lazy-result')
const Processor = require('../lib/processor')
const LazyResult = require('../lib/lazy-result');
const Processor = require('../lib/processor');
const mozilla = require('source-map')
const mozilla = require('source-map');
let processor = new Processor();
const processor = new Processor()
it('contains AST', () => {
let result = new LazyResult(processor, 'a {}', { });
expect(result.root.type).toEqual('root');
});
const result = new LazyResult(processor, 'a {}', { })
expect(result.root.type).toEqual('root')
})
it('will stringify css', () => {
let result = new LazyResult(processor, 'a {}', { });
expect(result.css).toEqual('a {}');
});
const result = new LazyResult(processor, 'a {}', { })
expect(result.css).toEqual('a {}')
})
it('stringifies css', () => {
let result = new LazyResult(processor, 'a {}', { });
expect('' + result).toEqual(result.css);
});
const result = new LazyResult(processor, 'a {}', { })
expect('' + result).toEqual(result.css)
})
it('has content alias for css', () => {
let result = new LazyResult(processor, 'a {}', { });
expect(result.content).toEqual('a {}');
});
const result = new LazyResult(processor, 'a {}', { })
expect(result.content).toEqual('a {}')
})
it('has map only if necessary', () => {
let result = new LazyResult(processor, '', { });
expect(result.map).not.toBeDefined();
const result1 = new LazyResult(processor, '', { })
expect(result1.map).not.toBeDefined()
result = new LazyResult(processor, '', { });
expect(result.map).not.toBeDefined();
const result2 = new LazyResult(processor, '', { })
expect(result2.map).not.toBeDefined()
result = new LazyResult(processor, '', { map: { inline: false } });
expect(result.map instanceof mozilla.SourceMapGenerator).toBeTruthy();
});
const result3 = new LazyResult(processor, '', { map: { inline: false } })
expect(result3.map instanceof mozilla.SourceMapGenerator).toBeTruthy()
})
it('contains options', () => {
let result = new LazyResult(processor, 'a {}', { to: 'a.css' });
expect(result.opts).toEqual({ to: 'a.css' });
});
const result = new LazyResult(processor, 'a {}', { to: 'a.css' })
expect(result.opts).toEqual({ to: 'a.css' })
})
it('contains warnings', () => {
let result = new LazyResult(processor, 'a {}', { });
expect(result.warnings()).toEqual([]);
});
const result = new LazyResult(processor, 'a {}', { })
expect(result.warnings()).toEqual([])
})
it('contains messages', () => {
let result = new LazyResult(processor, 'a {}', { });
expect(result.messages).toEqual([]);
});
const result = new LazyResult(processor, 'a {}', { })
expect(result.messages).toEqual([])
})
+23 -25
View File
@@ -1,45 +1,43 @@
'use strict';
const list = require('../lib/list');
const list = require('../lib/list')
it('space() splits list by spaces', () => {
expect(list.space('a b')).toEqual(['a', 'b']);
});
expect(list.space('a b')).toEqual(['a', 'b'])
})
it('space() trims values', () => {
expect(list.space(' a b ')).toEqual(['a', 'b']);
});
expect(list.space(' a b ')).toEqual(['a', 'b'])
})
it('space() checks quotes', () => {
expect(list.space('"a b\\"" \'\'')).toEqual(['"a b\\""', '\'\'']);
});
expect(list.space('"a b\\"" \'\'')).toEqual(['"a b\\""', '\'\''])
})
it('space() checks functions', () => {
expect(list.space('f( )) a( () )')).toEqual(['f( ))', 'a( () )']);
});
expect(list.space('f( )) a( () )')).toEqual(['f( ))', 'a( () )'])
})
it('space() works from variable', () => {
let space = list.space;
expect(space('a b')).toEqual(['a', 'b']);
});
const space = list.space
expect(space('a b')).toEqual(['a', 'b'])
})
it('comma() splits list by spaces', () => {
expect(list.comma('a, b')).toEqual(['a', 'b']);
});
expect(list.comma('a, b')).toEqual(['a', 'b'])
})
it('comma() adds last empty', () => {
expect(list.comma('a, b,')).toEqual(['a', 'b', '']);
});
expect(list.comma('a, b,')).toEqual(['a', 'b', ''])
})
it('comma() checks quotes', () => {
expect(list.comma('"a,b\\"", \'\'')).toEqual(['"a,b\\""', '\'\'']);
});
expect(list.comma('"a,b\\"", \'\'')).toEqual(['"a,b\\""', '\'\''])
})
it('comma() checks functions', () => {
expect(list.comma('f(,)), a(,(),)')).toEqual(['f(,))', 'a(,(),)']);
});
expect(list.comma('f(,)), a(,(),)')).toEqual(['f(,))', 'a(,(),)'])
})
it('comma() works from variable', () => {
let comma = list.comma;
expect(comma('a, b')).toEqual(['a', 'b']);
});
const comma = list.comma
expect(comma('a, b')).toEqual(['a', 'b'])
})
+426 -427
View File
@@ -1,558 +1,557 @@
'use strict';
const PreviousMap = require('../lib/previous-map')
const postcss = require('../lib/postcss')
const PreviousMap = require('../lib/previous-map');
const postcss = require('../lib/postcss');
const mozilla = require('source-map')
const path = require('path')
const fs = require('fs-extra')
const mozilla = require('source-map');
const path = require('path');
const fs = require('fs-extra');
let consumer = map => mozilla.SourceMapConsumer.fromSourceMap(map);
function read(result) {
let prev = new PreviousMap(result.css, { });
return prev.consumer();
function consumer (map) {
return mozilla.SourceMapConsumer.fromSourceMap(map)
}
let dir = path.join(__dirname, 'map-fixtures');
function read (result) {
const prev = new PreviousMap(result.css, { })
return prev.consumer()
}
let doubler = postcss( css => {
css.walkDecls( decl => decl.parent.prepend(decl.clone()) );
});
let lighter = postcss( css => {
css.walkDecls( decl => {
decl.value = 'white';
});
});
const dir = path.join(__dirname, 'map-fixtures')
const doubler = postcss(css => {
css.walkDecls(decl => decl.parent.prepend(decl.clone()))
})
const lighter = postcss(css => {
css.walkDecls(decl => {
decl.value = 'white'
})
})
afterEach(() => {
if ( fs.existsSync(dir) ) fs.removeSync(dir);
});
if (fs.existsSync(dir)) fs.removeSync(dir)
})
it('adds map field only on request', () => {
expect(postcss().process('a {}').map).not.toBeDefined();
});
expect(postcss().process('a {}').map).not.toBeDefined()
})
it('return map generator', () => {
let map = postcss().process('a {}', { map: { inline: false } }).map;
expect(map instanceof mozilla.SourceMapGenerator).toBeTruthy();
});
const map = postcss().process('a {}', { map: { inline: false } }).map
expect(map instanceof mozilla.SourceMapGenerator).toBeTruthy()
})
it('generate right source map', () => {
let css = 'a {\n color: black;\n }';
let processor = postcss( root => {
root.walkRules( rule => {
rule.selector = 'strong';
});
root.walkDecls( decl => {
decl.parent.prepend( decl.clone({ prop: 'background' }) );
});
});
const css = 'a {\n color: black;\n }'
const processor = postcss(root => {
root.walkRules(rule => {
rule.selector = 'strong'
})
root.walkDecls(decl => {
decl.parent.prepend(decl.clone({ prop: 'background' }))
})
})
let result = processor.process(css, {
from: 'a.css',
to: 'b.css',
map: true
});
let map = read(result);
const result = processor.process(css, {
from: 'a.css',
to: 'b.css',
map: true
})
const map = read(result)
expect(map.file).toEqual('b.css');
expect(map.file).toEqual('b.css')
expect(map.originalPositionFor({ line: 1, column: 0 })).toEqual({
source: 'a.css',
line: 1,
column: 0,
name: null
});
expect(map.originalPositionFor({ line: 2, column: 2 })).toEqual({
source: 'a.css',
line: 2,
column: 2,
name: null
});
expect(map.originalPositionFor({ line: 3, column: 2 })).toEqual({
source: 'a.css',
line: 2,
column: 2,
name: null
});
});
expect(map.originalPositionFor({ line: 1, column: 0 })).toEqual({
source: 'a.css',
line: 1,
column: 0,
name: null
})
expect(map.originalPositionFor({ line: 2, column: 2 })).toEqual({
source: 'a.css',
line: 2,
column: 2,
name: null
})
expect(map.originalPositionFor({ line: 3, column: 2 })).toEqual({
source: 'a.css',
line: 2,
column: 2,
name: null
})
})
it('changes previous source map', () => {
let css = 'a { color: black }';
const css = 'a { color: black }'
let doubled = doubler.process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
const doubled = doubler.process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
let lighted = lighter.process(doubled.css, {
from: 'b.css',
to: 'c.css',
map: { prev: doubled.map }
});
const lighted = lighter.process(doubled.css, {
from: 'b.css',
to: 'c.css',
map: { prev: doubled.map }
})
let map = consumer(lighted.map);
expect(map.originalPositionFor({ line: 1, column: 18 })).toEqual({
source: 'a.css',
line: 1,
column: 4,
name: null
});
});
const map = consumer(lighted.map)
expect(map.originalPositionFor({ line: 1, column: 18 })).toEqual({
source: 'a.css',
line: 1,
column: 4,
name: null
})
})
it('adds source map annotation', () => {
let css = 'a { }/*# sourceMappingURL=a.css.map */';
let result = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
const css = 'a { }/*# sourceMappingURL=a.css.map */'
const result = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
expect(result.css).toEqual('a { }\n/*# sourceMappingURL=b.css.map */');
});
expect(result.css).toEqual('a { }\n/*# sourceMappingURL=b.css.map */')
})
it('misses source map annotation, if user ask', () => {
let css = 'a { }';
let result = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { annotation: false }
});
const css = 'a { }'
const result = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { annotation: false }
})
expect(result.css).toEqual(css);
});
expect(result.css).toEqual(css)
})
it('misses source map annotation, if previous map missed it', () => {
let css = 'a { }';
const css = 'a { }'
let step1 = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { annotation: false }
});
const step1 = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { annotation: false }
})
let step2 = postcss().process(step1.css, {
from: 'b.css',
to: 'c.css',
map: { prev: step1.map }
});
const step2 = postcss().process(step1.css, {
from: 'b.css',
to: 'c.css',
map: { prev: step1.map }
})
expect(step2.css).toEqual(css);
});
expect(step2.css).toEqual(css)
})
it('uses user path in annotation, relative to options.to', () => {
let result = postcss().process('a { }', {
from: 'source/a.css',
to: 'build/b.css',
map: { annotation: 'maps/b.map' }
});
const result = postcss().process('a { }', {
from: 'source/a.css',
to: 'build/b.css',
map: { annotation: 'maps/b.map' }
})
expect(result.css).toEqual('a { }\n/*# sourceMappingURL=maps/b.map */');
let map = consumer(result.map);
expect(result.css).toEqual('a { }\n/*# sourceMappingURL=maps/b.map */')
const map = consumer(result.map)
expect(map.file).toEqual('../b.css');
expect(map.originalPositionFor({ line: 1, column: 0 }).source)
.toEqual('../../source/a.css');
});
expect(map.file).toEqual('../b.css')
expect(map.originalPositionFor({ line: 1, column: 0 }).source)
.toEqual('../../source/a.css')
})
it('generates inline map', () => {
let css = 'a { }';
const css = 'a { }'
let inline = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: true }
});
const inline = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: true }
})
expect(inline.map).not.toBeDefined();
expect(inline.css).toMatch(/# sourceMappingURL=data:/);
expect(inline.map).not.toBeDefined()
expect(inline.css).toMatch(/# sourceMappingURL=data:/)
let separated = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
const separated = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
let base64 = new Buffer(separated.map.toString()).toString('base64');
let end = inline.css.slice(-base64.length - 3);
expect(end).toEqual(base64 + ' */');
});
const base64 = Buffer.from(separated.map.toString()).toString('base64')
const end = inline.css.slice(-base64.length - 3)
expect(end).toEqual(base64 + ' */')
})
it('generates inline map by default', () => {
let inline = postcss().process('a { }', {
from: 'a.css',
to: 'b.css',
map: true
});
expect(inline.css).toMatch(/# sourceMappingURL=data:/);
});
const inline = postcss().process('a { }', {
from: 'a.css',
to: 'b.css',
map: true
})
expect(inline.css).toMatch(/# sourceMappingURL=data:/)
})
it('generates separated map if previous map was not inlined', () => {
let step1 = doubler.process('a { color: black }', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
let step2 = lighter.process(step1.css, {
from: 'b.css',
to: 'c.css',
map: { prev: step1.map }
});
const step1 = doubler.process('a { color: black }', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
const step2 = lighter.process(step1.css, {
from: 'b.css',
to: 'c.css',
map: { prev: step1.map }
})
expect(typeof step2.map).toEqual('object');
});
expect(typeof step2.map).toEqual('object')
})
it('generates separated map on annotation option', () => {
let result = postcss().process('a { }', {
from: 'a.css',
to: 'b.css',
map: { annotation: false }
});
const result = postcss().process('a { }', {
from: 'a.css',
to: 'b.css',
map: { annotation: false }
})
expect(typeof result.map).toEqual('object');
});
expect(typeof result.map).toEqual('object')
})
it('allows change map type', () => {
let step1 = postcss().process('a { }', {
from: 'a.css',
to: 'b.css',
map: { inline: true }
});
const step1 = postcss().process('a { }', {
from: 'a.css',
to: 'b.css',
map: { inline: true }
})
let step2 = postcss().process(step1.css, {
from: 'b.css',
to: 'c.css',
map: { inline: false }
});
const step2 = postcss().process(step1.css, {
from: 'b.css',
to: 'c.css',
map: { inline: false }
})
expect(typeof step2.map).toEqual('object');
expect(step2.css).toMatch(/# sourceMappingURL=c\.css\.map/);
});
expect(typeof step2.map).toEqual('object')
expect(step2.css).toMatch(/# sourceMappingURL=c\.css\.map/)
})
it('misses check files on requires', () => {
let file = path.join(dir, 'a.css');
const file = path.join(dir, 'a.css')
let step1 = doubler.process('a { }', {
from: 'a.css',
to: file,
map: true
});
const step1 = doubler.process('a { }', {
from: 'a.css',
to: file,
map: true
})
fs.outputFileSync(file + '.map', step1.map);
let step2 = lighter.process(step1.css, {
from: file,
to: 'b.css',
map: false
});
fs.outputFileSync(file + '.map', step1.map)
const step2 = lighter.process(step1.css, {
from: file,
to: 'b.css',
map: false
})
expect(step2.map).not.toBeDefined();
});
expect(step2.map).not.toBeDefined()
})
it('works in subdirs', () => {
let result = doubler.process('a { }', {
from: 'from/a.css',
to: 'out/b.css',
map: { inline: false }
});
const result = doubler.process('a { }', {
from: 'from/a.css',
to: 'out/b.css',
map: { inline: false }
})
expect(result.css).toMatch(/sourceMappingURL=b.css.map/);
expect(result.css).toMatch(/sourceMappingURL=b.css.map/)
let map = consumer(result.map);
expect(map.file).toEqual('b.css');
expect(map.sources).toEqual(['../from/a.css']);
});
const map = consumer(result.map)
expect(map.file).toEqual('b.css')
expect(map.sources).toEqual(['../from/a.css'])
})
it('uses map from subdir', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: { inline: false }
});
const step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: { inline: false }
})
let step2 = doubler.process(step1.css, {
from: 'out/b.css',
to: 'out/two/c.css',
map: { prev: step1.map }
});
const step2 = doubler.process(step1.css, {
from: 'out/b.css',
to: 'out/two/c.css',
map: { prev: step1.map }
})
let source = consumer(step2.map)
.originalPositionFor({ line: 1, column: 0 }).source;
expect(source).toEqual('../../a.css');
let source = consumer(step2.map)
.originalPositionFor({ line: 1, column: 0 }).source
expect(source).toEqual('../../a.css')
let step3 = doubler.process(step2.css, {
from: 'c.css',
to: 'd.css',
map: { prev: step2.map }
});
const step3 = doubler.process(step2.css, {
from: 'c.css',
to: 'd.css',
map: { prev: step2.map }
})
source = consumer(step3.map)
.originalPositionFor({ line: 1, column: 0 }).source;
expect(source).toEqual('../../a.css');
});
source = consumer(step3.map)
.originalPositionFor({ line: 1, column: 0 }).source
expect(source).toEqual('../../a.css')
})
it('uses map from subdir if it inlined', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: true
});
const step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: true
})
let step2 = doubler.process(step1.css, {
from: 'out/b.css',
to: 'out/two/c.css',
map: { inline: false }
});
const step2 = doubler.process(step1.css, {
from: 'out/b.css',
to: 'out/two/c.css',
map: { inline: false }
})
let source = consumer(step2.map)
.originalPositionFor({ line: 1, column: 0 }).source;
expect(source).toEqual('../../a.css');
});
const source = consumer(step2.map)
.originalPositionFor({ line: 1, column: 0 }).source
expect(source).toEqual('../../a.css')
})
it('uses map from subdir if it written as a file', () => {
let step1 = doubler.process('a { }', {
from: 'source/a.css',
to: 'one/b.css',
map: { annotation: 'maps/b.css.map', inline: false }
});
const step1 = doubler.process('a { }', {
from: 'source/a.css',
to: 'one/b.css',
map: { annotation: 'maps/b.css.map', inline: false }
})
let source = consumer(step1.map)
.originalPositionFor({ line: 1, column: 0 }).source;
expect(source).toEqual('../../source/a.css');
let source = consumer(step1.map)
.originalPositionFor({ line: 1, column: 0 }).source
expect(source).toEqual('../../source/a.css')
let file = path.join(dir, 'one', 'maps', 'b.css.map');
fs.outputFileSync(file, step1.map);
const file = path.join(dir, 'one', 'maps', 'b.css.map')
fs.outputFileSync(file, step1.map)
let step2 = doubler.process(step1.css, {
from: path.join(dir, 'one', 'b.css'),
to: path.join(dir, 'two', 'c.css'),
map: true
});
const step2 = doubler.process(step1.css, {
from: path.join(dir, 'one', 'b.css'),
to: path.join(dir, 'two', 'c.css'),
map: true
})
source = consumer(step2.map)
.originalPositionFor({ line: 1, column: 0 }).source;
expect(source).toEqual('../source/a.css');
});
source = consumer(step2.map)
.originalPositionFor({ line: 1, column: 0 }).source
expect(source).toEqual('../source/a.css')
})
it('works with different types of maps', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
const step1 = doubler.process('a { }', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
let map = step1.map;
let maps = [map, consumer(map), map.toJSON(), map.toString()];
const map = step1.map
const maps = [map, consumer(map), map.toJSON(), map.toString()]
for ( let i of maps ) {
let step2 = doubler.process(step1.css, {
from: 'b.css',
to: 'c.css',
map: { prev: i }
});
let source = consumer(step2.map)
.originalPositionFor({ line: 1, column: 0 }).source;
expect(source).toEqual('a.css');
}
});
for (const i of maps) {
const step2 = doubler.process(step1.css, {
from: 'b.css',
to: 'c.css',
map: { prev: i }
})
const source = consumer(step2.map)
.originalPositionFor({ line: 1, column: 0 }).source
expect(source).toEqual('a.css')
}
})
it('sets source content by default', () => {
let result = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: true
});
const result = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: true
})
expect(read(result).sourceContentFor('../a.css')).toEqual('a { }');
});
expect(read(result).sourceContentFor('../a.css')).toEqual('a { }')
})
it('misses source content on request', () => {
let result = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: { sourcesContent: false }
});
const result = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: { sourcesContent: false }
})
expect(read(result).sourceContentFor('../a.css')).toBe(null);
});
expect(read(result).sourceContentFor('../a.css')).toBeNull()
})
it('misses source content if previous not have', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/a.css',
map: { sourcesContent: false }
});
const step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/a.css',
map: { sourcesContent: false }
})
let file1 = postcss.parse(step1.css, {
from: 'a.css',
map: { prev: step1.map }
});
let file2 = postcss.parse('b { }', { from: 'b.css', map: true });
const file1 = postcss.parse(step1.css, {
from: 'a.css',
map: { prev: step1.map }
})
const file2 = postcss.parse('b { }', { from: 'b.css', map: true })
file2.append( file1.first.clone() );
let step2 = file2.toResult({ to: 'c.css', map: true });
file2.append(file1.first.clone())
const step2 = file2.toResult({ to: 'c.css', map: true })
expect(read(step2).sourceContentFor('b.css')).toBe(null);
});
expect(read(step2).sourceContentFor('b.css')).toBeNull()
})
it('misses source content on request', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/a.css',
map: { sourcesContent: true }
});
it('misses source content on request in multiple steps', () => {
const step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/a.css',
map: { sourcesContent: true }
})
let file1 = postcss.parse(step1.css, {
from: 'a.css',
map: { prev: step1.map }
});
let file2 = postcss.parse('b { }', { from: 'b.css', map: true });
const file1 = postcss.parse(step1.css, {
from: 'a.css',
map: { prev: step1.map }
})
const file2 = postcss.parse('b { }', { from: 'b.css', map: true })
file2.append( file1.first.clone() );
let step2 = file2.toResult({
to: 'c.css',
map: { sourcesContent: false }
});
file2.append(file1.first.clone())
const step2 = file2.toResult({
to: 'c.css',
map: { sourcesContent: false }
})
let map = read(step2);
expect(map.sourceContentFor('b.css')).toBe(null);
expect(map.sourceContentFor('../a.css')).toBe(null);
});
const map = read(step2)
expect(map.sourceContentFor('b.css')).toBeNull()
expect(map.sourceContentFor('../a.css')).toBeNull()
})
it('detects input file name from map', () => {
let one = doubler.process('a { }', { to: 'a.css', map: true });
let two = doubler.process(one.css, { map: { prev: one.map } });
expect(two.root.first.source.input.file).toEqual(path.resolve('a.css'));
});
const one = doubler.process('a { }', { to: 'a.css', map: true })
const two = doubler.process(one.css, { map: { prev: one.map } })
expect(two.root.first.source.input.file).toEqual(path.resolve('a.css'))
})
it('works without file names', () => {
let step1 = doubler.process('a { }', { map: true });
let step2 = doubler.process(step1.css);
expect(step2.css).toMatch(/a \{ \}\n\/\*/);
});
const step1 = doubler.process('a { }', { map: true })
const step2 = doubler.process(step1.css)
expect(step2.css).toMatch(/a \{ \}\n\/\*/)
})
it('supports UTF-8', () => {
let step1 = doubler.process('a { }', {
from: 'вход.css',
to: 'шаг1.css',
map: true
});
let step2 = doubler.process(step1.css, {
from: 'шаг1.css',
to: 'выход.css'
});
const step1 = doubler.process('a { }', {
from: 'вход.css',
to: 'шаг1.css',
map: true
})
const step2 = doubler.process(step1.css, {
from: 'шаг1.css',
to: 'выход.css'
})
expect(read(step2).file).toEqual('выход.css');
});
expect(read(step2).file).toEqual('выход.css')
})
it('generates map for node created manually', () => {
let contenter = postcss(css => {
css.first.prepend({ prop: 'content', value: '""' });
});
let result = contenter.process('a:after{\n}', { map: true });
let map = read(result);
expect(map.originalPositionFor({ line: 2, column: 5 })).toEqual({
source: '<no source>',
column: 0,
line: 1,
name: null
});
});
const contenter = postcss(css => {
css.first.prepend({ prop: 'content', value: '""' })
})
const result = contenter.process('a:after{\n}', { map: true })
const map = read(result)
expect(map.originalPositionFor({ line: 2, column: 5 })).toEqual({
source: '<no source>',
column: 0,
line: 1,
name: null
})
})
it('uses input file name as output file name', () => {
let result = doubler.process('a{}', {
from: 'a.css',
map: { inline: false }
});
expect(result.map.toJSON().file).toEqual('a.css');
});
const result = doubler.process('a{}', {
from: 'a.css',
map: { inline: false }
})
expect(result.map.toJSON().file).toEqual('a.css')
})
it('uses to.css as default output name', () => {
let result = doubler.process('a{}', { map: { inline: false } });
expect(result.map.toJSON().file).toEqual('to.css');
});
const result = doubler.process('a{}', { map: { inline: false } })
expect(result.map.toJSON().file).toEqual('to.css')
})
it('supports annotation comment in any place', () => {
let css = '/*# sourceMappingURL=a.css.map */a { }';
let result = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
const css = '/*# sourceMappingURL=a.css.map */a { }'
const result = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
expect(result.css).toEqual('a { }\n/*# sourceMappingURL=b.css.map */');
});
expect(result.css).toEqual('a { }\n/*# sourceMappingURL=b.css.map */')
})
it('does not update annotation on request', () => {
let css = 'a { }/*# sourceMappingURL=a.css.map */';
let result = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { annotation: false, inline: false }
});
const css = 'a { }/*# sourceMappingURL=a.css.map */'
const result = postcss().process(css, {
from: 'a.css',
to: 'b.css',
map: { annotation: false, inline: false }
})
expect(result.css).toEqual('a { }/*# sourceMappingURL=a.css.map */');
});
expect(result.css).toEqual('a { }/*# sourceMappingURL=a.css.map */')
})
it('clears source map', () => {
let css1 = postcss.root().toResult({ map: true }).css;
let css2 = postcss.root().toResult({ map: true }).css;
const css1 = postcss.root().toResult({ map: true }).css
const css2 = postcss.root().toResult({ map: true }).css
let root = postcss.root();
root.append(css1);
root.append(css2);
const root = postcss.root()
root.append(css1)
root.append(css2)
let css = root.toResult({ map: true }).css;
expect(css.match(/sourceMappingURL/g).length).toEqual(1);
});
const css = root.toResult({ map: true }).css
expect(css.match(/sourceMappingURL/g)).toHaveLength(1)
})
it('uses Windows line separation too', () => {
let result = postcss().process('a {\r\n}', { map: true });
expect(result.css).toMatch(/a \{\r\n\}\r\n\/\*# sourceMappingURL=/);
});
const result = postcss().process('a {\r\n}', { map: true })
expect(result.css).toMatch(/a \{\r\n\}\r\n\/\*# sourceMappingURL=/)
})
it('`map.from` should override the source map sources', () => {
let result = postcss().process('a{}', {
map: {
inline: false,
from: 'file:///dir/a.css'
}
});
expect(result.map.toJSON().sources).toEqual(['file:///dir/a.css']);
});
const result = postcss().process('a{}', {
map: {
inline: false,
from: 'file:///dir/a.css'
}
})
expect(result.map.toJSON().sources).toEqual(['file:///dir/a.css'])
})
it('preserves absolute urls in `to`', () => {
let result = postcss().process('a{}', {
from: '/dir/to/a.css',
to: 'http://example.com/a.css',
map: { inline: false }
});
expect(result.map.toJSON().file).toEqual('http://example.com/a.css');
});
const result = postcss().process('a{}', {
from: '/dir/to/a.css',
to: 'http://example.com/a.css',
map: { inline: false }
})
expect(result.map.toJSON().file).toEqual('http://example.com/a.css')
})
it('preserves absolute urls in sources', () => {
let result = postcss().process('a{}', {
from: 'file:///dir/a.css',
to: 'http://example.com/a.css',
map: { inline: false }
});
expect(result.map.toJSON().sources).toEqual(['file:///dir/a.css']);
});
const result = postcss().process('a{}', {
from: 'file:///dir/a.css',
to: 'http://example.com/a.css',
map: { inline: false }
})
expect(result.map.toJSON().sources).toEqual(['file:///dir/a.css'])
})
it('preserves absolute urls in sources from previous map', () => {
let result1 = postcss().process('a{}', {
from: 'http://example.com/a.css',
to: 'http://example.com/b.css',
map: true
});
let result2 = postcss().process(result1.css, {
to: 'http://example.com/c.css',
map: {
inline: false
}
});
expect(result2.root.source.input.file).toEqual('http://example.com/b.css');
expect(result2.map.toJSON().sources).toEqual(['http://example.com/a.css']);
});
const result1 = postcss().process('a{}', {
from: 'http://example.com/a.css',
to: 'http://example.com/b.css',
map: true
})
const result2 = postcss().process(result1.css, {
to: 'http://example.com/c.css',
map: {
inline: false
}
})
expect(result2.root.source.input.file).toEqual('http://example.com/b.css')
expect(result2.map.toJSON().sources).toEqual(['http://example.com/a.css'])
})
+234 -235
View File
@@ -1,337 +1,336 @@
'use strict';
const CssSyntaxError = require('../lib/css-syntax-error')
const Declaration = require('../lib/declaration')
const postcss = require('../lib/postcss')
const AtRule = require('../lib/at-rule')
const parse = require('../lib/parse')
const Root = require('../lib/root')
const Rule = require('../lib/rule')
const CssSyntaxError = require('../lib/css-syntax-error');
const Declaration = require('../lib/declaration');
const postcss = require('../lib/postcss');
const AtRule = require('../lib/at-rule');
const parse = require('../lib/parse');
const Root = require('../lib/root');
const Rule = require('../lib/rule');
const path = require('path')
const path = require('path');
let stringify = (node, builder) => builder(node.selector);
function stringify (node, builder) {
return builder(node.selector)
}
it('shows error on wrong constructor types', () => {
expect(() => {
new Rule('a');
}).toThrowError('PostCSS nodes constructor accepts object, not "a"');
});
expect(() => {
new Rule('a')
}).toThrowError('PostCSS nodes constructor accepts object, not "a"')
})
it('error() generates custom error', () => {
let file = path.resolve('a.css');
let css = parse('a{}', { from: file });
let error = css.first.error('Test');
expect(error instanceof CssSyntaxError).toBeTruthy();
expect(error.message).toEqual(file + ':1:1: Test');
});
const file = path.resolve('a.css')
const css = parse('a{}', { from: file })
const error = css.first.error('Test')
expect(error instanceof CssSyntaxError).toBeTruthy()
expect(error.message).toEqual(file + ':1:1: Test')
})
it('error() generates custom error for nodes without source', () => {
let rule = new Rule({ selector: 'a' });
let error = rule.error('Test');
expect(error.message).toEqual('<css input>: Test');
});
const rule = new Rule({ selector: 'a' })
const error = rule.error('Test')
expect(error.message).toEqual('<css input>: Test')
})
it('error() highlights index', () => {
let root = parse('a { b: c }');
let error = root.first.first.error('Bad semicolon', { index: 1 });
expect(error.showSourceCode(false)).toEqual('> 1 | a { b: c }\n' +
' | ^');
});
const root = parse('a { b: c }')
const error = root.first.first.error('Bad semicolon', { index: 1 })
expect(error.showSourceCode(false)).toEqual('> 1 | a { b: c }\n' +
' | ^')
})
it('error() highlights word', () => {
let root = parse('a { color: x red }');
let error = root.first.first.error('Wrong color', { word: 'x' });
expect(error.showSourceCode(false)).toEqual('> 1 | a { color: x red }\n' +
' | ^');
});
const root = parse('a { color: x red }')
const error = root.first.first.error('Wrong color', { word: 'x' })
expect(error.showSourceCode(false)).toEqual('> 1 | a { color: x red }\n' +
' | ^')
})
it('error() highlights word in multiline string', () => {
let root = parse('a { color: red\n x }');
let error = root.first.first.error('Wrong color', { word: 'x' });
expect(error.showSourceCode(false)).toEqual(' 1 | a { color: red\n' +
const root = parse('a { color: red\n x }')
const error = root.first.first.error('Wrong color', { word: 'x' })
expect(error.showSourceCode(false)).toEqual(' 1 | a { color: red\n' +
'> 2 | x }\n' +
' | ^');
});
' | ^')
})
it('warn() attaches a warning to the result object', () => {
let warning;
let warner = postcss.plugin('warner', () => {
return (css, result) => {
warning = css.first.warn(result, 'FIRST!');
};
});
let warning
const warner = postcss.plugin('warner', () => {
return (css, result) => {
warning = css.first.warn(result, 'FIRST!')
}
})
return postcss([warner]).process('a{}', { from: undefined })
.then(result => {
expect(warning.type).toEqual('warning');
expect(warning.text).toEqual('FIRST!');
expect(warning.plugin).toEqual('warner');
expect(result.warnings()).toEqual([warning]);
});
});
return postcss([warner]).process('a{}', { from: undefined }).then(result => {
expect(warning.type).toEqual('warning')
expect(warning.text).toEqual('FIRST!')
expect(warning.plugin).toEqual('warner')
expect(result.warnings()).toEqual([warning])
})
})
it('warn() accepts options', () => {
let warner = postcss.plugin('warner', () => {
return (css, result) => {
css.first.warn(result, 'FIRST!', { index: 1 });
};
});
const warner = postcss.plugin('warner', () => {
return (css, result) => {
css.first.warn(result, 'FIRST!', { index: 1 })
}
})
let result = postcss([ warner() ]).process('a{}');
expect(result.warnings().length).toEqual(1);
expect(result.warnings()[0].index).toEqual(1);
});
const result = postcss([warner()]).process('a{}')
expect(result.warnings()).toHaveLength(1)
expect(result.warnings()[0].index).toEqual(1)
})
it('remove() removes node from parent', () => {
let rule = new Rule({ selector: 'a' });
let decl = new Declaration({ prop: 'color', value: 'black' });
rule.append(decl);
const rule = new Rule({ selector: 'a' })
const decl = new Declaration({ prop: 'color', value: 'black' })
rule.append(decl)
decl.remove();
expect(rule.nodes.length).toEqual(0);
expect(decl.parent).not.toBeDefined();
});
decl.remove()
expect(rule.nodes).toHaveLength(0)
expect(decl.parent).not.toBeDefined()
})
it('replaceWith() inserts new node', () => {
let rule = new Rule({ selector: 'a' });
rule.append({ prop: 'color', value: 'black' });
rule.append({ prop: 'width', value: '1px' });
rule.append({ prop: 'height', value: '1px' });
const rule = new Rule({ selector: 'a' })
rule.append({ prop: 'color', value: 'black' })
rule.append({ prop: 'width', value: '1px' })
rule.append({ prop: 'height', value: '1px' })
let node = new Declaration({ prop: 'min-width', value: '1px' });
let width = rule.nodes[1];
let result = width.replaceWith(node);
const node = new Declaration({ prop: 'min-width', value: '1px' })
const width = rule.nodes[1]
const result = width.replaceWith(node)
expect(result).toEqual(width);
expect(rule.toString()).toEqual('a {\n' +
' color: black;\n' +
' min-width: 1px;\n' +
' height: 1px\n' +
'}');
});
expect(result).toEqual(width)
expect(rule.toString()).toEqual('a {\n' +
' color: black;\n' +
' min-width: 1px;\n' +
' height: 1px\n' +
'}')
})
it('replaceWith() inserts new root', () => {
let root = new Root();
root.append( new AtRule({ name: 'import', params: '"a.css"' }) );
const root = new Root()
root.append(new AtRule({ name: 'import', params: '"a.css"' }))
let a = new Root();
a.append( new Rule({ selector: 'a' }) );
a.append( new Rule({ selector: 'b' }) );
const a = new Root()
a.append(new Rule({ selector: 'a' }))
a.append(new Rule({ selector: 'b' }))
root.first.replaceWith(a);
expect(root.toString()).toEqual('a {}\nb {}');
});
root.first.replaceWith(a)
expect(root.toString()).toEqual('a {}\nb {}')
})
it('replaceWith() replaces node', () => {
let css = parse('a{one:1;two:2}');
let decl = { prop: 'fix', value: 'fixed' };
let result = css.first.first.replaceWith(decl);
const css = parse('a{one:1;two:2}')
const decl = { prop: 'fix', value: 'fixed' }
const result = css.first.first.replaceWith(decl)
expect(result.prop).toEqual('one');
expect(result.parent).not.toBeDefined();
expect(css.toString()).toEqual('a{fix:fixed;two:2}');
});
expect(result.prop).toEqual('one')
expect(result.parent).not.toBeDefined()
expect(css.toString()).toEqual('a{fix:fixed;two:2}')
})
it('toString() accepts custom stringifier', () => {
expect(new Rule({ selector: 'a' }).toString(stringify)).toEqual('a');
});
expect(new Rule({ selector: 'a' }).toString(stringify)).toEqual('a')
})
it('toString() accepts custom syntax', () => {
expect(new Rule({ selector: 'a' }).toString({ stringify })).toEqual('a');
});
expect(new Rule({ selector: 'a' }).toString({ stringify })).toEqual('a')
})
it('clone() clones nodes', () => {
let rule = new Rule({ selector: 'a' });
rule.append({ prop: 'color', value: '/**/black' });
const rule = new Rule({ selector: 'a' })
rule.append({ prop: 'color', value: '/**/black' })
let clone = rule.clone();
const clone = rule.clone()
expect(clone.parent).not.toBeDefined();
expect(clone.parent).not.toBeDefined()
expect(rule.first.parent).toBe(rule);
expect(clone.first.parent).toBe(clone);
expect(rule.first.parent).toBe(rule)
expect(clone.first.parent).toBe(clone)
clone.append({ prop: 'z-index', value: '1' });
expect(rule.nodes.length).toEqual(1);
});
clone.append({ prop: 'z-index', value: '1' })
expect(rule.nodes).toHaveLength(1)
})
it('clone() overrides properties', () => {
let rule = new Rule({ selector: 'a' });
let clone = rule.clone({ selector: 'b' });
expect(clone.selector).toEqual('b');
});
const rule = new Rule({ selector: 'a' })
const clone = rule.clone({ selector: 'b' })
expect(clone.selector).toEqual('b')
})
it('clone() keeps code style', () => {
let css = parse('@page 1{a{color:black;}}');
expect(css.clone().toString()).toEqual('@page 1{a{color:black;}}');
});
const css = parse('@page 1{a{color:black;}}')
expect(css.clone().toString()).toEqual('@page 1{a{color:black;}}')
})
it('clone() works with null in raws', () => {
let decl = new Declaration({
prop: 'color',
value: 'black',
raws: { value: null }
});
let clone = decl.clone();
expect(Object.keys(clone.raws)).toEqual(['value']);
});
const decl = new Declaration({
prop: 'color',
value: 'black',
raws: { value: null }
})
const clone = decl.clone()
expect(Object.keys(clone.raws)).toEqual(['value'])
})
it('cloneBefore() clones and insert before current node', () => {
let rule = new Rule({ selector: 'a', raws: { after: '' } });
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } });
const rule = new Rule({ selector: 'a', raws: { after: '' } })
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } })
let result = rule.first.cloneBefore({ value: '2' });
const result = rule.first.cloneBefore({ value: '2' })
expect(result).toBe(rule.first);
expect(rule.toString()).toEqual('a {z-index: 2;z-index: 1}');
});
expect(result).toBe(rule.first)
expect(rule.toString()).toEqual('a {z-index: 2;z-index: 1}')
})
it('cloneAfter() clones and insert after current node', () => {
let rule = new Rule({ selector: 'a', raws: { after: '' } });
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } });
const rule = new Rule({ selector: 'a', raws: { after: '' } })
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } })
let result = rule.first.cloneAfter({ value: '2' });
const result = rule.first.cloneAfter({ value: '2' })
expect(result).toBe(rule.last);
expect(rule.toString()).toEqual('a {z-index: 1;z-index: 2}');
});
expect(result).toBe(rule.last)
expect(rule.toString()).toEqual('a {z-index: 1;z-index: 2}')
})
it('before() insert before current node', () => {
let rule = new Rule({ selector: 'a', raws: { after: '' } });
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } });
const rule = new Rule({ selector: 'a', raws: { after: '' } })
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } })
let result = rule.first.before('color: black');
const result = rule.first.before('color: black')
expect(result).toBe(rule.last);
expect(rule.toString()).toEqual('a {color: black;z-index: 1}');
});
expect(result).toBe(rule.last)
expect(rule.toString()).toEqual('a {color: black;z-index: 1}')
})
it('after() insert after current node', () => {
let rule = new Rule({ selector: 'a', raws: { after: '' } });
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } });
const rule = new Rule({ selector: 'a', raws: { after: '' } })
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } })
let result = rule.first.after('color: black');
const result = rule.first.after('color: black')
expect(result).toBe(rule.first);
expect(rule.toString()).toEqual('a {z-index: 1;color: black}');
});
expect(result).toBe(rule.first)
expect(rule.toString()).toEqual('a {z-index: 1;color: black}')
})
it('next() returns next node', () => {
let css = parse('a{one:1;two:2}');
expect(css.first.first.next()).toBe(css.first.last);
expect(css.first.last.next()).not.toBeDefined();
});
const css = parse('a{one:1;two:2}')
expect(css.first.first.next()).toBe(css.first.last)
expect(css.first.last.next()).not.toBeDefined()
})
it('next() returns undefined on no parent', () => {
let css = parse('');
expect(css.next()).not.toBeDefined();
});
const css = parse('')
expect(css.next()).not.toBeDefined()
})
it('prev() returns previous node', () => {
let css = parse('a{one:1;two:2}');
expect(css.first.last.prev()).toBe(css.first.first);
expect(css.first.first.prev()).not.toBeDefined();
});
const css = parse('a{one:1;two:2}')
expect(css.first.last.prev()).toBe(css.first.first)
expect(css.first.first.prev()).not.toBeDefined()
})
it('prev() returns undefined on no parent', () => {
let css = parse('');
expect(css.prev()).not.toBeDefined();
});
const css = parse('')
expect(css.prev()).not.toBeDefined()
})
it('toJSON() cleans parents inside', () => {
let rule = new Rule({ selector: 'a' });
rule.append({ prop: 'color', value: 'b' });
const rule = new Rule({ selector: 'a' })
rule.append({ prop: 'color', value: 'b' })
let json = rule.toJSON();
expect(json.parent).not.toBeDefined();
expect(json.nodes[0].parent).not.toBeDefined();
const json = rule.toJSON()
expect(json.parent).not.toBeDefined()
expect(json.nodes[0].parent).not.toBeDefined()
expect(JSON.stringify(rule))
.toEqual('{"raws":{},"selector":"a","type":"rule","nodes":[' +
'{"raws":{},"prop":"color","value":"b","type":"decl"}' +
']}');
});
expect(JSON.stringify(rule))
.toEqual('{"raws":{},"selector":"a","type":"rule","nodes":[' +
'{"raws":{},"prop":"color","value":"b","type":"decl"}' +
']}')
})
it('toJSON() converts custom properties', () => {
let root = new Root();
root._cache = [1];
root._hack = {
toJSON() {
return 'hack';
}
};
const root = new Root()
root._cache = [1]
root._hack = {
toJSON () {
return 'hack'
}
}
expect(root.toJSON()).toEqual({
type: 'root',
nodes: [],
raws: { },
_hack: 'hack',
_cache: [1]
});
});
expect(root.toJSON()).toEqual({
type: 'root',
nodes: [],
raws: { },
_hack: 'hack',
_cache: [1]
})
})
it('raw() has shortcut to stringifier', () => {
let rule = new Rule({ selector: 'a' });
expect(rule.raw('before')).toEqual('');
});
const rule = new Rule({ selector: 'a' })
expect(rule.raw('before')).toEqual('')
})
it('root() returns root', () => {
let css = parse('@page{a{color:black}}');
expect(css.first.first.first.root()).toBe(css);
});
const css = parse('@page{a{color:black}}')
expect(css.first.first.first.root()).toBe(css)
})
it('root() returns parent of parents', () => {
let rule = new Rule({ selector: 'a' });
rule.append({ prop: 'color', value: 'black' });
expect(rule.first.root()).toBe(rule);
});
const rule = new Rule({ selector: 'a' })
rule.append({ prop: 'color', value: 'black' })
expect(rule.first.root()).toBe(rule)
})
it('root() returns self on root', () => {
let rule = new Rule({ selector: 'a' });
expect(rule.root()).toBe(rule);
});
const rule = new Rule({ selector: 'a' })
expect(rule.root()).toBe(rule)
})
it('cleanRaws() cleans style recursivelly', () => {
let css = parse('@page{a{color:black}}');
css.cleanRaws();
const css = parse('@page{a{color:black}}')
css.cleanRaws()
expect(css.toString())
.toEqual('@page {\n a {\n color: black\n }\n}');
expect(css.first.raws.before).not.toBeDefined();
expect(css.first.first.first.raws.before).not.toBeDefined();
expect(css.first.raws.between).not.toBeDefined();
expect(css.first.first.first.raws.between).not.toBeDefined();
expect(css.first.raws.after).not.toBeDefined();
});
expect(css.toString())
.toEqual('@page {\n a {\n color: black\n }\n}')
expect(css.first.raws.before).not.toBeDefined()
expect(css.first.first.first.raws.before).not.toBeDefined()
expect(css.first.raws.between).not.toBeDefined()
expect(css.first.first.first.raws.between).not.toBeDefined()
expect(css.first.raws.after).not.toBeDefined()
})
it('cleanRaws() keeps between on request', () => {
let css = parse('@page{a{color:black}}');
css.cleanRaws(true);
const css = parse('@page{a{color:black}}')
css.cleanRaws(true)
expect(css.toString())
.toEqual('@page{\n a{\n color:black\n }\n}');
expect(css.first.raws.between).toBeDefined();
expect(css.first.first.first.raws.between).toBeDefined();
expect(css.first.raws.before).not.toBeDefined();
expect(css.first.first.first.raws.before).not.toBeDefined();
expect(css.first.raws.after).not.toBeDefined();
});
expect(css.toString())
.toEqual('@page{\n a{\n color:black\n }\n}')
expect(css.first.raws.between).toBeDefined()
expect(css.first.first.first.raws.between).toBeDefined()
expect(css.first.raws.before).not.toBeDefined()
expect(css.first.first.first.raws.before).not.toBeDefined()
expect(css.first.raws.after).not.toBeDefined()
})
it('positionInside() returns position when node starts mid-line', () => {
let css = parse('a { one: X }');
let one = css.first.first;
expect(one.positionInside(6)).toEqual({ line: 1, column: 12 });
});
const css = parse('a { one: X }')
const one = css.first.first
expect(one.positionInside(6)).toEqual({ line: 1, column: 12 })
})
it('positionInside() returns position when before contains newline', () => {
let css = parse('a {\n one: X}');
let one = css.first.first;
expect(one.positionInside(6)).toEqual({ line: 2, column: 9 });
});
const css = parse('a {\n one: X}')
const one = css.first.first
expect(one.positionInside(6)).toEqual({ line: 2, column: 9 })
})
it('positionInside() returns position when node contains newlines', () => {
let css = parse('a {\n\tone: 1\n\t\tX\n3}');
let one = css.first.first;
expect(one.positionInside(10)).toEqual({ line: 3, column: 4 });
});
const css = parse('a {\n\tone: 1\n\t\tX\n3}')
const one = css.first.first
expect(one.positionInside(10)).toEqual({ line: 3, column: 4 })
})
+119 -121
View File
@@ -1,171 +1,169 @@
'use strict';
const parse = require('../lib/parse')
const Root = require('../lib/root')
const parse = require('../lib/parse');
const Root = require('../lib/root');
const cases = require('postcss-parser-tests');
const path = require('path');
const fs = require('fs');
const cases = require('postcss-parser-tests')
const path = require('path')
const fs = require('fs')
it('works with file reads', () => {
let stream = fs.readFileSync(cases.path('atrule-empty.css'));
expect(parse(stream) instanceof Root).toBeTruthy();
});
const stream = fs.readFileSync(cases.path('atrule-empty.css'))
expect(parse(stream) instanceof Root).toBeTruthy()
})
cases.each( (name, css, json) => {
it('parses ' + name, () => {
let parsed = cases.jsonify(parse(css, { from: name }));
expect(parsed).toEqual(json);
});
});
cases.each((name, css, json) => {
it('parses ' + name, () => {
const parsed = cases.jsonify(parse(css, { from: name }))
expect(parsed).toEqual(json)
})
})
it('saves source file', () => {
let css = parse('a {}', { from: 'a.css' });
expect(css.first.source.input.css).toEqual('a {}');
expect(css.first.source.input.file).toEqual(path.resolve('a.css'));
expect(css.first.source.input.from).toEqual(path.resolve('a.css'));
});
const css = parse('a {}', { from: 'a.css' })
expect(css.first.source.input.css).toEqual('a {}')
expect(css.first.source.input.file).toEqual(path.resolve('a.css'))
expect(css.first.source.input.from).toEqual(path.resolve('a.css'))
})
it('keeps absolute path in source', () => {
let css = parse('a {}', { from: 'http://example.com/a.css' });
expect(css.first.source.input.file).toEqual('http://example.com/a.css');
expect(css.first.source.input.from).toEqual('http://example.com/a.css');
});
const css = parse('a {}', { from: 'http://example.com/a.css' })
expect(css.first.source.input.file).toEqual('http://example.com/a.css')
expect(css.first.source.input.from).toEqual('http://example.com/a.css')
})
it('saves source file on previous map', () => {
let root1 = parse('a {}', { map: { inline: true } });
let css = root1.toResult({ map: { inline: true } }).css;
let root2 = parse(css);
expect(root2.first.source.input.file).toEqual(path.resolve('to.css'));
});
const root1 = parse('a {}', { map: { inline: true } })
const css = root1.toResult({ map: { inline: true } }).css
const root2 = parse(css)
expect(root2.first.source.input.file).toEqual(path.resolve('to.css'))
})
it('sets unique ID for file without name', () => {
let css1 = parse('a {}');
let css2 = parse('a {}');
expect(css1.first.source.input.id).toMatch(/^<input css \d+>$/);
expect(css1.first.source.input.from).toMatch(/^<input css \d+>$/);
expect(css2.first.source.input.id).not.toEqual(css1.first.source.input.id);
});
const css1 = parse('a {}')
const css2 = parse('a {}')
expect(css1.first.source.input.id).toMatch(/^<input css \d+>$/)
expect(css1.first.source.input.from).toMatch(/^<input css \d+>$/)
expect(css2.first.source.input.id).not.toEqual(css1.first.source.input.id)
})
it('sets parent node', () => {
let file = cases.path('atrule-rules.css');
let css = parse(fs.readFileSync(file));
const file = cases.path('atrule-rules.css')
const css = parse(fs.readFileSync(file))
let support = css.first;
let keyframes = support.first;
let from = keyframes.first;
let decl = from.first;
const support = css.first
const keyframes = support.first
const from = keyframes.first
const decl = from.first
expect(decl.parent).toBe(from);
expect(from.parent).toBe(keyframes);
expect(support.parent).toBe(css);
expect(keyframes.parent).toBe(support);
});
expect(decl.parent).toBe(from)
expect(from.parent).toBe(keyframes)
expect(support.parent).toBe(css)
expect(keyframes.parent).toBe(support)
})
it('ignores wrong close bracket', () => {
let root = parse('a { p: ()) }');
expect(root.first.first.value).toEqual('())');
});
const root = parse('a { p: ()) }')
expect(root.first.first.value).toEqual('())')
})
it('ignores symbols before declaration', () => {
let root = parse('a { :one: 1 }');
expect(root.first.first.raws.before).toEqual(' :');
});
const root = parse('a { :one: 1 }')
expect(root.first.first.raws.before).toEqual(' :')
})
it('parses double semicolon after rule', () => {
expect(parse('a { };;').toString()).toEqual('a { };;');
});
expect(parse('a { };;').toString()).toEqual('a { };;')
})
it('throws on unclosed blocks', () => {
expect(() => {
parse('\na {\n');
}).toThrowError(/:2:1: Unclosed block/);
});
expect(() => {
parse('\na {\n')
}).toThrowError(/:2:1: Unclosed block/)
})
it('throws on unnecessary block close', () => {
expect(() => {
parse('a {\n} }');
}).toThrowError(/:2:3: Unexpected }/);
});
expect(() => {
parse('a {\n} }')
}).toThrowError(/:2:3: Unexpected }/)
})
it('throws on unclosed comment', () => {
expect(() => {
parse('\n/*\n ');
}).toThrowError(/:2:1: Unclosed comment/);
});
expect(() => {
parse('\n/*\n ')
}).toThrowError(/:2:1: Unclosed comment/)
})
it('throws on unclosed quote', () => {
expect(() => {
parse('\n"\n\na ');
}).toThrowError(/:2:1: Unclosed string/);
});
expect(() => {
parse('\n"\n\na ')
}).toThrowError(/:2:1: Unclosed string/)
})
it('throws on unclosed bracket', () => {
expect(() => {
parse(':not(one() { }');
}).toThrowError(/:1:5: Unclosed bracket/);
});
expect(() => {
parse(':not(one() { }')
}).toThrowError(/:1:5: Unclosed bracket/)
})
it('throws on property without value', () => {
expect(() => {
parse('a { b;}');
}).toThrowError(/:1:5: Unknown word/);
expect(() => {
parse('a { b b }');
}).toThrowError(/:1:5: Unknown word/);
});
expect(() => {
parse('a { b;}')
}).toThrowError(/:1:5: Unknown word/)
expect(() => {
parse('a { b b }')
}).toThrowError(/:1:5: Unknown word/)
})
it('throws on nameless at-rule', () => {
expect(() => {
parse('@');
}).toThrowError(/:1:1: At-rule without name/);
});
expect(() => {
parse('@')
}).toThrowError(/:1:1: At-rule without name/)
})
it('throws on property without semicolon', () => {
expect(() => {
parse('a { one: filter(a:"") two: 2 }');
}).toThrowError(/:1:21: Missed semicolon/);
});
expect(() => {
parse('a { one: filter(a:"") two: 2 }')
}).toThrowError(/:1:21: Missed semicolon/)
})
it('throws on double colon', () => {
expect(() => {
parse('a { one:: 1 }');
}).toThrowError(/:1:9: Double colon/);
});
expect(() => {
parse('a { one:: 1 }')
}).toThrowError(/:1:9: Double colon/)
})
it('throws on just colon', () => {
expect(() => {
parse(':');
}).toThrowError(/:1:1: Unknown word/);
expect(() => {
parse(' : ');
}).toThrowError(/:1:2: Unknown word/);
});
expect(() => {
parse(':')
}).toThrowError(/:1:1: Unknown word/)
expect(() => {
parse(' : ')
}).toThrowError(/:1:2: Unknown word/)
})
it('does not suggest different parsers for CSS', () => {
let error;
try {
parse('a { one:: 1 }', { from: 'app.css' });
} catch (e) {
error = e;
}
expect(error.message).not.toMatch(/postcss-less|postcss-scss/);
});
let error
try {
parse('a { one:: 1 }', { from: 'app.css' })
} catch (e) {
error = e
}
expect(error.message).not.toMatch(/postcss-less|postcss-scss/)
})
it('suggests postcss-scss for SCSS sources', () => {
expect(() => {
parse('a { #{var}: 1 }', { from: 'app.scss' });
}).toThrowError(/postcss-scss/);
});
expect(() => {
parse('a { #{var}: 1 }', { from: 'app.scss' })
}).toThrowError(/postcss-scss/)
})
it('suggests postcss-sass for Sass sources', () => {
expect(() => {
parse('a\n #{var}: 1', { from: 'app.sass' });
}).toThrowError(/postcss-sass/);
});
expect(() => {
parse('a\n #{var}: 1', { from: 'app.sass' })
}).toThrowError(/postcss-sass/)
})
it('suggests postcss-less for Less sources', () => {
expect(() => {
parse('.@{my-selector} { }', { from: 'app.less' });
}).toThrowError(/postcss-less/);
});
expect(() => {
parse('.@{my-selector} { }', { from: 'app.less' })
}).toThrowError(/postcss-less/)
})
+108 -110
View File
@@ -1,150 +1,148 @@
'use strict';
const Processor = require('../lib/processor');
const postcss = require('../lib/postcss');
const Processor = require('../lib/processor')
const postcss = require('../lib/postcss')
it('creates plugins list', () => {
let processor = postcss();
expect(processor instanceof Processor).toBeTruthy();
expect(processor.plugins).toEqual([]);
});
const processor = postcss()
expect(processor instanceof Processor).toBeTruthy()
expect(processor.plugins).toEqual([])
})
it('saves plugins list', () => {
let a = () => 1;
let b = () => 2;
expect(postcss(a, b).plugins).toEqual([a, b]);
});
const a = () => 1
const b = () => 2
expect(postcss(a, b).plugins).toEqual([a, b])
})
it('saves plugins list as array', () => {
let a = () => 1;
let b = () => 2;
expect(postcss([a, b]).plugins).toEqual([a, b]);
});
const a = () => 1
const b = () => 2
expect(postcss([a, b]).plugins).toEqual([a, b])
})
it('takes plugin from other processor', () => {
let a = () => 1;
let b = () => 2;
let c = () => 3;
let other = postcss([a, b]);
expect(postcss([other, c]).plugins).toEqual([a, b, c]);
});
const a = () => 1
const b = () => 2
const c = () => 3
const other = postcss([a, b])
expect(postcss([other, c]).plugins).toEqual([a, b, c])
})
it('supports injecting additional processors at runtime', () => {
let plugin1 = postcss.plugin('one', () => {
return css => {
css.walkDecls(decl => {
decl.value = 'world';
});
};
});
let plugin2 = postcss.plugin('two', () => {
return (css, result) => {
result.processor.use(plugin1());
};
});
const plugin1 = postcss.plugin('one', () => {
return css => {
css.walkDecls(decl => {
decl.value = 'world'
})
}
})
const plugin2 = postcss.plugin('two', () => {
return (css, result) => {
result.processor.use(plugin1())
}
})
return postcss([ plugin2 ]).process('a{hello: bob}', { from: undefined })
.then(result => {
expect(result.css).toEqual('a{hello: world}');
});
});
return postcss([plugin2]).process('a{hello: bob}', { from: undefined })
.then(result => {
expect(result.css).toEqual('a{hello: world}')
})
})
it('creates plugin', () => {
let plugin = postcss.plugin('test', filter => {
return root => {
root.walkDecls(filter || 'two', i => i.remove());
};
});
const plugin = postcss.plugin('test', filter => {
return root => {
root.walkDecls(filter || 'two', i => i.remove())
}
})
let func1 = postcss(plugin).plugins[0];
expect(func1.postcssPlugin).toEqual('test');
expect(func1.postcssVersion).toMatch(/\d+.\d+.\d+/);
const func1 = postcss(plugin).plugins[0]
expect(func1.postcssPlugin).toEqual('test')
expect(func1.postcssVersion).toMatch(/\d+.\d+.\d+/)
let func2 = postcss(plugin()).plugins[0];
expect(func2.postcssPlugin).toEqual(func1.postcssPlugin);
expect(func2.postcssVersion).toEqual(func1.postcssVersion);
const func2 = postcss(plugin()).plugins[0]
expect(func2.postcssPlugin).toEqual(func1.postcssPlugin)
expect(func2.postcssVersion).toEqual(func1.postcssVersion)
let result1 = postcss(plugin('one')).process('a{ one: 1; two: 2 }');
expect(result1.css).toEqual('a{ two: 2 }');
const result1 = postcss(plugin('one')).process('a{ one: 1; two: 2 }')
expect(result1.css).toEqual('a{ two: 2 }')
let result2 = postcss(plugin).process('a{ one: 1; two: 2 }');
expect(result2.css).toEqual('a{ one: 1 }');
});
const result2 = postcss(plugin).process('a{ one: 1; two: 2 }')
expect(result2.css).toEqual('a{ one: 1 }')
})
it('does not call plugin constructor', () => {
let calls = 0;
let plugin = postcss.plugin('test', () => {
calls += 1;
return () => { };
});
expect(calls).toBe(0);
let calls = 0
const plugin = postcss.plugin('test', () => {
calls += 1
return () => { }
})
expect(calls).toBe(0)
postcss(plugin).process('a{}');
expect(calls).toBe(1);
postcss(plugin).process('a{}')
expect(calls).toBe(1)
postcss(plugin()).process('a{}');
expect(calls).toBe(2);
});
postcss(plugin()).process('a{}')
expect(calls).toBe(2)
})
it('creates a shortcut to process css', () => {
let plugin = postcss.plugin('test', str => {
return root => {
root.walkDecls(i => {
i.value = str || 'bar';
});
};
});
const plugin = postcss.plugin('test', str => {
return root => {
root.walkDecls(i => {
i.value = str || 'bar'
})
}
})
let result1 = plugin.process('a{value:foo}');
expect(result1.css).toEqual('a{value:bar}');
const result1 = plugin.process('a{value:foo}')
expect(result1.css).toEqual('a{value:bar}')
let result2 = plugin.process('a{value:foo}', { }, 'baz');
expect(result2.css).toEqual('a{value:baz}');
const result2 = plugin.process('a{value:foo}', { }, 'baz')
expect(result2.css).toEqual('a{value:baz}')
plugin.process('a{value:foo}', { from: 'a' }, 'baz').then(result => {
expect(result.opts).toEqual({ from: 'a' });
expect(result.css).toEqual('a{value:baz}');
});
});
return plugin.process('a{value:foo}', { from: 'a' }, 'baz').then(result => {
expect(result.opts).toEqual({ from: 'a' })
expect(result.css).toEqual('a{value:baz}')
})
})
it('contains parser', () => {
expect(postcss.parse('').type).toEqual('root');
});
expect(postcss.parse('').type).toEqual('root')
})
it('contains stringifier', () => {
expect(typeof postcss.stringify).toEqual('function');
});
expect(typeof postcss.stringify).toEqual('function')
})
it('allows to build own CSS', () => {
let root = postcss.root({ raws: { after: '\n' } });
let comment = postcss.comment({ text: 'Example' });
let media = postcss.atRule({ name: 'media', params: 'screen' });
let rule = postcss.rule({ selector: 'a' });
let decl = postcss.decl({ prop: 'color', value: 'black' });
const root = postcss.root({ raws: { after: '\n' } })
const comment = postcss.comment({ text: 'Example' })
const media = postcss.atRule({ name: 'media', params: 'screen' })
const rule = postcss.rule({ selector: 'a' })
const decl = postcss.decl({ prop: 'color', value: 'black' })
root.append(comment);
rule.append(decl);
media.append(rule);
root.append(media);
root.append(comment)
rule.append(decl)
media.append(rule)
root.append(media)
expect(root.toString()).toEqual('/* Example */\n' +
'@media screen {\n' +
' a {\n' +
' color: black\n' +
' }\n' +
'}\n');
});
expect(root.toString()).toEqual('/* Example */\n' +
'@media screen {\n' +
' a {\n' +
' color: black\n' +
' }\n' +
'}\n')
})
it('contains vendor module', () => {
expect(postcss.vendor.prefix('-moz-tab')).toEqual('-moz-');
});
expect(postcss.vendor.prefix('-moz-tab')).toEqual('-moz-')
})
it('contains list module', () => {
expect(postcss.list.space('a b')).toEqual(['a', 'b']);
});
expect(postcss.list.space('a b')).toEqual(['a', 'b'])
})
it('works with null', () => {
expect(() => {
postcss().process(null).css;
}).toThrowError(/PostCSS received null instead of CSS string/);
});
expect(() => {
postcss().process(null).css
}).toThrowError(/PostCSS received null instead of CSS string/)
})
+140 -142
View File
@@ -1,196 +1,194 @@
'use strict';
const parse = require('../lib/parse')
const parse = require('../lib/parse');
const mozilla = require('source-map')
const path = require('path')
const fs = require('fs-extra')
const mozilla = require('source-map');
const path = require('path');
const fs = require('fs-extra');
let dir = path.join(__dirname, 'prevmap-fixtures');
let mapObj = {
version: 3,
file: null,
sources: [],
names: [],
mappings: ''
};
let map = JSON.stringify(mapObj);
const dir = path.join(__dirname, 'prevmap-fixtures')
const mapObj = {
version: 3,
file: null,
sources: [],
names: [],
mappings: ''
}
const map = JSON.stringify(mapObj)
afterEach(() => {
if ( fs.existsSync(dir) ) fs.removeSync(dir);
});
if (fs.existsSync(dir)) fs.removeSync(dir)
})
it('misses property if no map', () => {
expect(parse('a{}').source.input.map).not.toBeDefined();
});
expect(parse('a{}').source.input.map).not.toBeDefined()
})
it('creates property if map present', () => {
let root = parse('a{}', { map: { prev: map } });
expect(root.source.input.map.text).toEqual(map);
});
const root = parse('a{}', { map: { prev: map } })
expect(root.source.input.map.text).toEqual(map)
})
it('returns consumer', () => {
let obj = parse('a{}', { map: { prev: map } }).source.input.map.consumer();
expect(obj instanceof mozilla.SourceMapConsumer).toBeTruthy();
});
const obj = parse('a{}', { map: { prev: map } }).source.input.map.consumer()
expect(obj instanceof mozilla.SourceMapConsumer).toBeTruthy()
})
it('sets annotation property', () => {
let mapOpts = { map: { prev: map } };
const mapOpts = { map: { prev: map } }
let root1 = parse('a{}', mapOpts);
expect(root1.source.input.map.annotation).not.toBeDefined();
const root1 = parse('a{}', mapOpts)
expect(root1.source.input.map.annotation).not.toBeDefined()
let root2 = parse('a{}/*# sourceMappingURL=a.css.map */', mapOpts);
expect(root2.source.input.map.annotation).toEqual('a.css.map');
});
const root2 = parse('a{}/*# sourceMappingURL=a.css.map */', mapOpts)
expect(root2.source.input.map.annotation).toEqual('a.css.map')
})
it('checks previous sources content', () => {
let map2 = {
version: 3,
file: 'b',
sources: ['a'],
names: [],
mappings: ''
};
const map2 = {
version: 3,
file: 'b',
sources: ['a'],
names: [],
mappings: ''
}
let opts = { map: { prev: map2 } };
expect(parse('a{}', opts).source.input.map.withContent()).toBe(false);
const opts = { map: { prev: map2 } }
expect(parse('a{}', opts).source.input.map.withContent()).toBe(false)
map2.sourcesContent = ['a{}'];
expect(parse('a{}', opts).source.input.map.withContent()).toBe(true);
});
map2.sourcesContent = ['a{}']
expect(parse('a{}', opts).source.input.map.withContent()).toBe(true)
})
it('decodes base64 maps', () => {
let b64 = new Buffer(map).toString('base64');
let css = 'a{}\n' +
`/*# sourceMappingURL=data:application/json;base64,${b64} */`;
const b64 = Buffer.from(map).toString('base64')
const css = 'a{}\n' +
`/*# sourceMappingURL=data:application/json;base64,${ b64 } */`
expect(parse(css).source.input.map.text).toEqual(map);
});
expect(parse(css).source.input.map.text).toEqual(map)
})
it('decodes base64 UTF-8 maps', () => {
let b64 = new Buffer(map).toString('base64');
let css = 'a{}\n/*# sourceMappingURL=data:application/json;' +
'charset=utf-8;base64,' + b64 + ' */';
const b64 = Buffer.from(map).toString('base64')
const css = 'a{}\n/*# sourceMappingURL=data:application/json;' +
'charset=utf-8;base64,' + b64 + ' */'
expect(parse(css).source.input.map.text).toEqual(map);
});
expect(parse(css).source.input.map.text).toEqual(map)
})
it('accepts different name for UTF-8 encoding', () => {
let b64 = new Buffer(map).toString('base64');
let css = 'a{}\n/*# sourceMappingURL=data:application/json;' +
'charset=utf8;base64,' + b64 + ' */';
const b64 = Buffer.from(map).toString('base64')
const css = 'a{}\n/*# sourceMappingURL=data:application/json;' +
'charset=utf8;base64,' + b64 + ' */'
expect(parse(css).source.input.map.text).toEqual(map);
});
expect(parse(css).source.input.map.text).toEqual(map)
})
it('decodes URI maps', () => {
let uri = 'data:application/json,' + decodeURI(map);
let css = `a{}\n/*# sourceMappingURL=${ uri } */`;
const uri = 'data:application/json,' + decodeURI(map)
const css = `a{}\n/*# sourceMappingURL=${ uri } */`
expect(parse(css).source.input.map.text).toEqual(map);
});
expect(parse(css).source.input.map.text).toEqual(map)
})
it('removes map on request', () => {
let uri = 'data:application/json,' + decodeURI(map);
let css = `a{}\n/*# sourceMappingURL=${ uri } */`;
const uri = 'data:application/json,' + decodeURI(map)
const css = `a{}\n/*# sourceMappingURL=${ uri } */`
let input = parse(css, { map: { prev: false } }).source.input;
expect(input.map).not.toBeDefined();
});
const input = parse(css, { map: { prev: false } }).source.input
expect(input.map).not.toBeDefined()
})
it('raises on unknown inline encoding', () => {
let css = 'a { }\n/*# sourceMappingURL=data:application/json;' +
'md5,68b329da9893e34099c7d8ad5cb9c940*/';
const css = 'a { }\n/*# sourceMappingURL=data:application/json;' +
'md5,68b329da9893e34099c7d8ad5cb9c940*/'
expect(() => {
parse(css);
}).toThrowError('Unsupported source map encoding md5');
});
expect(() => {
parse(css)
}).toThrowError('Unsupported source map encoding md5')
})
it('raises on unknown map format', () => {
expect(() => {
parse('a{}', { map: { prev: 1 } });
}).toThrowError('Unsupported previous source map format: 1');
});
expect(() => {
parse('a{}', { map: { prev: 1 } })
}).toThrowError('Unsupported previous source map format: 1')
})
it('reads map from annotation', () => {
let file = path.join(dir, 'a.map');
fs.outputFileSync(file, map);
let root = parse('a{}\n/*# sourceMappingURL=a.map */', { from: file });
const file = path.join(dir, 'a.map')
fs.outputFileSync(file, map)
const root = parse('a{}\n/*# sourceMappingURL=a.map */', { from: file })
expect(root.source.input.map.text).toEqual(map);
expect(root.source.input.map.root).toEqual(dir);
});
expect(root.source.input.map.text).toEqual(map)
expect(root.source.input.map.root).toEqual(dir)
})
it('sets uniq name for inline map', () => {
let map2 = {
version: 3,
sources: ['a'],
names: [],
mappings: ''
};
const map2 = {
version: 3,
sources: ['a'],
names: [],
mappings: ''
}
let opts = { map: { prev: map2 } };
let file1 = parse('a{}', opts).source.input.map.file;
let file2 = parse('a{}', opts).source.input.map.file;
const opts = { map: { prev: map2 } }
const file1 = parse('a{}', opts).source.input.map.file
const file2 = parse('a{}', opts).source.input.map.file
expect(file1).toMatch(/^<input css \d+>$/);
expect(file1).not.toEqual(file2);
});
expect(file1).toMatch(/^<input css \d+>$/)
expect(file1).not.toEqual(file2)
})
it('accepts an empty mappings string', () => {
let emptyMap = {
version: 3,
sources: [],
names: [],
mappings: ''
};
parse('body{}', { map: { prev: emptyMap } } );
});
const emptyMap = {
version: 3,
sources: [],
names: [],
mappings: ''
}
parse('body{}', { map: { prev: emptyMap } })
})
it('accepts a function', () => {
let css = 'body{}\n/*# sourceMappingURL=a.map */';
let file = path.join(dir, 'previous-sourcemap-function.map');
fs.outputFileSync(file, map);
let opts = {
map: {
prev: () => file
}
};
let root = parse(css, opts);
expect(root.source.input.map.text).toEqual(map);
expect(root.source.input.map.annotation).toEqual('a.map');
});
const css = 'body{}\n/*# sourceMappingURL=a.map */'
const file = path.join(dir, 'previous-sourcemap-function.map')
fs.outputFileSync(file, map)
const opts = {
map: {
prev: () => file
}
}
const root = parse(css, opts)
expect(root.source.input.map.text).toEqual(map)
expect(root.source.input.map.annotation).toEqual('a.map')
})
it('calls function with opts.from', () => {
expect.assertions(1);
expect.assertions(1)
let css = 'body{}\n/*# sourceMappingURL=a.map */';
let file = path.join(dir, 'previous-sourcemap-function.map');
fs.outputFileSync(file, map);
let opts = {
from: 'a.css',
map: {
prev: from => {
expect(from).toEqual('a.css');
return file;
}
}
};
parse(css, opts);
});
const css = 'body{}\n/*# sourceMappingURL=a.map */'
const file = path.join(dir, 'previous-sourcemap-function.map')
fs.outputFileSync(file, map)
const opts = {
from: 'a.css',
map: {
prev: from => {
expect(from).toEqual('a.css')
return file
}
}
}
parse(css, opts)
})
it('raises when function returns invalid path', () => {
let css = 'body{}\n/*# sourceMappingURL=a.map */';
let fakeMap = Number.MAX_SAFE_INTEGER.toString() + '.map';
let fakePath = path.join(dir, fakeMap);
let opts = {
map: {
prev: () => fakePath
}
};
expect(() => {
parse(css, opts);
}).toThrowError('Unable to load previous source map: ' + fakePath);
});
const css = 'body{}\n/*# sourceMappingURL=a.map */'
const fakeMap = Number.MAX_SAFE_INTEGER.toString() + '.map'
const fakePath = path.join(dir, fakeMap)
const opts = {
map: {
prev: () => fakePath
}
}
expect(() => {
parse(css, opts)
}).toThrowError('Unable to load previous source map: ' + fakePath)
})
+365 -366
View File
@@ -1,469 +1,468 @@
'use strict';
const LazyResult = require('../lib/lazy-result')
const Processor = require('../lib/processor')
const postcss = require('../lib/postcss')
const Result = require('../lib/result')
const parse = require('../lib/parse')
const Root = require('../lib/root')
const LazyResult = require('../lib/lazy-result');
const Processor = require('../lib/processor');
const postcss = require('../lib/postcss');
const Result = require('../lib/result');
const parse = require('../lib/parse');
const Root = require('../lib/root');
const path = require('path')
const path = require('path');
function prs() {
return new Root({ raws: { after: 'ok' } });
function prs () {
return new Root({ raws: { after: 'ok' } })
}
function str(node, builder) {
builder(node.raws.after + '!');
function str (node, builder) {
builder(node.raws.after + '!')
}
let beforeFix = new Processor([ css => {
css.walkRules( rule => {
if ( !rule.selector.match(/::(before|after)/) ) return;
if ( !rule.some( i => i.prop === 'content' ) ) {
rule.prepend({ prop: 'content', value: '""' });
}
});
}]);
const beforeFix = new Processor([css => {
css.walkRules(rule => {
if (!rule.selector.match(/::(before|after)/)) return
if (!rule.some(i => i.prop === 'content')) {
rule.prepend({ prop: 'content', value: '""' })
}
})
}])
const originWarn = console.warn;
const originError = console.error;
const originWarn = console.warn
const originError = console.error
afterAll(() => {
console.warn = originWarn;
console.error = originError;
});
console.warn = originWarn
console.error = originError
})
it('adds new plugins', () => {
let a = () => 1;
let processor = new Processor();
processor.use(a);
expect(processor.plugins).toEqual([a]);
});
const a = () => 1
const processor = new Processor()
processor.use(a)
expect(processor.plugins).toEqual([a])
})
it('adds new plugin by object', () => {
let a = () => 1;
let processor = new Processor();
processor.use({ postcss: a });
expect(processor.plugins).toEqual([a]);
});
const a = () => 1
const processor = new Processor()
processor.use({ postcss: a })
expect(processor.plugins).toEqual([a])
})
it('adds new plugin by object-function', () => {
let a = () => 1;
let obj = () => 2;
obj.postcss = a;
let processor = new Processor();
processor.use(obj);
expect(processor.plugins).toEqual([a]);
});
const a = () => 1
const obj = () => 2
obj.postcss = a
const processor = new Processor()
processor.use(obj)
expect(processor.plugins).toEqual([a])
})
it('adds new processors of another postcss instance', () => {
let a = () => 1;
let processor = new Processor();
let other = new Processor([a]);
processor.use(other);
expect(processor.plugins).toEqual([a]);
});
const a = () => 1
const processor = new Processor()
const other = new Processor([a])
processor.use(other)
expect(processor.plugins).toEqual([a])
})
it('adds new processors from object', () => {
let a = () => 1;
let processor = new Processor();
let other = new Processor([a]);
processor.use({ postcss: other });
expect(processor.plugins).toEqual([a]);
});
const a = () => 1
const processor = new Processor()
const other = new Processor([a])
processor.use({ postcss: other })
expect(processor.plugins).toEqual([a])
})
it('returns itself', () => {
let a = () => 1;
let b = () => 2;
let processor = new Processor();
expect(processor.use(a).use(b).plugins).toEqual([a, b]);
});
const a = () => 1
const b = () => 2
const processor = new Processor()
expect(processor.use(a).use(b).plugins).toEqual([a, b])
})
it('throws on wrong format', () => {
let pr = new Processor();
expect(() => {
pr.use(1);
}).toThrowError(/1 is not a PostCSS plugin/);
});
const pr = new Processor()
expect(() => {
pr.use(1)
}).toThrowError(/1 is not a PostCSS plugin/)
})
it('processes CSS', () => {
let result = beforeFix.process('a::before{top:0}');
expect(result.css).toEqual('a::before{content:"";top:0}');
});
const result = beforeFix.process('a::before{top:0}')
expect(result.css).toEqual('a::before{content:"";top:0}')
})
it('processes parsed AST', () => {
let root = parse('a::before{top:0}');
let result = beforeFix.process(root);
expect(result.css).toEqual('a::before{content:"";top:0}');
});
const root = parse('a::before{top:0}')
const result = beforeFix.process(root)
expect(result.css).toEqual('a::before{content:"";top:0}')
})
it('processes previous result', () => {
let result = (new Processor()).process('a::before{top:0}');
result = beforeFix.process(result);
expect(result.css).toEqual('a::before{content:"";top:0}');
});
let result = (new Processor()).process('a::before{top:0}')
result = beforeFix.process(result)
expect(result.css).toEqual('a::before{content:"";top:0}')
})
it('takes maps from previous result', () => {
let one = (new Processor()).process('a{}', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
let two = (new Processor()).process(one, { to: 'c.css' });
expect(two.map.toJSON().sources).toEqual(['a.css']);
});
const one = (new Processor()).process('a{}', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
const two = (new Processor()).process(one, { to: 'c.css' })
expect(two.map.toJSON().sources).toEqual(['a.css'])
})
it('inlines maps from previous result', () => {
let one = (new Processor()).process('a{}', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
let two = (new Processor()).process(one, {
to: 'c.css',
map: { inline: true }
});
expect(two.map).not.toBeDefined();
});
const one = (new Processor()).process('a{}', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
const two = (new Processor()).process(one, {
to: 'c.css',
map: { inline: true }
})
expect(two.map).not.toBeDefined()
})
it('throws with file name', () => {
let error;
try {
(new Processor()).process('a {', { from: 'a.css' }).css;
} catch (e) {
if ( e.name === 'CssSyntaxError' ) {
error = e;
} else {
throw e;
}
let error
try {
(new Processor()).process('a {', { from: 'a.css' }).css
} catch (e) {
if (e.name === 'CssSyntaxError') {
error = e
} else {
throw e
}
}
expect(error.file).toEqual(path.resolve('a.css'));
expect(error.message).toMatch(/a.css:1:1: Unclosed block$/);
});
expect(error.file).toEqual(path.resolve('a.css'))
expect(error.message).toMatch(/a.css:1:1: Unclosed block$/)
})
it('allows to replace Root', () => {
let plugin = (css, result) => {
result.root = new Root();
};
let processor = new Processor([plugin]);
expect(processor.process('a {}').css).toEqual('');
});
const plugin = (css, result) => {
result.root = new Root()
}
const processor = new Processor([plugin])
expect(processor.process('a {}').css).toEqual('')
})
it('returns LazyResult object', () => {
let result = (new Processor()).process('a{}');
expect(result instanceof LazyResult).toBeTruthy();
expect(result.css).toEqual('a{}');
expect(result.toString()).toEqual('a{}');
});
const result = (new Processor()).process('a{}')
expect(result instanceof LazyResult).toBeTruthy()
expect(result.css).toEqual('a{}')
expect(result.toString()).toEqual('a{}')
})
it('calls all plugins once', () => {
expect.assertions(1);
expect.assertions(1)
let calls = '';
let a = () => {
calls += 'a';
};
let b = () => {
calls += 'b';
};
let calls = ''
const a = () => {
calls += 'a'
}
const b = () => {
calls += 'b'
}
let result = new Processor([a, b]).process('', { from: undefined });
result.css;
result.map;
result.root;
return result.then( () => {
expect(calls).toEqual('ab');
});
});
const result = new Processor([a, b]).process('', { from: undefined })
result.css
result.map
result.root
return result.then(() => {
expect(calls).toEqual('ab')
})
})
it('parses, converts and stringifies CSS', () => {
let a = css => expect(css instanceof Root).toBeTruthy();
expect(typeof (new Processor([a])).process('a {}').css).toEqual('string');
});
const a = css => expect(css instanceof Root).toBeTruthy()
expect(typeof (new Processor([a])).process('a {}').css).toEqual('string')
})
it('send result to plugins', () => {
expect.assertions(4);
let processor = new Processor();
let a = (css, result) => {
expect(result instanceof Result).toBeTruthy();
expect(result.processor).toEqual(processor);
expect(result.opts).toEqual({ map: true });
expect(result.root).toEqual(css);
};
return processor.use(a).process('a {}', { map: true, from: undefined });
});
expect.assertions(4)
const processor = new Processor()
const a = (css, result) => {
expect(result instanceof Result).toBeTruthy()
expect(result.processor).toEqual(processor)
expect(result.opts).toEqual({ map: true })
expect(result.root).toEqual(css)
}
return processor.use(a).process('a {}', { map: true, from: undefined })
})
it('accepts source map from PostCSS', () => {
let one = (new Processor()).process('a{}', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
});
let two = (new Processor()).process(one.css, {
from: 'b.css',
to: 'c.css',
map: { prev: one.map, inline: false }
});
expect(two.map.toJSON().sources).toEqual(['a.css']);
});
const one = (new Processor()).process('a{}', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
const two = (new Processor()).process(one.css, {
from: 'b.css',
to: 'c.css',
map: { prev: one.map, inline: false }
})
expect(two.map.toJSON().sources).toEqual(['a.css'])
})
it('supports async plugins', () => {
let starts = 0;
let finish = 0;
let async1 = css => {
return new Promise(resolve => {
starts += 1;
setTimeout(() => {
expect(starts).toEqual(1);
let starts = 0
let finish = 0
const async1 = css => {
return new Promise(resolve => {
starts += 1
setTimeout(() => {
expect(starts).toEqual(1)
css.append('a {}');
finish += 1;
resolve();
}, 1);
});
};
let async2 = css => {
return new Promise(resolve => {
expect(starts).toEqual(1);
expect(finish).toEqual(1);
css.append('a {}')
finish += 1
resolve()
}, 1)
})
}
const async2 = css => {
return new Promise(resolve => {
expect(starts).toEqual(1)
expect(finish).toEqual(1)
starts += 1;
setTimeout(() => {
css.append('b {}');
finish += 1;
resolve();
}, 1);
});
};
return (new Processor([async1, async2])).process('', { from: undefined })
.then( result => {
expect(starts).toEqual(2);
expect(finish).toEqual(2);
expect(result.css).toEqual('a {}b {}');
});
});
starts += 1
setTimeout(() => {
css.append('b {}')
finish += 1
resolve()
}, 1)
})
}
return (new Processor([async1, async2])).process('', { from: undefined })
.then(result => {
expect(starts).toEqual(2)
expect(finish).toEqual(2)
expect(result.css).toEqual('a {}b {}')
})
})
it('works async without plugins', () => {
return (new Processor()).process('a {}', { from: undefined })
.then( result => {
expect(result.css).toEqual('a {}');
});
});
return (new Processor()).process('a {}', { from: undefined })
.then(result => {
expect(result.css).toEqual('a {}')
})
})
it('runs async plugin only once', () => {
expect.assertions(1);
expect.assertions(1)
let calls = 0;
let async = () => {
return new Promise( resolve => {
setTimeout(() => {
calls += 1;
resolve();
}, 1);
});
};
let calls = 0
const async = () => {
return new Promise(resolve => {
setTimeout(() => {
calls += 1
resolve()
}, 1)
})
}
let result = (new Processor([async])).process('a {}', { from: undefined });
result.then( () => { });
return result.then( () => {
return result.then( () => {
expect(calls).toEqual(1);
});
});
});
const result = (new Processor([async])).process('a {}', { from: undefined })
result.then(() => { })
return result.then(() => {
return result.then(() => {
expect(calls).toEqual(1)
})
})
})
it('supports async errors', done => {
let error = new Error('Async');
let async = () => {
return new Promise( (resolve, reject) => {
reject(error);
});
};
let result = (new Processor([async])).process('', { from: undefined });
result.then( () => {
done.fail();
}).catch( err => {
expect(err).toEqual(error);
return result.catch( err2 => {
expect(err2).toEqual(error);
done();
});
});
});
const error = new Error('Async')
const async = () => {
return new Promise((resolve, reject) => {
reject(error)
})
}
const result = (new Processor([async])).process('', { from: undefined })
result.then(() => {
done.fail()
}).catch(err => {
expect(err).toEqual(error)
return result.catch(err2 => {
expect(err2).toEqual(error)
done()
})
})
})
it('supports sync errors in async mode', done => {
let error = new Error('Async');
let async = () => {
throw error;
};
(new Processor([async])).process('', { from: undefined }).then( () => {
done.fail();
}).catch( err => {
expect(err).toEqual(error);
done();
});
});
const error = new Error('Async')
const async = () => {
throw error
};
(new Processor([async])).process('', { from: undefined }).then(() => {
done.fail()
}).catch(err => {
expect(err).toEqual(error)
done()
})
})
it('throws parse error in async', () => {
return (new Processor()).process('a{').catch( err => {
expect(err.message).toEqual('<css input>:1:1: Unclosed block');
});
});
return (new Processor()).process('a{').catch(err => {
expect(err.message).toEqual('<css input>:1:1: Unclosed block')
})
})
it('throws error on sync method to async plugin', () => {
let async = () => {
return new Promise( resolve => resolve() );
};
expect(() => {
(new Processor([async])).process('a{}').css;
}).toThrowError(/async/);
});
const async = () => {
return new Promise(resolve => resolve())
}
expect(() => {
(new Processor([async])).process('a{}').css
}).toThrowError(/async/)
})
it('throws a sync call in async running', () => {
let async = () => new Promise( done => setTimeout(done, 1) );
const async = () => new Promise(resolve => setTimeout(resolve, 1))
let processor = (new Processor([async])).process('a{}');
processor.async();
const processor = (new Processor([async])).process('a{}')
processor.async()
expect(() => {
processor.sync();
}).toThrowError(/then/);
});
expect(() => {
processor.sync()
}).toThrowError(/then/)
})
it('checks plugin compatibility', () => {
let plugin = postcss.plugin('test', () => {
return () => {
throw new Error('Er');
};
});
let func = plugin();
func.postcssVersion = '2.1.5';
const plugin = postcss.plugin('test', () => {
return () => {
throw new Error('Er')
}
})
const func = plugin()
func.postcssVersion = '2.1.5'
let processBy = version => {
let processor = new Processor([func]);
processor.version = version;
processor.process('a{}').css;
};
const processBy = version => {
const processor = new Processor([func])
processor.version = version
processor.process('a{}').css
}
console.error = jest.fn();
console.error = jest.fn()
expect(() => {
processBy('1.0.0');
}).toThrowError('Er');
expect(console.error.mock.calls.length).toEqual(1);
expect(console.error.mock.calls[0][0]).toEqual(
'Unknown error from PostCSS plugin. ' +
'Your current PostCSS version is 1.0.0, but test uses 2.1.5. ' +
'Perhaps this is the source of the error below.');
expect(() => {
processBy('1.0.0')
}).toThrowError('Er')
expect(console.error.mock.calls).toHaveLength(1)
expect(console.error.mock.calls[0][0]).toEqual(
'Unknown error from PostCSS plugin. ' +
'Your current PostCSS version is 1.0.0, but test uses 2.1.5. ' +
'Perhaps this is the source of the error below.'
)
expect(() => {
processBy('3.0.0');
}).toThrowError('Er');
expect(console.error.mock.calls.length).toEqual(2);
expect(() => {
processBy('3.0.0')
}).toThrowError('Er')
expect(console.error.mock.calls).toHaveLength(2)
expect(() => {
processBy('2.0.0');
}).toThrowError('Er');
expect(console.error.mock.calls.length).toEqual(3);
expect(() => {
processBy('2.0.0')
}).toThrowError('Er')
expect(console.error.mock.calls).toHaveLength(3)
expect(() => {
processBy('2.1.0');
}).toThrowError('Er');
expect(console.error.mock.calls.length).toEqual(3);
});
expect(() => {
processBy('2.1.0')
}).toThrowError('Er')
expect(console.error.mock.calls).toHaveLength(3)
})
it('sets last plugin to result', () => {
let plugin1 = function (css, result) {
expect(result.lastPlugin).toBe(plugin1);
};
let plugin2 = function (css, result) {
expect(result.lastPlugin).toBe(plugin2);
};
const plugin1 = function (css, result) {
expect(result.lastPlugin).toBe(plugin1)
}
const plugin2 = function (css, result) {
expect(result.lastPlugin).toBe(plugin2)
}
let processor = new Processor([plugin1, plugin2]);
return processor.process('a{}', { from: undefined }).then( result => {
expect(result.lastPlugin).toBe(plugin2);
});
});
const processor = new Processor([plugin1, plugin2])
return processor.process('a{}', { from: undefined }).then(result => {
expect(result.lastPlugin).toBe(plugin2)
})
})
it('uses custom parsers', () => {
let processor = new Processor([]);
return processor.process('a{}', { parser: prs, from: undefined })
.then( result => {
expect(result.css).toEqual('ok');
});
});
const processor = new Processor([])
return processor.process('a{}', { parser: prs, from: undefined })
.then(result => {
expect(result.css).toEqual('ok')
})
})
it('uses custom parsers from object', () => {
let processor = new Processor([]);
let syntax = { parse: prs, stringify: str };
return processor.process('a{}', { parser: syntax, from: undefined })
.then( result => {
expect(result.css).toEqual('ok');
});
});
const processor = new Processor([])
const syntax = { parse: prs, stringify: str }
return processor.process('a{}', { parser: syntax, from: undefined })
.then(result => {
expect(result.css).toEqual('ok')
})
})
it('uses custom stringifier', () => {
let processor = new Processor([]);
return processor.process('a{}', { stringifier: str, from: undefined })
.then( result => {
expect(result.css).toEqual('!');
});
});
const processor = new Processor([])
return processor.process('a{}', { stringifier: str, from: undefined })
.then(result => {
expect(result.css).toEqual('!')
})
})
it('uses custom stringifier from object', () => {
let processor = new Processor([]);
let syntax = { parse: prs, stringify: str };
return processor.process('', { stringifier: syntax, from: undefined })
.then( result => {
expect(result.css).toEqual('!');
});
});
const processor = new Processor([])
const syntax = { parse: prs, stringify: str }
return processor.process('', { stringifier: syntax, from: undefined })
.then(result => {
expect(result.css).toEqual('!')
})
})
it('uses custom stringifier with source maps', () => {
let processor = new Processor([]);
return processor.process('a{}', {
map: true, stringifier: str, from: undefined
}).then( result => {
expect(result.css).toMatch(/!\n\/\*# sourceMap/);
});
});
const processor = new Processor([])
return processor.process('a{}', {
map: true, stringifier: str, from: undefined
}).then(result => {
expect(result.css).toMatch(/!\n\/\*# sourceMap/)
})
})
it('uses custom syntax', () => {
let processor = new Processor([]);
return processor.process('a{}', {
syntax: { parse: prs, stringify: str }, from: undefined
}).then( result => {
expect(result.css).toEqual('ok!');
});
});
const processor = new Processor([])
return processor.process('a{}', {
syntax: { parse: prs, stringify: str }, from: undefined
}).then(result => {
expect(result.css).toEqual('ok!')
})
})
it('contains PostCSS version', () => {
expect((new Processor()).version).toMatch(/\d+.\d+.\d+/);
});
expect((new Processor()).version).toMatch(/\d+.\d+.\d+/)
})
it('throws on syntax as plugin', () => {
let processor = new Processor();
expect(() => {
processor.use({
parse() { }
});
}).toThrowError(/syntax/);
});
const processor = new Processor()
expect(() => {
processor.use({
parse () { }
})
}).toThrowError(/syntax/)
})
it('warns about missed from', () => {
console.warn = jest.fn();
let processor = new Processor();
console.warn = jest.fn()
const processor = new Processor()
processor.process('a{}').css;
expect(console.warn).not.toBeCalled();
processor.process('a{}').css
expect(console.warn).not.toBeCalled()
return processor.process('a{}').then(() => {
expect(console.warn).toBeCalledWith(
'Without `from` option PostCSS could generate wrong source map ' +
'and will not find Browserslist config. Set it to CSS file path ' +
'or to `undefined` to prevent this warning.'
);
});
});
return processor.process('a{}').then(() => {
expect(console.warn).toBeCalledWith(
'Without `from` option PostCSS could generate wrong source map ' +
'and will not find Browserslist config. Set it to CSS file path ' +
'or to `undefined` to prevent this warning.'
)
})
})
+44 -46
View File
@@ -1,60 +1,58 @@
'use strict';
const Warning = require('../lib/warning');
const postcss = require('../lib/postcss');
const Result = require('../lib/result');
const Warning = require('../lib/warning')
const postcss = require('../lib/postcss')
const Result = require('../lib/result')
it('stringifies', () => {
let result = new Result();
result.css = 'a{}';
expect('' + result).toEqual(result.css);
});
const result = new Result()
result.css = 'a{}'
expect('' + result).toEqual(result.css)
})
it('adds warning', () => {
let warning;
let plugin = postcss.plugin('test-plugin', () => {
return (css, res) => {
warning = res.warn('test', { node: css.first });
};
});
let result = postcss([plugin]).process('a{}').sync();
let warning
const plugin = postcss.plugin('test-plugin', () => {
return (css, res) => {
warning = res.warn('test', { node: css.first })
}
})
const result = postcss([plugin]).process('a{}').sync()
expect(warning).toEqual(new Warning('test', {
plugin: 'test-plugin',
node: result.root.first
}));
expect(warning).toEqual(new Warning('test', {
plugin: 'test-plugin',
node: result.root.first
}))
expect(result.messages).toEqual([warning]);
});
expect(result.messages).toEqual([warning])
})
it('allows to override plugin', () => {
let plugin = postcss.plugin('test-plugin', () => {
return (css, res) => {
res.warn('test', { plugin: 'test-plugin#one' });
};
});
let result = postcss([plugin]).process('a{}').sync();
const plugin = postcss.plugin('test-plugin', () => {
return (css, res) => {
res.warn('test', { plugin: 'test-plugin#one' })
}
})
const result = postcss([plugin]).process('a{}').sync()
expect(result.messages[0].plugin).toEqual('test-plugin#one');
});
expect(result.messages[0].plugin).toEqual('test-plugin#one')
})
it('allows Root', () => {
let result = new Result();
let root = postcss.parse('a{}');
result.warn('TT', { node: root });
const result = new Result()
const root = postcss.parse('a{}')
result.warn('TT', { node: root })
expect(result.messages[0].toString()).toEqual('<css input>:1:1: TT');
});
expect(result.messages[0].toString()).toEqual('<css input>:1:1: TT')
})
it('returns only warnings', () => {
let result = new Result();
result.messages = [
{ type: 'warning', text: 'a' },
{ type: 'custom' },
{ type: 'warning', text: 'b' }
];
expect(result.warnings()).toEqual([
{ type: 'warning', text: 'a' },
{ type: 'warning', text: 'b' }
]);
});
const result = new Result()
result.messages = [
{ type: 'warning', text: 'a' },
{ type: 'custom' },
{ type: 'warning', text: 'b' }
]
expect(result.warnings()).toEqual([
{ type: 'warning', text: 'a' },
{ type: 'warning', text: 'b' }
])
})
+42 -44
View File
@@ -1,64 +1,62 @@
'use strict';
const Result = require('../lib/result');
const parse = require('../lib/parse');
const Result = require('../lib/result')
const parse = require('../lib/parse')
it('prepend() fixes spaces on insert before first', () => {
let css = parse('a {} b {}');
css.prepend({ selector: 'em' });
expect(css.toString()).toEqual('em {} a {} b {}');
});
const css = parse('a {} b {}')
css.prepend({ selector: 'em' })
expect(css.toString()).toEqual('em {} a {} b {}')
})
it('prepend() fixes spaces on multiple inserts before first', () => {
let css = parse('a {} b {}');
css.prepend({ selector: 'em' }, { selector: 'strong' });
expect(css.toString()).toEqual('em {} strong {} a {} b {}');
});
const css = parse('a {} b {}')
css.prepend({ selector: 'em' }, { selector: 'strong' })
expect(css.toString()).toEqual('em {} strong {} a {} b {}')
})
it('prepend() uses default spaces on only first', () => {
let css = parse('a {}');
css.prepend({ selector: 'em' });
expect(css.toString()).toEqual('em {}\na {}');
});
const css = parse('a {}')
css.prepend({ selector: 'em' })
expect(css.toString()).toEqual('em {}\na {}')
})
it('append() sets new line between rules in multiline files', () => {
let a = parse('a {}\n\na {}\n');
let b = parse('b {}\n');
expect(a.append(b).toString()).toEqual('a {}\n\na {}\n\nb {}\n');
});
const a = parse('a {}\n\na {}\n')
const b = parse('b {}\n')
expect(a.append(b).toString()).toEqual('a {}\n\na {}\n\nb {}\n')
})
it('insertAfter() does not use before of first rule', () => {
let css = parse('a{} b{}');
css.insertAfter(0, { selector: '.a' });
css.insertAfter(2, { selector: '.b' });
const css = parse('a{} b{}')
css.insertAfter(0, { selector: '.a' })
css.insertAfter(2, { selector: '.b' })
expect(css.nodes[1].raws.before).not.toBeDefined();
expect(css.nodes[3].raws.before).toEqual(' ');
expect(css.toString()).toEqual('a{} .a{} b{} .b{}');
});
expect(css.nodes[1].raws.before).not.toBeDefined()
expect(css.nodes[3].raws.before).toEqual(' ')
expect(css.toString()).toEqual('a{} .a{} b{} .b{}')
})
it('fixes spaces on removing first rule', () => {
let css = parse('a{}\nb{}\n');
css.first.remove();
expect(css.toString()).toEqual('b{}\n');
});
const css = parse('a{}\nb{}\n')
css.first.remove()
expect(css.toString()).toEqual('b{}\n')
})
it('keeps spaces on moving root', () => {
let css1 = parse('a{}\nb{}\n');
const css1 = parse('a{}\nb{}\n')
let css2 = parse('');
css2.append(css1);
expect(css2.toString()).toEqual('a{}\nb{}');
const css2 = parse('')
css2.append(css1)
expect(css2.toString()).toEqual('a{}\nb{}')
let css3 = parse('\n');
css3.append(css2.nodes);
expect(css3.toString()).toEqual('a{}\nb{}\n');
});
const css3 = parse('\n')
css3.append(css2.nodes)
expect(css3.toString()).toEqual('a{}\nb{}\n')
})
it('generates result with map', () => {
let root = parse('a {}');
let result = root.toResult({ map: true });
const root = parse('a {}')
const result = root.toResult({ map: true })
expect(result instanceof Result).toBeTruthy();
expect(result.css).toMatch(/a \{\}\n\/\*# sourceMappingURL=/);
});
expect(result instanceof Result).toBeTruthy()
expect(result.css).toMatch(/a \{\}\n\/\*# sourceMappingURL=/)
})
+55 -57
View File
@@ -1,82 +1,80 @@
'use strict';
const parse = require('../lib/parse');
const Rule = require('../lib/rule');
const parse = require('../lib/parse')
const Rule = require('../lib/rule')
it('initializes with properties', () => {
let rule = new Rule({ selector: 'a' });
expect(rule.selector).toEqual('a');
});
const rule = new Rule({ selector: 'a' })
expect(rule.selector).toEqual('a')
})
it('returns array in selectors', () => {
let rule = new Rule({ selector: 'a,b' });
expect(rule.selectors).toEqual(['a', 'b']);
});
const rule = new Rule({ selector: 'a,b' })
expect(rule.selectors).toEqual(['a', 'b'])
})
it('trims selectors', () => {
let rule = new Rule({ selector: '.a\n, .b , .c' });
expect(rule.selectors).toEqual(['.a', '.b', '.c']);
});
const rule = new Rule({ selector: '.a\n, .b , .c' })
expect(rule.selectors).toEqual(['.a', '.b', '.c'])
})
it('is smart about selectors commas', () => {
let rule = new Rule({
selector: '[foo=\'a, b\'], a:-moz-any(:focus, [href*=\',\'])'
});
expect(rule.selectors).toEqual([
'[foo=\'a, b\']',
'a:-moz-any(:focus, [href*=\',\'])'
]);
});
const rule = new Rule({
selector: '[foo=\'a, b\'], a:-moz-any(:focus, [href*=\',\'])'
})
expect(rule.selectors).toEqual([
'[foo=\'a, b\']',
'a:-moz-any(:focus, [href*=\',\'])'
])
})
it('receive array in selectors', () => {
let rule = new Rule({ selector: 'i, b' });
rule.selectors = ['em', 'strong'];
expect(rule.selector).toEqual('em, strong');
});
const rule = new Rule({ selector: 'i, b' })
rule.selectors = ['em', 'strong']
expect(rule.selector).toEqual('em, strong')
})
it('saves separator in selectors', () => {
let rule = new Rule({ selector: 'i,\nb' });
rule.selectors = ['em', 'strong'];
expect(rule.selector).toEqual('em,\nstrong');
});
const rule = new Rule({ selector: 'i,\nb' })
rule.selectors = ['em', 'strong']
expect(rule.selector).toEqual('em,\nstrong')
})
it('uses between to detect separator in selectors', () => {
let rule = new Rule({ selector: 'b', raws: { between: '' } });
rule.selectors = ['b', 'strong'];
expect(rule.selector).toEqual('b,strong');
});
const rule = new Rule({ selector: 'b', raws: { between: '' } })
rule.selectors = ['b', 'strong']
expect(rule.selector).toEqual('b,strong')
})
it('uses space in separator be default in selectors', () => {
let rule = new Rule({ selector: 'b' });
rule.selectors = ['b', 'strong'];
expect(rule.selector).toEqual('b, strong');
});
const rule = new Rule({ selector: 'b' })
rule.selectors = ['b', 'strong']
expect(rule.selector).toEqual('b, strong')
})
it('selectors works in constructor', () => {
let rule = new Rule({ selectors: ['a', 'b'] });
expect(rule.selector).toEqual('a, b');
});
const rule = new Rule({ selectors: ['a', 'b'] })
expect(rule.selector).toEqual('a, b')
})
it('inserts default spaces', () => {
let rule = new Rule({ selector: 'a' });
expect(rule.toString()).toEqual('a {}');
rule.append({ prop: 'color', value: 'black' });
expect(rule.toString()).toEqual('a {\n color: black\n}');
});
const rule = new Rule({ selector: 'a' })
expect(rule.toString()).toEqual('a {}')
rule.append({ prop: 'color', value: 'black' })
expect(rule.toString()).toEqual('a {\n color: black\n}')
})
it('clones spaces from another rule', () => {
let root = parse('b{\n }');
let rule = new Rule({ selector: 'em' });
root.append(rule);
expect(root.toString()).toEqual('b{\n }\nem{\n }');
});
const root = parse('b{\n }')
const rule = new Rule({ selector: 'em' })
root.append(rule)
expect(root.toString()).toEqual('b{\n }\nem{\n }')
})
it('uses different spaces for empty rules', () => {
let root = parse('a{}\nb{\n a:1\n}');
let rule = new Rule({ selector: 'em' });
root.append(rule);
expect(root.toString()).toEqual('a{}\nb{\n a:1\n}\nem{}');
const root = parse('a{}\nb{\n a:1\n}')
const rule = new Rule({ selector: 'em' })
root.append(rule)
expect(root.toString()).toEqual('a{}\nb{\n a:1\n}\nem{}')
rule.append({ prop: 'top', value: '0' });
expect(root.toString()).toEqual('a{}\nb{\n a:1\n}\nem{\n top:0\n}');
});
rule.append({ prop: 'top', value: '0' })
expect(root.toString()).toEqual('a{}\nb{\n a:1\n}\nem{\n top:0\n}')
})
+121 -123
View File
@@ -1,175 +1,173 @@
'use strict';
const Stringifier = require('../lib/stringifier')
const Declaration = require('../lib/declaration')
const AtRule = require('../lib/at-rule')
const parse = require('../lib/parse')
const Node = require('../lib/node')
const Root = require('../lib/root')
const Rule = require('../lib/rule')
const Stringifier = require('../lib/stringifier');
const Declaration = require('../lib/declaration');
const AtRule = require('../lib/at-rule');
const parse = require('../lib/parse');
const Node = require('../lib/node');
const Root = require('../lib/root');
const Rule = require('../lib/rule');
let str;
let str
beforeAll(() => {
str = new Stringifier();
});
str = new Stringifier()
})
it('creates trimmed/raw property', () => {
let b = new Node({ one: 'trim' });
b.raws.one = { value: 'trim', raw: 'raw' };
expect(str.rawValue(b, 'one')).toEqual('raw');
const b = new Node({ one: 'trim' })
b.raws.one = { value: 'trim', raw: 'raw' }
expect(str.rawValue(b, 'one')).toEqual('raw')
b.one = 'trim1';
expect(str.rawValue(b, 'one')).toEqual('trim1');
});
b.one = 'trim1'
expect(str.rawValue(b, 'one')).toEqual('trim1')
})
it('works without rawValue magic', () => {
let b = new Node();
b.one = '1';
expect(b.one).toEqual('1');
expect(str.rawValue(b, 'one')).toEqual('1');
});
const b = new Node()
b.one = '1'
expect(b.one).toEqual('1')
expect(str.rawValue(b, 'one')).toEqual('1')
})
it('uses node raw', () => {
let rule = new Rule({ selector: 'a', raws: { between: '\n' } });
expect(str.raw(rule, 'between', 'beforeOpen')).toEqual('\n');
});
const rule = new Rule({ selector: 'a', raws: { between: '\n' } })
expect(str.raw(rule, 'between', 'beforeOpen')).toEqual('\n')
})
it('hacks before for nodes without parent', () => {
let rule = new Rule({ selector: 'a' });
expect(str.raw(rule, 'before')).toEqual('');
});
const rule = new Rule({ selector: 'a' })
expect(str.raw(rule, 'before')).toEqual('')
})
it('hacks before for first node', () => {
let root = new Root();
root.append(new Rule({ selector: 'a' }));
expect(str.raw(root.first, 'before')).toEqual('');
});
const root = new Root()
root.append(new Rule({ selector: 'a' }))
expect(str.raw(root.first, 'before')).toEqual('')
})
it('hacks before for first decl', () => {
let decl = new Declaration({ prop: 'color', value: 'black' });
expect(str.raw(decl, 'before')).toEqual('');
const decl = new Declaration({ prop: 'color', value: 'black' })
expect(str.raw(decl, 'before')).toEqual('')
let rule = new Rule({ selector: 'a' });
rule.append(decl);
expect(str.raw(decl, 'before')).toEqual('\n ');
});
const rule = new Rule({ selector: 'a' })
rule.append(decl)
expect(str.raw(decl, 'before')).toEqual('\n ')
})
it('detects after raw', () => {
let root = new Root();
root.append({ selector: 'a', raws: { after: ' ' } });
root.first.append({ prop: 'color', value: 'black' });
root.append({ selector: 'a' });
expect(str.raw(root.last, 'after')).toEqual(' ');
});
const root = new Root()
root.append({ selector: 'a', raws: { after: ' ' } })
root.first.append({ prop: 'color', value: 'black' })
root.append({ selector: 'a' })
expect(str.raw(root.last, 'after')).toEqual(' ')
})
it('uses defaults without parent', () => {
let rule = new Rule({ selector: 'a' });
expect(str.raw(rule, 'between', 'beforeOpen')).toEqual(' ');
});
const rule = new Rule({ selector: 'a' })
expect(str.raw(rule, 'between', 'beforeOpen')).toEqual(' ')
})
it('uses defaults for unique node', () => {
let root = new Root();
root.append(new Rule({ selector: 'a' }));
expect(str.raw(root.first, 'between', 'beforeOpen')).toEqual(' ');
});
const root = new Root()
root.append(new Rule({ selector: 'a' }))
expect(str.raw(root.first, 'between', 'beforeOpen')).toEqual(' ')
})
it('clones raw from first node', () => {
let root = new Root();
root.append( new Rule({ selector: 'a', raws: { between: '' } }) );
root.append( new Rule({ selector: 'b' }) );
const root = new Root()
root.append(new Rule({ selector: 'a', raws: { between: '' } }))
root.append(new Rule({ selector: 'b' }))
expect(str.raw(root.last, 'between', 'beforeOpen')).toEqual('');
});
expect(str.raw(root.last, 'between', 'beforeOpen')).toEqual('')
})
it('indents by default', () => {
let root = new Root();
root.append( new AtRule({ name: 'page' }) );
root.first.append( new Rule({ selector: 'a' }) );
root.first.first.append({ prop: 'color', value: 'black' });
const root = new Root()
root.append(new AtRule({ name: 'page' }))
root.first.append(new Rule({ selector: 'a' }))
root.first.first.append({ prop: 'color', value: 'black' })
expect(root.toString()).toEqual('@page {\n' +
' a {\n' +
' color: black\n' +
' }\n' +
'}');
});
expect(root.toString()).toEqual('@page {\n' +
' a {\n' +
' color: black\n' +
' }\n' +
'}')
})
it('clones style', () => {
let compress = parse('@page{ a{ } }');
let spaces = parse('@page {\n a {\n }\n}');
const compress = parse('@page{ a{ } }')
const spaces = parse('@page {\n a {\n }\n}')
compress.first.first.append({ prop: 'color', value: 'black' });
expect(compress.toString()).toEqual('@page{ a{ color: black } }');
compress.first.first.append({ prop: 'color', value: 'black' })
expect(compress.toString()).toEqual('@page{ a{ color: black } }')
spaces.first.first.append({ prop: 'color', value: 'black' });
expect(spaces.toString())
.toEqual('@page {\n a {\n color: black\n }\n}');
});
spaces.first.first.append({ prop: 'color', value: 'black' })
expect(spaces.toString())
.toEqual('@page {\n a {\n color: black\n }\n}')
})
it('clones indent', () => {
let root = parse('a{\n}');
root.first.append({ text: 'a' });
root.first.append({ text: 'b', raws: { before: '\n\n ' } });
expect(root.toString()).toEqual('a{\n\n /* a */\n\n /* b */\n}');
});
const root = parse('a{\n}')
root.first.append({ text: 'a' })
root.first.append({ text: 'b', raws: { before: '\n\n ' } })
expect(root.toString()).toEqual('a{\n\n /* a */\n\n /* b */\n}')
})
it('clones declaration before for comment', () => {
let root = parse('a{\n}');
root.first.append({ text: 'a' });
root.first.append({
prop: 'a',
value: '1',
raws: { before: '\n\n ' }
});
expect(root.toString()).toEqual('a{\n\n /* a */\n\n a: 1\n}');
});
const root = parse('a{\n}')
root.first.append({ text: 'a' })
root.first.append({
prop: 'a',
value: '1',
raws: { before: '\n\n ' }
})
expect(root.toString()).toEqual('a{\n\n /* a */\n\n a: 1\n}')
})
it('clones indent by types', () => {
let css = parse('a {\n *color: black\n}\n\nb {\n}');
css.append(new Rule({ selector: 'em' }));
css.last.append({ prop: 'z-index', value: '1' });
expect(css.last.first.raw('before')).toEqual('\n ');
});
const css = parse('a {\n *color: black\n}\n\nb {\n}')
css.append(new Rule({ selector: 'em' }))
css.last.append({ prop: 'z-index', value: '1' })
expect(css.last.first.raw('before')).toEqual('\n ')
})
it('ignores non-space symbols in indent cloning', () => {
let css = parse('a {\n color: black\n}\n\nb {\n}');
css.append(new Rule({ selector: 'em' }));
css.last.append({ prop: 'z-index', value: '1' });
const css = parse('a {\n color: black\n}\n\nb {\n}')
css.append(new Rule({ selector: 'em' }))
css.last.append({ prop: 'z-index', value: '1' })
expect(css.last.raw('before')).toEqual('\n\n');
expect(css.last.first.raw('before')).toEqual('\n ');
});
expect(css.last.raw('before')).toEqual('\n\n')
expect(css.last.first.raw('before')).toEqual('\n ')
})
it('clones indent by before and after', () => {
let css = parse('@page{\n\n a{\n color: black}}');
css.first.append(new Rule({ selector: 'b' }));
css.first.last.append({ prop: 'z-index', value: '1' });
const css = parse('@page{\n\n a{\n color: black}}')
css.first.append(new Rule({ selector: 'b' }))
css.first.last.append({ prop: 'z-index', value: '1' })
expect(css.first.last.raw('before')).toEqual('\n\n ');
expect(css.first.last.raw('after')).toEqual('');
});
expect(css.first.last.raw('before')).toEqual('\n\n ')
expect(css.first.last.raw('after')).toEqual('')
})
it('clones semicolon only from rules with children', () => {
let css = parse('a{}b{one:1;}');
expect(str.raw(css.first, 'semicolon')).toBeTruthy();
});
const css = parse('a{}b{one:1;}')
expect(str.raw(css.first, 'semicolon')).toBeTruthy()
})
it('clones only spaces in before', () => {
let css = parse('a{*one:1}');
css.first.append({ prop: 'two', value: '2' });
css.append({ name: 'keyframes', params: 'a' });
css.last.append({ selector: 'from' });
expect(css.toString()).toEqual('a{*one:1;two:2}\n@keyframes a{\nfrom{}}');
});
const css = parse('a{*one:1}')
css.first.append({ prop: 'two', value: '2' })
css.append({ name: 'keyframes', params: 'a' })
css.last.append({ selector: 'from' })
expect(css.toString()).toEqual('a{*one:1;two:2}\n@keyframes a{\nfrom{}}')
})
it('clones only spaces in between', () => {
let css = parse('a{one/**/:1}');
css.first.append({ prop: 'two', value: '2' });
expect(css.toString()).toEqual('a{one/**/:1;two:2}');
});
const css = parse('a{one/**/:1}')
css.first.append({ prop: 'two', value: '2' })
expect(css.toString()).toEqual('a{one/**/:1;two:2}')
})
it('uses optional raws.indent', () => {
let rule = new Rule({ selector: 'a', raws: { indent: ' ' } });
rule.append({ prop: 'color', value: 'black' });
expect(rule.toString()).toEqual('a {\n color: black\n}');
});
const rule = new Rule({ selector: 'a', raws: { indent: ' ' } })
rule.append({ prop: 'color', value: 'black' })
expect(rule.toString()).toEqual('a {\n color: black\n}')
})
+14 -16
View File
@@ -1,19 +1,17 @@
'use strict';
const stringify = require('../lib/stringify')
const parse = require('../lib/parse')
const stringify = require('../lib/stringify');
const parse = require('../lib/parse');
const cases = require('postcss-parser-tests')
const cases = require('postcss-parser-tests');
cases.each((name, css) => {
if (name === 'bom.css') return
cases.each( (name, css) => {
if ( name === 'bom.css' ) return;
it('stringifies ' + name, () => {
let root = parse(css);
let result = '';
stringify(root, i => {
result += i;
});
expect(result).toEqual(css);
});
});
it('stringifies ' + name, () => {
const root = parse(css)
let result = ''
stringify(root, i => {
result += i
})
expect(result).toEqual(css)
})
})
+202 -204
View File
@@ -1,273 +1,271 @@
'use strict';
const tokenizer = require('../lib/tokenize')
const Input = require('../lib/input')
const tokenizer = require('../lib/tokenize');
const Input = require('../lib/input');
function tokenize(css, opts) {
let processor = tokenizer(new Input(css), opts);
let tokens = [];
while (!processor.endOfFile()) {
tokens.push(processor.nextToken());
}
return tokens;
function tokenize (css, opts) {
const processor = tokenizer(new Input(css), opts)
const tokens = []
while (!processor.endOfFile()) {
tokens.push(processor.nextToken())
}
return tokens
}
function run(css, tokens, opts) {
expect(tokenize(css, opts)).toEqual(tokens);
function run (css, tokens, opts) {
expect(tokenize(css, opts)).toEqual(tokens)
}
it('tokenizes empty file', () => {
run('', []);
});
run('', [])
})
it('tokenizes space', () => {
run('\r\n \f\t', [ ['space', '\r\n \f\t'] ]);
});
run('\r\n \f\t', [['space', '\r\n \f\t']])
})
it('tokenizes word', () => {
run('ab', [ ['word', 'ab', 1, 1, 1, 2] ]);
});
run('ab', [['word', 'ab', 1, 1, 1, 2]])
})
it('splits word by !', () => {
run('aa!bb', [
['word', 'aa', 1, 1, 1, 2],
['word', '!bb', 1, 3, 1, 5]
]);
});
run('aa!bb', [
['word', 'aa', 1, 1, 1, 2],
['word', '!bb', 1, 3, 1, 5]
])
})
it('changes lines in spaces', () => {
run('a \n b', [
['word', 'a', 1, 1, 1, 1],
['space', ' \n '],
['word', 'b', 2, 2, 2, 2]
]);
});
run('a \n b', [
['word', 'a', 1, 1, 1, 1],
['space', ' \n '],
['word', 'b', 2, 2, 2, 2]
])
})
it('tokenizes control chars', () => {
run('{:;}', [
['{', '{', 1, 1],
[':', ':', 1, 2],
[';', ';', 1, 3],
['}', '}', 1, 4]
]);
});
run('{:;}', [
['{', '{', 1, 1],
[':', ':', 1, 2],
[';', ';', 1, 3],
['}', '}', 1, 4]
])
})
it('escapes control symbols', () => {
run('\\(\\{\\"\\@\\\\""', [
['word', '\\(', 1, 1, 1, 2],
['word', '\\{', 1, 3, 1, 4],
['word', '\\"', 1, 5, 1, 6],
['word', '\\@', 1, 7, 1, 8],
['word', '\\\\', 1, 9, 1, 10],
['string', '""', 1, 11, 1, 12]
]);
});
run('\\(\\{\\"\\@\\\\""', [
['word', '\\(', 1, 1, 1, 2],
['word', '\\{', 1, 3, 1, 4],
['word', '\\"', 1, 5, 1, 6],
['word', '\\@', 1, 7, 1, 8],
['word', '\\\\', 1, 9, 1, 10],
['string', '""', 1, 11, 1, 12]
])
})
it('escapes backslash', () => {
run('\\\\\\\\{', [
['word', '\\\\\\\\', 1, 1, 1, 4],
['{', '{', 1, 5]
]);
});
run('\\\\\\\\{', [
['word', '\\\\\\\\', 1, 1, 1, 4],
['{', '{', 1, 5]
])
})
it('tokenizes simple brackets', () => {
run('(ab)', [ ['brackets', '(ab)', 1, 1, 1, 4] ]);
});
run('(ab)', [['brackets', '(ab)', 1, 1, 1, 4]])
})
it('tokenizes square brackets', () => {
run('a[bc]', [
['word', 'a', 1, 1, 1, 1],
['[', '[', 1, 2],
['word', 'bc', 1, 3, 1, 4],
[']', ']', 1, 5]
]);
});
run('a[bc]', [
['word', 'a', 1, 1, 1, 1],
['[', '[', 1, 2],
['word', 'bc', 1, 3, 1, 4],
[']', ']', 1, 5]
])
})
it('tokenizes complicated brackets', () => {
run('(())("")(/**/)(\\\\)(\n)(', [
['(', '(', 1, 1],
['brackets', '()', 1, 2, 1, 3],
[')', ')', 1, 4],
['(', '(', 1, 5],
['string', '""', 1, 6, 1, 7],
[')', ')', 1, 8],
['(', '(', 1, 9],
['comment', '/**/', 1, 10, 1, 13],
[')', ')', 1, 14],
['(', '(', 1, 15],
['word', '\\\\', 1, 16, 1, 17],
[')', ')', 1, 18],
['(', '(', 1, 19],
['space', '\n'],
[')', ')', 2, 1],
['(', '(', 2, 2]
]);
});
run('(())("")(/**/)(\\\\)(\n)(', [
['(', '(', 1, 1],
['brackets', '()', 1, 2, 1, 3],
[')', ')', 1, 4],
['(', '(', 1, 5],
['string', '""', 1, 6, 1, 7],
[')', ')', 1, 8],
['(', '(', 1, 9],
['comment', '/**/', 1, 10, 1, 13],
[')', ')', 1, 14],
['(', '(', 1, 15],
['word', '\\\\', 1, 16, 1, 17],
[')', ')', 1, 18],
['(', '(', 1, 19],
['space', '\n'],
[')', ')', 2, 1],
['(', '(', 2, 2]
])
})
it('tokenizes string', () => {
run('\'"\'"\\""', [
['string', '\'"\'', 1, 1, 1, 3],
['string', '"\\""', 1, 4, 1, 7]
]);
});
run('\'"\'"\\""', [
['string', '\'"\'', 1, 1, 1, 3],
['string', '"\\""', 1, 4, 1, 7]
])
})
it('tokenizes escaped string', () => {
run('"\\\\"', [ ['string', '"\\\\"', 1, 1, 1, 4] ]);
});
run('"\\\\"', [['string', '"\\\\"', 1, 1, 1, 4]])
})
it('changes lines in strings', () => {
run('"\n\n""\n\n"', [
['string', '"\n\n"', 1, 1, 3, 1],
['string', '"\n\n"', 3, 2, 5, 1]
]);
});
run('"\n\n""\n\n"', [
['string', '"\n\n"', 1, 1, 3, 1],
['string', '"\n\n"', 3, 2, 5, 1]
])
})
it('tokenizes at-word', () => {
run('@word ', [ ['at-word', '@word', 1, 1, 1, 5], ['space', ' '] ]);
});
run('@word ', [['at-word', '@word', 1, 1, 1, 5], ['space', ' ']])
})
it('tokenizes at-word end', () => {
run('@one{@two()@three""@four;', [
['at-word', '@one', 1, 1, 1, 4],
['{', '{', 1, 5],
['at-word', '@two', 1, 6, 1, 9],
['brackets', '()', 1, 10, 1, 11],
['at-word', '@three', 1, 12, 1, 17],
['string', '""', 1, 18, 1, 19],
['at-word', '@four', 1, 20, 1, 24],
[';', ';', 1, 25]
]);
});
run('@one{@two()@three""@four;', [
['at-word', '@one', 1, 1, 1, 4],
['{', '{', 1, 5],
['at-word', '@two', 1, 6, 1, 9],
['brackets', '()', 1, 10, 1, 11],
['at-word', '@three', 1, 12, 1, 17],
['string', '""', 1, 18, 1, 19],
['at-word', '@four', 1, 20, 1, 24],
[';', ';', 1, 25]
])
})
it('tokenizes urls', () => {
run('url(/*\\))', [
['word', 'url', 1, 1, 1, 3],
['brackets', '(/*\\))', 1, 4, 1, 9]
]);
});
run('url(/*\\))', [
['word', 'url', 1, 1, 1, 3],
['brackets', '(/*\\))', 1, 4, 1, 9]
])
})
it('tokenizes quoted urls', () => {
run('url(")")', [
['word', 'url', 1, 1, 1, 3],
['(', '(', 1, 4],
['string', '")"', 1, 5, 1, 7],
[')', ')', 1, 8]
]);
});
run('url(")")', [
['word', 'url', 1, 1, 1, 3],
['(', '(', 1, 4],
['string', '")"', 1, 5, 1, 7],
[')', ')', 1, 8]
])
})
it('tokenizes at-symbol', () => {
run('@', [ ['at-word', '@', 1, 1, 1, 1] ]);
});
run('@', [['at-word', '@', 1, 1, 1, 1]])
})
it('tokenizes comment', () => {
run('/* a\nb */', [ ['comment', '/* a\nb */', 1, 1, 2, 4] ]);
});
run('/* a\nb */', [['comment', '/* a\nb */', 1, 1, 2, 4]])
})
it('changes lines in comments', () => {
run('a/* \n */b', [
['word', 'a', 1, 1, 1, 1],
['comment', '/* \n */', 1, 2, 2, 3],
['word', 'b', 2, 4, 2, 4]
]);
});
run('a/* \n */b', [
['word', 'a', 1, 1, 1, 1],
['comment', '/* \n */', 1, 2, 2, 3],
['word', 'b', 2, 4, 2, 4]
])
})
it('supports line feed', () => {
run('a\fb', [
['word', 'a', 1, 1, 1, 1],
['space', '\f'],
['word', 'b', 2, 1, 2, 1]
]);
});
run('a\fb', [
['word', 'a', 1, 1, 1, 1],
['space', '\f'],
['word', 'b', 2, 1, 2, 1]
])
})
it('supports carriage return', () => {
run('a\rb\r\nc', [
['word', 'a', 1, 1, 1, 1],
['space', '\r'],
['word', 'b', 2, 1, 2, 1],
['space', '\r\n'],
['word', 'c', 3, 1, 3, 1]
]);
});
run('a\rb\r\nc', [
['word', 'a', 1, 1, 1, 1],
['space', '\r'],
['word', 'b', 2, 1, 2, 1],
['space', '\r\n'],
['word', 'c', 3, 1, 3, 1]
])
})
it('tokenizes CSS', () => {
let css = 'a {\n' +
const css = 'a {\n' +
' content: "a";\n' +
' width: calc(1px;)\n' +
' }\n' +
'/* small screen */\n' +
'@media screen {}';
run(css, [
['word', 'a', 1, 1, 1, 1],
['space', ' '],
['{', '{', 1, 3],
['space', '\n '],
['word', 'content', 2, 3, 2, 9],
[':', ':', 2, 10],
['space', ' '],
['string', '"a"', 2, 12, 2, 14],
[';', ';', 2, 15],
['space', '\n '],
['word', 'width', 3, 3, 3, 7],
[':', ':', 3, 8],
['space', ' '],
['word', 'calc', 3, 10, 3, 13],
['brackets', '(1px;)', 3, 14, 3, 19],
['space', '\n '],
['}', '}', 4, 3],
['space', '\n'],
['comment', '/* small screen */', 5, 1, 5, 18],
['space', '\n'],
['at-word', '@media', 6, 1, 6, 6],
['space', ' '],
['word', 'screen', 6, 8, 6, 13],
['space', ' '],
['{', '{', 6, 15],
['}', '}', 6, 16]
]);
});
'@media screen {}'
run(css, [
['word', 'a', 1, 1, 1, 1],
['space', ' '],
['{', '{', 1, 3],
['space', '\n '],
['word', 'content', 2, 3, 2, 9],
[':', ':', 2, 10],
['space', ' '],
['string', '"a"', 2, 12, 2, 14],
[';', ';', 2, 15],
['space', '\n '],
['word', 'width', 3, 3, 3, 7],
[':', ':', 3, 8],
['space', ' '],
['word', 'calc', 3, 10, 3, 13],
['brackets', '(1px;)', 3, 14, 3, 19],
['space', '\n '],
['}', '}', 4, 3],
['space', '\n'],
['comment', '/* small screen */', 5, 1, 5, 18],
['space', '\n'],
['at-word', '@media', 6, 1, 6, 6],
['space', ' '],
['word', 'screen', 6, 8, 6, 13],
['space', ' '],
['{', '{', 6, 15],
['}', '}', 6, 16]
])
})
it('throws error on unclosed string', () => {
expect(() => {
tokenize(' "');
}).toThrowError(/:1:2: Unclosed string/);
});
expect(() => {
tokenize(' "')
}).toThrowError(/:1:2: Unclosed string/)
})
it('throws error on unclosed comment', () => {
expect(() => {
tokenize(' /*');
}).toThrowError(/:1:2: Unclosed comment/);
});
expect(() => {
tokenize(' /*')
}).toThrowError(/:1:2: Unclosed comment/)
})
it('throws error on unclosed url', () => {
expect(() => {
tokenize('url(');
}).toThrowError(/:1:4: Unclosed bracket/);
});
expect(() => {
tokenize('url(')
}).toThrowError(/:1:4: Unclosed bracket/)
})
it('ignores unclosing string on request', () => {
run(' "', [
['space', ' '], ['string', '\"', 1, 2, 1, 3]
], { ignoreErrors: true });
});
run(' "', [
['space', ' '], ['string', '"', 1, 2, 1, 3]
], { ignoreErrors: true })
})
it('ignores unclosing comment on request', () => {
run(' /*', [
['space', ' '], ['comment', '/*', 1, 2, 1, 4]
], { ignoreErrors: true });
});
run(' /*', [
['space', ' '], ['comment', '/*', 1, 2, 1, 4]
], { ignoreErrors: true })
})
it('ignores unclosing comment on request', () => {
run('url(', [
['word', 'url', 1, 1, 1, 3],
['brackets', '(', 1, 4, 1, 4]
], { ignoreErrors: true });
});
it('ignores unclosing function on request', () => {
run('url(', [
['word', 'url', 1, 1, 1, 3],
['brackets', '(', 1, 4, 1, 4]
], { ignoreErrors: true })
})
it('tokenizes hexadecimal escape', () => {
run('\\0a \\09 \\z ', [
['word', '\\0a ', 1, 1, 1, 4],
['word', '\\09 ', 1, 5, 1, 8],
['word', '\\z', 1, 9, 1, 10],
['space', ' ']
]);
});
run('\\0a \\09 \\z ', [
['word', '\\0a ', 1, 1, 1, 4],
['word', '\\09 ', 1, 5, 1, 8],
['word', '\\z', 1, 9, 1, 10],
['space', ' ']
])
})
+10 -12
View File
@@ -1,17 +1,15 @@
'use strict';
const vendor = require('../lib/vendor')
const vendor = require('../lib/vendor');
const VALUE = '-1px -1px 1px rgba(0, 0, 0, 0.2) inset';
const VALUE = '-1px -1px 1px rgba(0, 0, 0, 0.2) inset'
it('returns prefix', () => {
expect(vendor.prefix('-moz-color')).toEqual('-moz-');
expect(vendor.prefix('color' )).toEqual('');
expect(vendor.prefix(VALUE)).toEqual('');
});
expect(vendor.prefix('-moz-color')).toEqual('-moz-')
expect(vendor.prefix('color')).toEqual('')
expect(vendor.prefix(VALUE)).toEqual('')
})
it('returns unprefixed version', () => {
expect(vendor.unprefixed('-moz-color')).toEqual('color');
expect(vendor.unprefixed('color' )).toEqual('color');
expect(vendor.unprefixed(VALUE)).toEqual(VALUE);
});
expect(vendor.unprefixed('-moz-color')).toEqual('color')
expect(vendor.unprefixed('color')).toEqual('color')
expect(vendor.unprefixed(VALUE)).toEqual(VALUE)
})
+63 -65
View File
@@ -1,88 +1,86 @@
'use strict';
const Declaration = require('../lib/declaration')
const Warning = require('../lib/warning')
const parse = require('../lib/parse')
const Declaration = require('../lib/declaration');
const Warning = require('../lib/warning');
const parse = require('../lib/parse');
const path = require('path');
const path = require('path')
it('outputs simple warning', () => {
let warning = new Warning('text');
expect(warning.toString()).toEqual('text');
});
const warning = new Warning('text')
expect(warning.toString()).toEqual('text')
})
it('outputs warning with plugin', () => {
let warning = new Warning('text', { plugin: 'plugin' });
expect(warning.toString()).toEqual('plugin: text');
});
const warning = new Warning('text', { plugin: 'plugin' })
expect(warning.toString()).toEqual('plugin: text')
})
it('outputs warning with position', () => {
let root = parse('a{}');
let warning = new Warning('text', { node: root.first });
expect(warning.toString()).toEqual('<css input>:1:1: text');
});
const root = parse('a{}')
const warning = new Warning('text', { node: root.first })
expect(warning.toString()).toEqual('<css input>:1:1: text')
})
it('outputs warning with plugin and node', () => {
let file = path.resolve('a.css');
let root = parse('a{}', { from: file });
let warning = new Warning('text', {
plugin: 'plugin',
node: root.first
});
expect(warning.toString()).toEqual(`plugin: ${ file }:1:1: text`);
});
const file = path.resolve('a.css')
const root = parse('a{}', { from: file })
const warning = new Warning('text', {
plugin: 'plugin',
node: root.first
})
expect(warning.toString()).toEqual(`plugin: ${ file }:1:1: text`)
})
it('outputs warning with index', () => {
let file = path.resolve('a.css');
let root = parse('@rule param {}', { from: file });
let warning = new Warning('text', {
plugin: 'plugin',
node: root.first,
index: 7
});
expect(warning.toString()).toEqual(`plugin: ${ file }:1:8: text`);
});
const file = path.resolve('a.css')
const root = parse('@rule param {}', { from: file })
const warning = new Warning('text', {
plugin: 'plugin',
node: root.first,
index: 7
})
expect(warning.toString()).toEqual(`plugin: ${ file }:1:8: text`)
})
it('outputs warning with word', () => {
let file = path.resolve('a.css');
let root = parse('@rule param {}', { from: file });
let warning = new Warning('text', {
plugin: 'plugin',
node: root.first,
word: 'am'
});
expect(warning.toString()).toEqual(`plugin: ${ file }:1:10: text`);
});
const file = path.resolve('a.css')
const root = parse('@rule param {}', { from: file })
const warning = new Warning('text', {
plugin: 'plugin',
node: root.first,
word: 'am'
})
expect(warning.toString()).toEqual(`plugin: ${ file }:1:10: text`)
})
it('generates warning without source', () => {
let decl = new Declaration({ prop: 'color', value: 'black' });
let warning = new Warning('text', { node: decl });
expect(warning.toString()).toEqual('<css input>: text');
});
const decl = new Declaration({ prop: 'color', value: 'black' })
const warning = new Warning('text', { node: decl })
expect(warning.toString()).toEqual('<css input>: text')
})
it('has line and column is undefined by default', () => {
let warning = new Warning('text');
expect(warning.line).not.toBeDefined();
expect(warning.column).not.toBeDefined();
});
const warning = new Warning('text')
expect(warning.line).not.toBeDefined()
expect(warning.column).not.toBeDefined()
})
it('gets position from node', () => {
let root = parse('a{}');
let warning = new Warning('text', { node: root.first });
expect(warning.line).toEqual(1);
expect(warning.column).toEqual(1);
});
const root = parse('a{}')
const warning = new Warning('text', { node: root.first })
expect(warning.line).toEqual(1)
expect(warning.column).toEqual(1)
})
it('gets position from word', () => {
let root = parse('a b{}');
let warning = new Warning('text', { node: root.first, word: 'b' });
expect(warning.line).toEqual(1);
expect(warning.column).toEqual(3);
});
const root = parse('a b{}')
const warning = new Warning('text', { node: root.first, word: 'b' })
expect(warning.line).toEqual(1)
expect(warning.column).toEqual(3)
})
it('gets position from index', () => {
let root = parse('a b{}');
let warning = new Warning('text', { node: root.first, index: 2 });
expect(warning.line).toEqual(1);
expect(warning.column).toEqual(3);
});
const root = parse('a b{}')
const warning = new Warning('text', { node: root.first, index: 2 })
expect(warning.line).toEqual(1)
expect(warning.column).toEqual(3)
})
+115 -6
View File
@@ -1682,6 +1682,10 @@ constants-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
contains-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
[email protected]:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
@@ -2119,6 +2123,13 @@ docdash@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/docdash/-/docdash-0.4.0.tgz#05c3a50d83189981699ee0c076d3a3950db7ec00"
[email protected]:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
dependencies:
esutils "^2.0.2"
isarray "^1.0.0"
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@@ -2317,9 +2328,71 @@ eslint-ci@^0.1.1:
dependencies:
ci-job-number "^0.3.0"
eslint-config-postcss@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/eslint-config-postcss/-/eslint-config-postcss-2.0.2.tgz#cae1c6093ced7850894a5b85fbe1d1e232b72afb"
eslint-config-logux@^21.0.0:
version "21.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-logux/-/eslint-config-logux-21.0.0.tgz#8594de2d5e61cfcbb81f9111a4b6656963942fb4"
dependencies:
globals "^11.3.0"
eslint-config-standard@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz#87ee0d3c9d95382dc761958cbb23da9eea31e0ba"
eslint-import-resolver-node@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
dependencies:
debug "^2.6.9"
resolve "^1.5.0"
eslint-module-utils@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746"
dependencies:
debug "^2.6.8"
pkg-dir "^1.0.0"
eslint-plugin-import@^2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.12.0.tgz#dad31781292d6664b25317fd049d2e2b2f02205d"
dependencies:
contains-path "^0.1.0"
debug "^2.6.8"
doctrine "1.5.0"
eslint-import-resolver-node "^0.3.1"
eslint-module-utils "^2.2.0"
has "^1.0.1"
lodash "^4.17.4"
minimatch "^3.0.3"
read-pkg-up "^2.0.0"
resolve "^1.6.0"
eslint-plugin-jest@^21.15.1:
version "21.15.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.15.1.tgz#662a3f0888002878f0f388efd09c190a95c33d82"
eslint-plugin-node@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-6.0.1.tgz#bf19642298064379315d7a4b2a75937376fa05e4"
dependencies:
ignore "^3.3.6"
minimatch "^3.0.4"
resolve "^1.3.3"
semver "^5.4.1"
eslint-plugin-promise@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz#f4bde5c2c77cdd69557a8f69a24d1ad3cfc9e67e"
eslint-plugin-security@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz#d4f314484a80b1b613b8c8886e84f52efe1526c2"
dependencies:
safe-regex "^1.1.0"
eslint-plugin-standard@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz#2a9e21259ba4c47c02d53b2d0c9135d4b1022d47"
eslint-scope@^3.7.1, eslint-scope@~3.7.1:
version "3.7.1"
@@ -3034,7 +3107,7 @@ global-prefix@^1.0.1:
is-windows "^1.0.1"
which "^1.2.14"
globals@^11.0.1, globals@^11.1.0:
globals@^11.0.1, globals@^11.1.0, globals@^11.3.0:
version "11.5.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.5.0.tgz#6bc840de6771173b191f13d3a9c94d441ee92642"
@@ -3411,7 +3484,7 @@ ignore-walk@^3.0.1:
dependencies:
minimatch "^3.0.4"
ignore@^3.3.3, ignore@^3.3.5:
ignore@^3.3.3, ignore@^3.3.5, ignore@^3.3.6:
version "3.3.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b"
@@ -4452,6 +4525,15 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
load-json-file@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
dependencies:
graceful-fs "^4.1.2"
parse-json "^2.2.0"
pify "^2.0.0"
strip-bom "^3.0.0"
load-json-file@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
@@ -5439,6 +5521,12 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
dependencies:
pify "^2.0.0"
path-type@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
@@ -5477,6 +5565,12 @@ pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
pkg-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
dependencies:
find-up "^1.0.0"
pkg-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
@@ -5959,6 +6053,13 @@ read-pkg-up@^1.0.1:
find-up "^1.0.0"
read-pkg "^1.0.0"
read-pkg-up@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
dependencies:
find-up "^2.0.0"
read-pkg "^2.0.0"
read-pkg-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07"
@@ -5974,6 +6075,14 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
read-pkg@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
dependencies:
load-json-file "^2.0.0"
normalize-package-data "^2.3.2"
path-type "^2.0.0"
read-pkg@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
@@ -6227,7 +6336,7 @@ [email protected]:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@^1.1.6, resolve@^1.1.7:
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.3, resolve@^1.5.0, resolve@^1.6.0:
version "1.7.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
dependencies: