0.2.0 - Mid migration

This commit is contained in:
Daniel Mason 2022-04-25 14:47:15 +12:00
parent 139e6a915e
commit 7e38fdbd7d
42393 changed files with 5358157 additions and 62 deletions

View file

@ -0,0 +1,118 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [3.0.3] - 2020-07-25
### Fixed
- treat `:import` and `:export` statements as pure
## [3.0.2] - 2019-06-05
### Fixed
- better handle invalid syntax
## [3.0.1] - 2019-05-16
### Fixed
- adds safety check before accessing "rule parent"
## [3.0.0] - 2019-05-07
### Features
- don't localize imported values in selectors
### Changes
- don't localize imported values in selectors
## [2.0.6] - 2019-03-05
### Fixed
- handles properly selector with escaping characters (like: `.\31 a2b3c { color: red }`)
## [2.0.5] - 2019-02-06
### Fixed
- Path to `index.js`
## [2.0.4] - 2019-01-04
### Fixed
- Inappropriate modification of `steps` function arguments
## [2.0.3] - 2018-12-21
### Fixed
- Don't modify inappropriate animation keywords
## [2.0.2] - 2018-12-05
### Fixed
- Don't break unicode characters.
## [2.0.1] - 2018-11-23
### Fixed
- Handle uppercase `keyframes` at rule.
## [2.0.0] - 2018-11-23
### Changed
- Drop support `nodejs@4`.
- Update `postcss` version to `7`.
## [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
[2.0.0]: https://github.com/postcss-modules-local-by-default/compare/v1.3.1...v2.0.0
[2.0.1]: https://github.com/postcss-modules-local-by-default/compare/v2.0.0...v2.0.1

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright 2015 Mark Dalgleish <mark.john.dalgleish@gmail.com>
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.

View file

