Skip to content

Commit

Permalink
Rewrote to ES6.
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-eb committed Jun 22, 2016
1 parent 79d096a commit 150d40a
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 240 deletions.
9 changes: 9 additions & 0 deletions .babelrc
@@ -0,0 +1,9 @@
{
"presets": ["es2015-loose", "stage-0"],
"plugins": ["add-module-exports"],
"env": {
"development": {
"sourceMaps": "inline"
}
}
}
49 changes: 0 additions & 49 deletions .eslintrc

This file was deleted.

5 changes: 4 additions & 1 deletion .gitignore
@@ -1,3 +1,6 @@
.nyc_output
coverage
dist
node_modules
!fixtures/node_modules
!src/__tests__/fixtures/node_modules
npm-debug.log
3 changes: 3 additions & 0 deletions .travis.yml
Expand Up @@ -5,3 +5,6 @@ node_js:
- '5'
- '4'
- '0.12'

after_success:
- './node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls'
33 changes: 27 additions & 6 deletions package.json
Expand Up @@ -4,10 +4,14 @@
"description": "Enable PostCSS plugins directly in your stylesheet.",
"main": "index.js",
"files": [
"index.js"
"dist",
"LICENSE-MIT"
],
"scripts": {
"test": "tape test.js | tap-spec"
"pretest": "eslint src/*.js src/__test__/*.js",
"prepublish": "del-cli dist && cross-env BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/",
"report": "nyc report --reporter=html",
"test": "nyc ava src/__tests__/*.js"
},
"keywords": [
"css",
Expand All @@ -17,11 +21,22 @@
"license": "MIT",
"devDependencies": {
"autoprefixer": "^6.2.3",
"eslint": "^1.10.3",
"ava": "^0.15.0",
"babel-cli": "^6.5.1",
"babel-core": "^6.5.1",
"babel-plugin-add-module-exports": "^0.2.0",
"babel-preset-es2015-loose": "^7.0.0",
"babel-preset-stage-0": "^6.5.0",
"babel-register": "^6.9.0",
"coveralls": "^2.11.6",
"cross-env": "^1.0.7",
"del-cli": "^0.2.0",
"eslint": "^2.13.1",
"eslint-config-cssnano": "^2.0.0",
"nyc": "^6.0.0",
"postcss-cssnext": "^2.6.0",
"postcss-discard-comments": "^2.0.3",
"postcss-discard-font-face": "^2.1.0",
"tap-spec": "^4.1.1",
"tape": "^4.4.0"
"postcss-discard-font-face": "^3.0.0"
},
"homepage": "https://github.com/postcss/postcss-use",
"author": {
Expand All @@ -34,5 +49,11 @@
"balanced-match": "^0.4.1",
"postcss": "^5.0.21",
"resolve-from": "^2.0.0"
},
"ava": {
"require": "babel-register"
},
"eslintConfig": {
"extends": "cssnano"
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
127 changes: 127 additions & 0 deletions src/__tests__/index.js
@@ -0,0 +1,127 @@
import fs from 'fs';
import path from 'path';
import test from 'ava';
import postcss from 'postcss';
import plugin from '..';
import {name} from '../../package.json';

const tests = [{
message: 'should enable all modules from css',
fixture: '/* test comment */@use postcss-discard-comments;/* test comment */',
expected: '',
options: {modules: '*'}
}, {
message: 'should enable postcss-discard-comments from css',
fixture: '/* test comment */@use postcss-discard-comments;/* test comment */',
expected: '',
options: {modules: ['postcss-discard-comments']}
}, {
message: 'should enable postcss-discard-comments from css, with options',
fixture: '/*! license comment */@use postcss-discard-comments(removeAll: true);',
expected: '',
options: {modules: ['postcss-discard-comments']}
}, {
message: 'should enable postcss-discard-font-face from css, with array as options',
fixture: '@use postcss-discard-font-face(["svg", "woff"]); @font-face { font-family: A; src: url("a.svg") format("svg"), url("a.ttf") format("truetype")}',
expected: '@font-face { font-family: A; src: url("a.svg") format("svg")}',
options: {modules: ['postcss-discard-font-face']}
}, {
message: 'should enable autoprefixer from css',
fixture: '@use autoprefixer (remove: false; browsers: "> 1%, firefox 32");main{-webkit-border-radius:10px;border-radius:10px;display:flex;}',
expected: 'main{-webkit-border-radius:10px;border-radius:10px;display:-webkit-box;display:flex;}',
options: {modules: ['autoprefixer']}
}, {
message: 'should enable autoprefixer from css, with nested options',
fixture: '@use autoprefixer { remove: false; browsers: > 0%, firefox 32 };main{-webkit-border-radius:10px;border-radius:10px;display:flex;}',
expected: 'main{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex;}',
options: {modules: ['autoprefixer']}
}, {
message: 'should enable autoprefixer from css, with nested stringy options',
fixture: '@use autoprefixer { remove: false; browsers: "> 0%, firefox 32" };main{-webkit-border-radius:10px;border-radius:10px;display:flex;}',
expected: 'main{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex;}',
options: {modules: ['autoprefixer']}
}, {
message: 'should enable autoprefixer from css, with deeply nested options',
fixture: '@use autoprefixer { remove: false; browsers: "> 0%, firefox 32"; foo: { bar: true } /* ignores comments */ };main{-webkit-border-radius:10px;border-radius:10px;display:flex;}',
expected: 'main{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex;}',
options: {modules: ['autoprefixer']}
}, {
message: 'should enable postcss-cssnext',
fixture: '@use postcss-cssnext;:root{--a: red}h1{color: var(--a)}',
expected: 'h1{color: red}',
options: {modules: ['postcss-cssnext']}
}];

function process (css, options) {
return postcss(plugin(options)).process(css).css;
}

tests.forEach(({message, fixture, expected, options}) => {
test(message, t => {
t.deepEqual(process(fixture, options), expected);
});
});

test('multiple runs', t => {
const processor = postcss(plugin({modules: ['postcss-discard-comments']}));

return processor.process('@use postcss-discard-comments;/*test*/').then(({css}) => {
t.deepEqual(css, '');
}).then(processor.process('/*test*/').then(({css}) => {
t.deepEqual(css, '/*test*/');
}));
});

function shouldThrow (t, css, opts = {}) {
t.throws(() => process(css, opts));
}

test('should not support arrows', shouldThrow, '@use postcss-discard-comments(foo => bar)', {modules: ['postcss-discard-comments']});
test('should not support function syntax', shouldThrow, '@use postcss-discard-comments(function () { alert(1); })', {modules: ['postcss-discard-comments']});
test('should not support unclosed keys', shouldThrow, '@use postcss-discard-comments(removeAll:)', {modules: ['postcss-discard-comments']});
test('should not support unclosed arrays', shouldThrow, '@use postcss-discard-font-face(["svg")', {modules: ['postcss-discard-font-face']});
test('should not support plugins that are not whitelisted', shouldThrow, '@use postcss-discard-comments;');
test('should not support null', shouldThrow, '@use postcss-discard-comments;', {modules: null});
test('should not support false', shouldThrow, '@use postcss-discard-comments;', {modules: false});
test('should not support strings that are not "*"', shouldThrow, '@use postcss-discard-comments;', {modules: 'all'});

test('should use the postcss plugin api', t => {
t.truthy(plugin().postcssVersion, 'should be able to access version');
t.deepEqual(plugin().postcssPlugin, name, 'should be able to access name');
});

test('should use plugins relative to CSS file when using resolveFromFile', t => {
const inputFile = path.join(__dirname, 'fixtures', 'test.css');
const outputFile = path.join(__dirname, 'fixtures', 'test.out.css');
const inputCss = fs.readFileSync(inputFile);
return postcss(plugin({modules: '*', resolveFromFile: true})).process(inputCss, {
from: inputFile,
to: outputFile
}).then(({css}) => {
t.deepEqual(css, '.foo {color: red;}\n', 'should remove background decls');
});
});

test('should give meaningful error when module is not found', t => {
const inputFile = path.join(__dirname, 'fixtures', 'error.css');
const outputFile = path.join(__dirname, 'fixtures', 'error.out.css');
const inputCss = fs.readFileSync(inputFile);
return postcss(plugin({modules: '*', resolveFromFile: true})).process(inputCss, {
from: inputFile,
to: outputFile
}).catch(err => {
t.deepEqual(err.message, `Cannot find module 'postcss-fourohfour'`);
});
});

test('should not resolve plugins relative to CSS file by default', t => {
const inputFile = path.join(__dirname, 'fixtures', 'test.css');
const outputFile = path.join(__dirname, 'fixtures', 'test.out.css');
const inputCss = fs.readFileSync(inputFile);
return postcss(plugin({modules: '*'})).process(inputCss, {
from: inputFile,
to: outputFile
}).catch(err => {
t.deepEqual(err.message, `Cannot find module 'postcss-nobg'`);
});
});
73 changes: 35 additions & 38 deletions index.js → src/index.js
@@ -1,20 +1,23 @@
'use strict';
import path from 'path';
import postcss from 'postcss';
import balanced from 'balanced-match';
import resolveFrom from 'resolve-from';

var path = require('path');
var postcss = require('postcss');
var balanced = require('balanced-match');
var resolveFrom = require('resolve-from');
const NAME = 'postcss-use';

function invalidOption (option) {
throw new SyntaxError(`Invalid option '${option}'`);
}

function trim (value) {
return value.trim();
}

function pluginOptsFromRule (rule) {
var options = {};
var index = -1;
var node;
// Extra parentheses to fix JSHint
while ((node = rule.nodes && rule.nodes[++index])) {
const options = {};
let index = -1;
let node;
while (node = rule.nodes && rule.nodes[++index]) {
if (node.type === 'decl') {
try {
options[node.prop] = JSON.parse(node.value);
Expand All @@ -28,21 +31,20 @@ function pluginOptsFromRule (rule) {
return options;
}

module.exports = postcss.plugin('postcss-use', function (opts) {
opts = opts || {};
return function (css, result) {
export default postcss.plugin(NAME, (opts = {}) => {
return (css, result) => {
if (!opts.modules) {
throw new Error('postcss-use must be configured with a whitelist of plugins.');
throw new Error(`${NAME} must be configured with a whitelist of plugins.`);
}
var origin = result.processor.plugins.slice();
css.walkAtRules('use', function (rule) {
var pluginOpts;
var plugin = trim(rule.params);
var match = balanced('(', ')', rule.params);
const origin = result.processor.plugins.slice();
css.walkAtRules('use', rule => {
let pluginOpts;
let plugin = trim(rule.params);
let match = balanced('(', ')', rule.params);
if (!match) {
if (~rule.params.indexOf('(')) {
var params = rule.params + ';';
var next = rule.next();
let params = rule.params + ';';
let next = rule.next();
while (next && next.type === 'decl') {
params += String(next);
next = next.next();
Expand All @@ -54,22 +56,19 @@ module.exports = postcss.plugin('postcss-use', function (opts) {
}
}
if (match) {
var body = match.body;
if (~body.indexOf('function')) {
throw new SyntaxError('Functions are not supported by postcss-use.');
}
let body = match.body;
if (!body.indexOf('[')) {
if (body.lastIndexOf(']') === body.length - 1) {
pluginOpts = JSON.parse(body);
} else {
throw new SyntaxError('Invalid option "' + body + '"');
invalidOption(body);
}
} else {
body = body.split(';');
pluginOpts = body.reduce(function (config, option) {
var parts = option.split(':').map(trim);
pluginOpts = body.reduce((config, option) => {
let parts = option.split(':').map(trim);
if (!parts[1]) {
throw new SyntaxError('Invalid option "' + parts[0] + '"');
invalidOption(parts[0]);
}
config[parts[0]] = JSON.parse(parts[1]);
return config;
Expand All @@ -80,32 +79,30 @@ module.exports = postcss.plugin('postcss-use', function (opts) {
// Remove any directory traversal
plugin = plugin.replace(/\.\/\\/g, '');
if (~opts.modules.indexOf(plugin) || opts.modules === '*') {
var pluginPath = plugin;
let pluginPath = plugin;
if (opts.resolveFromFile && rule.source.input.file) {
pluginPath = resolveFrom(path.dirname(rule.source.input.file), plugin);
}

if (!pluginPath) {
throw new Error('Cannot find module \'' + plugin + '\'');
throw new Error(`Cannot find module '${plugin}'`);
}

var instance = require(pluginPath)(pluginOpts);
let instance = require(pluginPath)(pluginOpts);
if (instance.plugins) {
instance.plugins.forEach(function (p) {
instance.plugins.forEach((p) => {
result.processor.plugins.push(p);
});
} else {
result.processor.plugins.push(instance);
}
} else {
throw new ReferenceError(plugin + ' is not a valid postcss plugin.');
throw new ReferenceError(`'${plugin}' is not a valid postcss plugin.`);
}
rule.remove();
});
result.processor.plugins.push(postcss.plugin('postcss-use#reset', function () {
return function (styles, res) {
res.processor.plugins = origin;
};
result.processor.plugins.push(postcss.plugin(`${NAME}#reset`, () => {
return (styles, res) => res.processor.plugins = origin;
})());
};
});

0 comments on commit 150d40a

Please sign in to comment.