postcss-modules-local-by-default
Advanced tools
Comparing version 0.0.7 to 0.0.8
@@ -9,2 +9,9 @@ # Change Log | ||
## [0.0.8] - 2015-06-11 | ||
### Added | ||
- Pure mode where only local scope is allowed. | ||
### Changed | ||
- Using global selectors outside of a global context now triggers warnings. | ||
## [0.0.7] - 2015-05-30 | ||
@@ -42,3 +49,3 @@ ### Changed | ||
[unreleased]: https://github.com/postcss-modules-local-by-default/compare/v0.0.7...HEAD | ||
[unreleased]: https://github.com/postcss-modules-local-by-default/compare/v0.0.8...HEAD | ||
[0.0.2]: https://github.com/postcss-modules-local-by-default/compare/v0.0.1...v0.0.2 | ||
@@ -50,1 +57,2 @@ [0.0.3]: https://github.com/postcss-modules-local-by-default/compare/v0.0.2...v0.0.3 | ||
[0.0.7]: https://github.com/postcss-modules-local-by-default/compare/v0.0.6...v0.0.7 | ||
[0.0.8]: https://github.com/postcss-modules-local-by-default/compare/v0.0.7...v0.0.8 |
263
index.js
var postcss = require('postcss'); | ||
var Tokenizer = require('css-selector-tokenizer'); | ||
function localizeNodes(nodes) { | ||
var isGlobalContext = false; | ||
function normalizeNodeArray(nodes) { | ||
var array = []; | ||
nodes.forEach(function(x) { | ||
if(Array.isArray(x)) { | ||
normalizeNodeArray(x).forEach(function(item) { | ||
array.push(item); | ||
}); | ||
} else if(x) { | ||
array.push(x); | ||
} | ||
}); | ||
if(array.length > 0 && array[array.length - 1].type === "spacing") { | ||
array.pop(); | ||
} | ||
return array; | ||
} | ||
return nodes | ||
.map(function(node, i) { | ||
var newNode = node; | ||
function localizeNode(node, context) { | ||
if(context.ignoreNextSpacing && node.type !== "spacing") { | ||
throw new Error("Missing whitespace after :" + context.ignoreNextSpacing); | ||
} | ||
if(context.enforceNoSpacing && node.type === "spacing") { | ||
throw new Error("Missing whitespace before :" + context.enforceNoSpacing); | ||
} | ||
if (isGlobal(newNode)) { | ||
isGlobalContext = true; | ||
var newNodes; | ||
switch(node.type) { | ||
case "selectors": | ||
var resultingGlobal; | ||
context.hasPureGlobals = false; | ||
context.hasPureImplicitGlobals = false; | ||
newNodes = node.nodes.map(function(n) { | ||
var nContext = { | ||
global: context.global, | ||
lastWasSpacing: true, | ||
hasLocals: false, | ||
hasImplicitGlobals: false, | ||
explicit: false | ||
}; | ||
n = localizeNode(n, nContext); | ||
if(typeof resultingGlobal === "undefined") { | ||
resultingGlobal = nContext.global; | ||
} else if(resultingGlobal !== nContext.global) { | ||
throw new Error("Inconsistent rule global/local result in rule '" + | ||
Tokenizer.stringify(node) + "' (multiple selectors must result in the same mode for the rule)"); | ||
} | ||
if(!nContext.hasLocals) { | ||
context.hasPureGlobals = true; | ||
if(nContext.hasImplicitGlobals) { | ||
context.hasPureImplicitGlobals = true; | ||
} | ||
} | ||
return n; | ||
}); | ||
context.global = resultingGlobal; | ||
node = Object.create(node); | ||
node.nodes = normalizeNodeArray(newNodes); | ||
break; | ||
case "selector": | ||
newNodes = node.nodes.map(function(n) { | ||
return localizeNode(n, context); | ||
}); | ||
node = Object.create(node); | ||
node.nodes = normalizeNodeArray(newNodes); | ||
break; | ||
case "spacing": | ||
if(context.ignoreNextSpacing) { | ||
context.ignoreNextSpacing = false; | ||
context.lastWasSpacing = false; | ||
context.enforceNoSpacing = false; | ||
return null; | ||
} | ||
context.lastWasSpacing = true; | ||
return node; | ||
if (newNode.type === 'spacing' && isGlobal(nodes[i-1])) { | ||
case "pseudo-class": | ||
if(node.name === "local" || node.name === "global") { | ||
if(context.inside) { | ||
throw new Error("A :" + node.name + " is not allowed inside of a :" + context.inside + "(...)"); | ||
} | ||
context.ignoreNextSpacing = context.lastWasSpacing ? node.name : false; | ||
context.enforceNoSpacing = context.lastWasSpacing ? false : node.name; | ||
context.global = (node.name === "global"); | ||
context.explicit = true; | ||
return null; | ||
} | ||
break; | ||
if (!isGlobalContext && node.type === 'class') { | ||
newNode = { type: 'nested-pseudo-class', name: 'local', nodes: [node] } | ||
} else if (isNestedGlobal(newNode)) { | ||
newNode = node.nodes[0]; | ||
} else if (!isNestedLocal(newNode) && newNode.nodes) { | ||
newNode.nodes = localizeNodes(newNode.nodes); | ||
case "nested-pseudo-class": | ||
var subContext; | ||
if(node.name === "local" || node.name === "global") { | ||
if(context.inside) { | ||
throw new Error("A :" + node.name + "(...) is not allowed inside of a :" + context.inside + "(...)"); | ||
} | ||
subContext = { | ||
global: (node.name === "global"), | ||
inside: node.name, | ||
hasLocals: false, | ||
hasImplicitGlobals: false, | ||
explicit: true | ||
}; | ||
node = node.nodes.map(function(n) { | ||
return localizeNode(n, subContext); | ||
}); | ||
// don't leak spacing | ||
node[0].before = undefined; | ||
node[node.length - 1].after = undefined; | ||
} else { | ||
subContext = { | ||
global: context.global, | ||
inside: context.inside, | ||
lastWasSpacing: true, | ||
hasLocals: false, | ||
hasImplicitGlobals: false, | ||
explicit: context.explicit | ||
}; | ||
newNodes = node.nodes.map(function(n) { | ||
return localizeNode(n, subContext); | ||
}); | ||
node = Object.create(node); | ||
node.nodes = normalizeNodeArray(newNodes); | ||
} | ||
if(subContext.hasLocals) { | ||
context.hasLocals = true; | ||
} | ||
if(subContext.hasImplicitGlobals) { | ||
context.hasImplicitGlobals = true; | ||
} | ||
break; | ||
return newNode; | ||
}).filter(function(node) { | ||
return node !== null | ||
}); | ||
case "attribute": | ||
case "element": | ||
if(!context.global && !context.explicit) { | ||
context.hasImplicitGlobals = true; | ||
} | ||
break; | ||
case "id": | ||
case "class": | ||
if(!context.global) { | ||
node = { | ||
type: "nested-pseudo-class", | ||
name: "local", | ||
nodes: [node] | ||
}; | ||
context.hasLocals = true; | ||
} | ||
break; | ||
} | ||
// reset context | ||
context.lastWasSpacing = false; | ||
context.ignoreNextSpacing = false; | ||
context.enforceNoSpacing = false; | ||
return node; | ||
} | ||
function isGlobal(node) { | ||
return node.type === 'pseudo-class' && node.name === 'global'; | ||
function localizeDeclNode(node, context) { | ||
var newNode; | ||
switch(node.type) { | ||
case "item": | ||
if(context.localizeNextItem) { | ||
newNode = Object.create(node); | ||
newNode.name = ":local(" + newNode.name + ")"; | ||
context.localizeNextItem = false; | ||
return newNode; | ||
} | ||
break; | ||
case "url": | ||
if(context.options.rewriteUrl) { | ||
newNode = Object.create(node); | ||
newNode.url = context.options.rewriteUrl(context.global, node.url); | ||
return newNode; | ||
} | ||
break; | ||
} | ||
return node; | ||
} | ||
function isNestedGlobal(node) { | ||
return node.type === 'nested-pseudo-class' && node.name === 'global'; | ||
function localizeDeclValue(valueNode, context) { | ||
var newValueNode = Object.create(valueNode); | ||
newValueNode.nodes = valueNode.nodes.map(function(node) { | ||
return localizeDeclNode(node, context); | ||
}); | ||
return newValueNode; | ||
} | ||
function isNestedLocal(node) { | ||
return node.type === 'nested-pseudo-class' && node.name === 'local'; | ||
function localizeDecl(decl, context) { | ||
var valuesNode = Tokenizer.parseValues(decl.value); | ||
var localizeName = /animation(-name)?/.test(decl.prop); | ||
var newValuesNode = Object.create(valuesNode); | ||
newValuesNode.nodes = valuesNode.nodes.map(function(valueNode) { | ||
var subContext = { | ||
options: context.options, | ||
global: context.global, | ||
localizeNextItem: localizeName && !context.global | ||
}; | ||
return localizeDeclValue(valueNode, subContext); | ||
}); | ||
decl.value = Tokenizer.stringifyValues(newValuesNode); | ||
} | ||
module.exports = postcss.plugin('postcss-modules-local-by-default', function () { | ||
return function(css, result) { | ||
module.exports = postcss.plugin('postcss-modules-local-by-default', function (options) { | ||
if(options && options.mode) { | ||
if(options.mode !== "global" && options.mode !== "local" && options.mode !== "pure") { | ||
throw new Error("options.mode must be either 'global', 'local' or 'pure' (default 'local')"); | ||
} | ||
} | ||
var pureMode = options && options.mode === "pure"; | ||
var globalMode = options && options.mode === "global"; | ||
return function(css) { | ||
css.eachAtRule(function(atrule) { | ||
if(/keyframes$/.test(atrule.name)) { | ||
var globalMatch = /^\s*:global\s*\((.+)\)\s*$/.exec(atrule.params); | ||
var localMatch = /^\s*:local\s*\((.+)\)\s*$/.exec(atrule.params); | ||
if(globalMatch) { | ||
if(pureMode) { | ||
throw atrule.error("@keyframes :global(...) is not allowed in pure mode"); | ||
} | ||
atrule.params = globalMatch[1]; | ||
} else if(localMatch) { | ||
atrule.params = localMatch[0]; | ||
} else if(!globalMode) { | ||
atrule.params = ":local(" + atrule.params + ")"; | ||
} | ||
} | ||
}); | ||
css.eachRule(function(rule) { | ||
var selector = Tokenizer.parse(rule.selector); | ||
selector.nodes = localizeNodes(selector.nodes); | ||
rule.selector = Tokenizer.stringify(selector).trim(); | ||
var context = { | ||
options: options, | ||
global: globalMode, | ||
hasPureGlobals: false, | ||
hasPureImplicitGlobals: false | ||
}; | ||
var newSelector; | ||
try { | ||
newSelector = localizeNode(selector, context); | ||
} catch(e) { | ||
throw rule.error(e.message); | ||
} | ||
if(pureMode && context.hasPureGlobals) { | ||
throw rule.error("Selector '" + Tokenizer.stringify(selector) + "' is not pure " + | ||
"(pure selectors must contain at least one local class or id)"); | ||
} | ||
if(!globalMode && context.hasPureImplicitGlobals) { | ||
throw rule.error("Selector '" + Tokenizer.stringify(selector) + "' must be explicit flagged :global " + | ||
"(elsewise it would leak globally)"); | ||
} | ||
rule.nodes.forEach(function(decl) { | ||
localizeDecl(decl, context); | ||
}); | ||
rule.selector = Tokenizer.stringify(newSelector); | ||
}); | ||
}; | ||
}); |
{ | ||
"name": "postcss-modules-local-by-default", | ||
"version": "0.0.7", | ||
"version": "0.0.8", | ||
"description": "A CSS Modules transform to make local scope the default", | ||
@@ -18,12 +18,23 @@ "keywords": [ | ||
"dependencies": { | ||
"css-selector-tokenizer": "^0.3.1", | ||
"css-selector-tokenizer": "^0.4.0", | ||
"postcss": "^4.1.5" | ||
}, | ||
"devDependencies": { | ||
"chokidar-cli": "^0.2.1", | ||
"codecov.io": "^0.1.2", | ||
"coveralls": "^2.11.2", | ||
"eslint": "^0.22.1", | ||
"istanbul": "^0.3.14", | ||
"tape": "^4.0.0" | ||
}, | ||
"scripts": { | ||
"lint": "eslint index.js", | ||
"pretest": "npm run lint", | ||
"test": "tape test.js", | ||
"autotest": "chokidar index.js test.js -c 'npm test'", | ||
"precover": "npm run lint", | ||
"cover": "istanbul cover test.js", | ||
"travis": "npm run cover -- --report lcovonly", | ||
"prepublish": "npm prune && npm test" | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
[![Build Status][ci-img]][ci] [![npm][npm-img]][npm] | ||
[![Build Status][ci-img]][ci] [![codecov][codecov-img]][codecov] [![npm][npm-img]][npm] | ||
@@ -29,3 +29,3 @@ # CSS Modules: Local by Default | ||
## Development | ||
## Building | ||
@@ -37,2 +37,12 @@ ```bash | ||
- Build: [![Build Status][ci-img]][ci] | ||
- Lines: [![coveralls][coveralls-img]][coveralls] | ||
- Statements: [![codecov][codecov-img]][codecov] | ||
## Development | ||
```bash | ||
$ npm run autotest | ||
``` | ||
## License | ||
@@ -47,5 +57,12 @@ | ||
[ci-img]: https://img.shields.io/travis/css-modules/postcss-modules-local-by-default/master.svg?style=flat-square | ||
[ci]: https://travis-ci.org/css-modules/postcss-modules-local-by-default | ||
[npm-img]: https://img.shields.io/npm/v/postcss-modules-local-by-default.svg?style=flat-square | ||
[npm]: https://www.npmjs.com/package/postcss-modules-local-by-default | ||
--- | ||
Mark Dalgleish, 2015. | ||
[ci-img]: https://img.shields.io/travis/css-modules/postcss-modules-local-by-default/master.svg?style=flat-square | ||
[ci]: https://travis-ci.org/css-modules/postcss-modules-local-by-default | ||
[npm-img]: https://img.shields.io/npm/v/postcss-modules-local-by-default.svg?style=flat-square | ||
[npm]: https://www.npmjs.com/package/postcss-modules-local-by-default | ||
[coveralls-img]: https://img.shields.io/coveralls/css-modules/postcss-modules-local-by-default/master.svg?style=flat-square | ||
[coveralls]: https://coveralls.io/r/css-modules/postcss-modules-local-by-default?branch=master | ||
[codecov-img]: https://img.shields.io/codecov/c/github/css-modules/postcss-modules-local-by-default/master.svg?style=flat-square | ||
[codecov]: https://codecov.io/github/css-modules/postcss-modules-local-by-default?branch=master |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
14693
8
247
66
6
1
+ Addedcss-selector-tokenizer@0.4.1(transitive)
- Removedcss-selector-tokenizer@0.3.1(transitive)