@ -0,0 +1,65 @@
[![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

View file

@ -0,0 +1,549 @@
'use strict';
const postcss = require('postcss');
const selectorParser = require('postcss-selector-parser');
const valueParser = require('postcss-value-parser');
const { extractICSS } = require('icss-utils');
const isSpacing = node => node.type === 'combinator' && node.value === ' ';
function getImportLocalAliases(icssImports) {
const localAliases = new Map();
Object.keys(icssImports).forEach(key => {
Object.keys(icssImports[key]).forEach(prop => {
localAliases.set(prop, icssImports[key][prop]);
});
});
return localAliases;
}
function maybeLocalizeValue(value, localAliasMap) {
if (localAliasMap.has(value)) return value;
}
function normalizeNodeArray(nodes) {
const 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 && isSpacing(array[array.length - 1])) {
array.pop();
}
return array;
}
function localizeNode(rule, mode, localAliasMap) {
const isScopePseudo = node =>
node.value === ':local' || node.value === ':global';
const isImportExportPseudo = node =>
node.value === ':import' || node.value === ':export';
const transform = (node, context) => {
if (context.ignoreNextSpacing && !isSpacing(node)) {
throw new Error('Missing whitespace after ' + context.ignoreNextSpacing);
}
if (context.enforceNoSpacing && isSpacing(node)) {
throw new Error('Missing whitespace before ' + context.enforceNoSpacing);
}
let newNodes;
switch (node.type) {
case 'root': {
let resultingGlobal;
context.hasPureGlobals = false;
newNodes = node.nodes.map(function(n) {
const nContext = {
global: context.global,
lastWasSpacing: true,
hasLocals: false,
explicit: false,
};
n = transform(n, nContext);
if (typeof resultingGlobal === 'undefined') {
resultingGlobal = nContext.global;
} else if (resultingGlobal !== nContext.global) {
throw new Error(
'Inconsistent rule global/local result in rule "' +
node +
'" (multiple selectors must result in the same mode for the rule)'
);
}
if (!nContext.hasLocals) {
context.hasPureGlobals = true;
}
return n;
});
context.global = resultingGlobal;
node.nodes = normalizeNodeArray(newNodes);
break;
}
case 'selector': {
newNodes = node.map(childNode => transform(childNode, context));
node = node.clone();
node.nodes = normalizeNodeArray(newNodes);
break;
}
case 'combinator': {
if (isSpacing(node)) {
if (context.ignoreNextSpacing) {
context.ignoreNextSpacing = false;
context.lastWasSpacing = false;
context.enforceNoSpacing = false;
return null;
}
context.lastWasSpacing = true;
return node;
}
break;
}
case 'pseudo': {
let childContext;
const isNested = !!node.length;
const isScoped = isScopePseudo(node);
const isImportExport = isImportExportPseudo(node);
if (isImportExport) {
context.hasLocals = true;
// :local(.foo)
} else if (isNested) {
if (isScoped) {
if (node.nodes.length === 0) {
throw new Error(`${node.value}() can't be empty`);
}
if (context.inside) {
throw new Error(
`A ${node.value} is not allowed inside of a ${
context.inside
}(...)`
);
}
childContext = {
global: node.value === ':global',
inside: node.value,
hasLocals: false,
explicit: true,
};
newNodes = node
.map(childNode => transform(childNode, childContext))
.reduce((acc, next) => acc.concat(next.nodes), []);
if (newNodes.length) {
const { before, after } = node.spaces;
const first = newNodes[0];
const last = newNodes[newNodes.length - 1];
first.spaces = { before, after: first.spaces.after };
last.spaces = { before: last.spaces.before, after };
}
node = newNodes;
break;
} else {
childContext = {
global: context.global,
inside: context.inside,
lastWasSpacing: true,
hasLocals: false,
explicit: context.explicit,
};
newNodes = node.map(childNode =>
transform(childNode, childContext)
);
node = node.clone();
node.nodes = normalizeNodeArray(newNodes);
if (childContext.hasLocals) {
context.hasLocals = true;
}
}
break;
//:local .foo .bar
} else if (isScoped) {
if (context.inside) {
throw new Error(
`A ${node.value} is not allowed inside of a ${
context.inside
}(...)`
);
}
const addBackSpacing = !!node.spaces.before;
context.ignoreNextSpacing = context.lastWasSpacing
? node.value
: false;
context.enforceNoSpacing = context.lastWasSpacing
? false
: node.value;
context.global = node.value === ':global';
context.explicit = true;
// because this node has spacing that is lost when we remove it
// we make up for it by adding an extra combinator in since adding
// spacing on the parent selector doesn't work
return addBackSpacing
? selectorParser.combinator({ value: ' ' })
: null;
}
break;
}
case 'id':
case 'class': {
if (!node.value) {
throw new Error('Invalid class or id selector syntax');
}
if (context.global) {
break;
}
const isImportedValue = localAliasMap.has(node.value);
const isImportedWithExplicitScope = isImportedValue && context.explicit;
if (!isImportedValue || isImportedWithExplicitScope) {
const innerNode = node.clone();
innerNode.spaces = { before: '', after: '' };
node = selectorParser.pseudo({
value: ':local',
nodes: [innerNode],
spaces: node.spaces,
});
context.hasLocals = true;
}
break;
}
}
context.lastWasSpacing = false;
context.ignoreNextSpacing = false;
context.enforceNoSpacing = false;
return node;
};
const rootContext = {
global: mode === 'global',
hasPureGlobals: false,
};
rootContext.selector = selectorParser(root => {
transform(root, rootContext);
}).processSync(rule, { updateSelector: false, lossless: true });
return rootContext;
}
function localizeDeclNode(node, context) {
switch (node.type) {
case 'word':
if (context.localizeNextItem) {
if (!context.localAliasMap.has(node.value)) {
node.value = ':local(' + node.value + ')';
context.localizeNextItem = false;
}
}
break;
case 'function':
if (
context.options &&
context.options.rewriteUrl &&
node.value.toLowerCase() === 'url'
) {
node.nodes.map(nestedNode => {
if (nestedNode.type !== 'string' && nestedNode.type !== 'word') {
return;
}
let newUrl = context.options.rewriteUrl(
context.global,
nestedNode.value
);
switch (nestedNode.type) {
case 'string':
if (nestedNode.quote === "'") {
newUrl = newUrl.replace(/(\\)/g, '\\$1').replace(/'/g, "\\'");
}
if (nestedNode.quote === '"') {
newUrl = newUrl.replace(/(\\)/g, '\\$1').replace(/"/g, '\\"');
}
break;
case 'word':
newUrl = newUrl.replace(/("|'|\)|\\)/g, '\\$1');
break;
}
nestedNode.value = newUrl;
});
}
break;
}
return node;
}
function isWordAFunctionArgument(wordNode, functionNode) {
return functionNode
? functionNode.nodes.some(
functionNodeChild =>
functionNodeChild.sourceIndex === wordNode.sourceIndex
)
: false;
}
function localizeAnimationShorthandDeclValues(decl, context) {
const 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.
*/
const 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,
};
const didParseAnimationName = false;
let parsedAnimationKeywords = {};
let stepsFunctionNode = null;
const valueNodes = valueParser(decl.value).walk(node => {
/* If div-token appeared (represents as comma ','), a possibility of an animation-keywords should be reflesh. */
if (node.type === 'div') {
parsedAnimationKeywords = {};
}
if (node.type === 'function' && node.value.toLowerCase() === 'steps') {
stepsFunctionNode = node;
}
const value =
node.type === 'word' && !isWordAFunctionArgument(node, stepsFunctionNode)
? node.value.toLowerCase()
: null;
let 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;
}
}
const subContext = {
options: context.options,
global: context.global,
localizeNextItem: shouldParseAnimationName && !context.global,
localAliasMap: context.localAliasMap,
};
return localizeDeclNode(node, subContext);
});
decl.value = valueNodes.toString();
}
function localizeDeclValues(localize, decl, context) {
const valueNodes = valueParser(decl.value);
valueNodes.walk((node, index, nodes) => {
const subContext = {
options: context.options,
global: context.global,
localizeNextItem: localize && !context.global,
localAliasMap: context.localAliasMap,
};
nodes[index] = localizeDeclNode(node, subContext);
});
decl.value = valueNodes.toString();
}
function localizeDecl(decl, context) {
const isAnimation = /animation$/i.test(decl.prop);
if (isAnimation) {
return localizeAnimationShorthandDeclValues(decl, context);
}
const isAnimationName = /animation(-name)?$/i.test(decl.prop);
if (isAnimationName) {
return localizeDeclValues(true, decl, context);
}
const hasUrl = /url\(/i.test(decl.value);
if (hasUrl) {
return localizeDeclValues(false, 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")'
);
}
}
const pureMode = options && options.mode === 'pure';
const globalMode = options && options.mode === 'global';
return function(css) {
const { icssImports } = extractICSS(css, false);
const localAliasMap = getImportLocalAliases(icssImports);
css.walkAtRules(function(atrule) {
if (/keyframes$/i.test(atrule.name)) {
const globalMatch = /^\s*:global\s*\((.+)\)\s*$/.exec(atrule.params);
const localMatch = /^\s*:local\s*\((.+)\)\s*$/.exec(atrule.params);
let 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) {
if (atrule.params && !localAliasMap.has(atrule.params))
atrule.params = ':local(' + atrule.params + ')';
}
atrule.walkDecls(function(decl) {
localizeDecl(decl, {
localAliasMap,
options: options,
global: globalKeyframes,
});
});
} else if (atrule.nodes) {
atrule.nodes.forEach(function(decl) {
if (decl.type === 'decl') {
localizeDecl(decl, {
localAliasMap,
options: options,
global: globalMode,
});
}
});
}
});
css.walkRules(function(rule) {
if (
rule.parent &&
rule.parent.type === 'atrule' &&
/keyframes$/i.test(rule.parent.name)
) {
// ignore keyframe rules
return;
}
if (
rule.nodes &&
rule.selector.slice(0, 2) === '--' &&
rule.selector.slice(-1) === ':'
) {
// ignore custom property set
return;
}
const context = localizeNode(rule, options.mode, localAliasMap);
context.options = options;
context.localAliasMap = localAliasMap;
if (pureMode && context.hasPureGlobals) {
throw rule.error(
'Selector "' +
rule.selector +
'" is not pure ' +
'(pure selectors must contain at least one local class or id)'
);
}
rule.selector = context.selector;
// Less-syntax mixins parse as rules with no nodes
if (rule.nodes) {
rule.nodes.forEach(decl => localizeDecl(decl, context));
}
});
};
});

View file

@ -0,0 +1,52 @@
{
"name": "postcss-modules-local-by-default",
"version": "3.0.3",
"description": "A CSS Modules transform to make local scope the default",
"main": "index.js",
"engines": {
"node": ">= 6"
},
"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"
},
"prettier": {
"singleQuote": true,
"trailingComma": "es5"
},
"dependencies": {
"icss-utils": "^4.1.1",
"postcss": "^7.0.32",
"postcss-selector-parser": "^6.0.2",
"postcss-value-parser": "^4.1.0"
},
"devDependencies": {
"chokidar-cli": "^1.2.3",
"codecov.io": "^0.1.6",
"coveralls": "^3.1.0",
"eslint": "^5.16.0",
"istanbul": "^0.4.5",
"tape": "^5.0.1"
},
"scripts": {
"lint": "eslint index.js test.js",
"pretest": "yarn lint",
"test": "tape test.js",
"autotest": "chokidar index.js test.js -c 'yarn test'",
"precover": "yarn lint",
"cover": "istanbul cover test.js",
"travis": "yarn lint && yarn cover -- --report lcovonly",
"prepublish": "yarn test"
},
"files": [
"index.js"
]
}