postcss-modules-local-by-default/0000755000175000017500000000000013624163411016723 5ustar xavierxavierpostcss-modules-local-by-default/LICENSE0000644000175000017500000000212513624163411017730 0ustar xavierxavierThe MIT License (MIT) Copyright 2015 Mark Dalgleish Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. postcss-modules-local-by-default/CHANGELOG.md0000644000175000017500000000520413624163411020535 0ustar xavierxavier# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased][unreleased] ### Changed - Nothing yet. ## [0.0.11] - 2015-07-19 ### Fixed - Localisation of animation properties. ## [0.0.10] - 2015-06-17 ### Added - Localised at-rules. ## [0.0.9] - 2015-06-12 ### Changed - Using global selectors outside of a global context no longer triggers warnings. Instead, this functionality will be provided by a CSS Modules linter. ### Fixed - Keyframe rules. ## [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 ### Changed - Migrated to `css-selector-tokenizer`. ## [0.0.6] - 2015-05-28 ### Changed - Renamed project to `postcss-modules-local-by-default`. ## [0.0.5] - 2015-05-22 ### Added - Support for css-loader [inheritance](https://github.com/webpack/css-loader#inheriting) and [local imports](https://github.com/webpack/css-loader#importing-local-class-names). ## [0.0.4] - 2015-05-22 ### Changed - Hide global leak detection behind undocumented `lint` option until it's more robust. ## [0.0.3] - 2015-05-22 ### Changed - Transformer output now uses the new `:local(.identifier)` syntax. ### Added - Simple global leak detection. Non-local selectors like `input{}` and `[data-foobar]` now throw when not marked as global. ## [0.0.2] - 2015-05-14 ### Added - Support for global selectors appended directly to locals, e.g. `.foo:global(.bar)` ## 0.0.1 - 2015-05-12 ### Added - Automatic local classes - Explicit global selectors with `:global` [unreleased]: https://github.com/postcss-modules-local-by-default/compare/v0.0.10...HEAD [0.0.2]: https://github.com/postcss-modules-local-by-default/compare/v0.0.1...v0.0.2 [0.0.3]: https://github.com/postcss-modules-local-by-default/compare/v0.0.2...v0.0.3 [0.0.4]: https://github.com/postcss-modules-local-by-default/compare/v0.0.3...v0.0.4 [0.0.5]: https://github.com/postcss-modules-local-by-default/compare/v0.0.4...v0.0.5 [0.0.6]: https://github.com/postcss-modules-local-by-default/compare/v0.0.5...v0.0.6 [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 [0.0.9]: https://github.com/postcss-modules-local-by-default/compare/v0.0.8...v0.0.9 [0.0.10]: https://github.com/postcss-modules-local-by-default/compare/v0.0.9...v0.0.10 [0.0.11]: https://github.com/postcss-modules-local-by-default/compare/v0.0.10...v0.0.11 postcss-modules-local-by-default/package.json0000644000175000017500000000222213624163411021207 0ustar xavierxavier{ "name": "postcss-modules-local-by-default", "version": "1.2.0", "description": "A CSS Modules transform to make local scope the default", "keywords": [ "css-modules", "postcss", "css", "postcss-plugin" ], "author": "Mark Dalgleish", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/css-modules/postcss-modules-local-by-default.git" }, "dependencies": { "css-selector-tokenizer": "^0.7.0", "postcss": "^6.0.1" }, "devDependencies": { "chokidar-cli": "^1.0.1", "codecov.io": "^0.1.2", "coveralls": "^2.11.2", "eslint": "^3.19.0", "istanbul": "^0.4.5", "tape": "^4.0.0" }, "scripts": { "lint": "eslint index.js test.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", "publish-patch": "npm prune && npm test && npm version patch && git push && git push --tags && npm publish" }, "files": [ "index.js" ] } postcss-modules-local-by-default/index.js0000644000175000017500000002626113624163411020377 0ustar xavierxaviervar postcss = require('postcss'); var Tokenizer = require('css-selector-tokenizer'); 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; } 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); } var newNodes; switch(node.type) { case 'selectors': var resultingGlobal; context.hasPureGlobals = false; newNodes = node.nodes.map(function(n) { var nContext = { global: context.global, lastWasSpacing: true, hasLocals: 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; } 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; 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; 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, 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, 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; } 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 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 'nested-item': var newNodes = node.nodes.map(function(n) { return localizeDeclValue(n, context); }); node = Object.create(node); node.nodes = newNodes; break; case 'url': if(context.options && context.options.rewriteUrl) { newNode = Object.create(node); newNode.url = context.options.rewriteUrl(context.global, node.url); return newNode; } break; } return node; } function localizeDeclValue(valueNode, context) { var newValueNode = Object.create(valueNode); newValueNode.nodes = valueNode.nodes.map(function(node) { return localizeDeclNode(node, context); }); return newValueNode; } function localizeAnimationShorthandDeclValueNodes(nodes, context) { var validIdent = validIdent = /^-?[_a-z][_a-z0-9-]*$/i; /* The spec defines some keywords that you can use to describe properties such as the timing function. These are still valid animation names, so as long as there is a property that accepts a keyword, it is given priority. Only when all the properties that can take a keyword are exhausted can the animation name be set to the keyword. I.e. animation: infinite infinite; The animation will repeat an infinite number of times from the first argument, and will have an animation name of infinite from the second. */ var animationKeywords = { '$alternate': 1, '$alternate-reverse': 1, '$backwards': 1, '$both': 1, '$ease': 1, '$ease-in': 1, '$ease-in-out': 1, '$ease-out': 1, '$forwards': 1, '$infinite': 1, '$linear': 1, '$none': Infinity, // No matter how many times you write none, it will never be an animation name '$normal': 1, '$paused': 1, '$reverse': 1, '$running': 1, '$step-end': 1, '$step-start': 1, '$initial': Infinity, '$inherit': Infinity, '$unset': Infinity, }; var didParseAnimationName = false; var parsedAnimationKeywords = {}; return nodes.map(function(valueNode) { var value = valueNode.type === 'item' ? valueNode.name.toLowerCase() : null; var shouldParseAnimationName = false; if (!didParseAnimationName && value && validIdent.test(value)) { if ('$' + value in animationKeywords) { parsedAnimationKeywords['$' + value] = ('$' + value in parsedAnimationKeywords) ? (parsedAnimationKeywords['$' + value] + 1) : 0; shouldParseAnimationName = (parsedAnimationKeywords['$' + value] >= animationKeywords['$' + value]); } else { shouldParseAnimationName = true; } } var subContext = { options: context.options, global: context.global, localizeNextItem: shouldParseAnimationName && !context.global }; return localizeDeclNode(valueNode, subContext); }); } function localizeAnimationShorthandDeclValues(valuesNode, decl, context) { var newValuesNode = Object.create(valuesNode); newValuesNode.nodes = valuesNode.nodes.map(function(valueNode, index) { var newValueNode = Object.create(valueNode); newValueNode.nodes = localizeAnimationShorthandDeclValueNodes(valueNode.nodes, context); return newValueNode; }); decl.value = Tokenizer.stringifyValues(newValuesNode); } function localizeDeclValues(localize, valuesNode, decl, context) { var newValuesNode = Object.create(valuesNode); newValuesNode.nodes = valuesNode.nodes.map(function(valueNode) { var subContext = { options: context.options, global: context.global, localizeNextItem: localize && !context.global }; return localizeDeclValue(valueNode, subContext); }); decl.value = Tokenizer.stringifyValues(newValuesNode); } function localizeDecl(decl, context) { var valuesNode = Tokenizer.parseValues(decl.value); var isAnimation = /animation?$/.test(decl.prop); if (isAnimation) return localizeAnimationShorthandDeclValues(valuesNode, decl, context); var isAnimationName = /animation(-name)?$/.test(decl.prop); if (isAnimationName) return localizeDeclValues(true, valuesNode, decl, context); return localizeDeclValues(false, valuesNode, decl, context); } module.exports = postcss.plugin('postcss-modules-local-by-default', function (options) { if (typeof options !== 'object') { options = {}; // If options is undefined or not an object the plugin fails } 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.walkAtRules(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); var globalKeyframes = globalMode; if(globalMatch) { if(pureMode) { throw atrule.error('@keyframes :global(...) is not allowed in pure mode'); } atrule.params = globalMatch[1]; globalKeyframes = true; } else if(localMatch) { atrule.params = localMatch[0]; globalKeyframes = false; } else if(!globalMode) { atrule.params = ':local(' + atrule.params + ')'; } atrule.walkDecls(function(decl) { localizeDecl(decl, { options: options, global: globalKeyframes }); }); } else if(atrule.nodes) { atrule.nodes.forEach(function(decl) { if(decl.type === 'decl') { localizeDecl(decl, { options: options, global: globalMode }); } }); } }); css.walkRules(function(rule) { if(rule.parent.type === 'atrule' && /keyframes$/.test(rule.parent.name)) { // ignore keyframe rules return; } var selector = Tokenizer.parse(rule.selector); var context = { options: options, global: globalMode, hasPureGlobals: 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)'); } // Less-syntax mixins parse as rules with no nodes if (rule.nodes) { rule.nodes.forEach(function(decl) { localizeDecl(decl, context); }); } rule.selector = Tokenizer.stringify(newSelector); }); }; }); postcss-modules-local-by-default/README.md0000644000175000017500000000365513624163411020213 0ustar xavierxavier[![Build Status][ci-img]][ci] [![codecov][codecov-img]][codecov] [![npm][npm-img]][npm] # CSS Modules: Local by Default Transformation examples: ```css .foo { ... } /* => */ :local(.foo) { ... } .foo .bar { ... } /* => */ :local(.foo) :local(.bar) { ... } /* Shorthand global selector */ :global .foo .bar { ... } /* => */ .foo .bar { ... } .foo :global .bar { ... } /* => */ :local(.foo) .bar { ... } /* Targeted global selector */ :global(.foo) .bar { ... } /* => */ .foo :local(.bar) { ... } .foo:global(.bar) { ... } /* => */ :local(.foo).bar { ... } .foo :global(.bar) .baz { ... } /* => */ :local(.foo) .bar :local(.baz) { ... } .foo:global(.bar) .baz { ... } /* => */ :local(.foo).bar :local(.baz) { ... } ``` ## Building ```bash $ npm install $ npm test ``` - Build: [![Build Status][ci-img]][ci] - Lines: [![coveralls][coveralls-img]][coveralls] - Statements: [![codecov][codecov-img]][codecov] ## Development ```bash $ npm run autotest ``` ## License MIT ## With thanks - [Tobias Koppers](https://github.com/sokra) - [Glen Maddern](https://github.com/geelen) --- 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