Skip to content

chore: migrate to postcss-selector-parser #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,21 @@
"url": "https://github.com/css-modules/postcss-modules-scope/issues"
},
"homepage": "https://github.com/css-modules/postcss-modules-scope",
"prettier": {
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
},
"dependencies": {
"css-selector-tokenizer": "^0.7.0",
"postcss": "^7.0.6"
"postcss": "^7.0.6",
"postcss-selector-parser": "^5.0.0"
},
"devDependencies": {
"chokidar-cli": "^1.0.1",
"codecov.io": "^0.1.2",
"coveralls": "^3.0.2",
"css-selector-parser": "^1.0.4",
"eslint": "^5.9.0",
"nyc": "^13.1.0",
"mocha": "^5.2.0"
"mocha": "^5.2.0",
"nyc": "^13.1.0"
}
}
85 changes: 45 additions & 40 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,53 @@
'use strict';

const postcss = require('postcss');
const Tokenizer = require('css-selector-tokenizer');
const selectorParser = require('postcss-selector-parser');

const hasOwnProperty = Object.prototype.hasOwnProperty;

function getSingleLocalNamesForComposes(selectors) {
return selectors.nodes.map(node => {
function getSingleLocalNamesForComposes(root) {
return root.nodes.map(node => {
if (node.type !== 'selector' || node.nodes.length !== 1) {
throw new Error(
'composition is only allowed when selector is single :local class name not in "' +
Tokenizer.stringify(selectors) +
'"'
`composition is only allowed when selector is single :local class name not in "${root}"`
);
}
node = node.nodes[0];
if (
node.type !== 'nested-pseudo-class' ||
node.name !== 'local' ||
node.type !== 'pseudo' ||
node.value !== ':local' ||
node.nodes.length !== 1
) {
throw new Error(
'composition is only allowed when selector is single :local class name not in "' +
Tokenizer.stringify(selectors) +
root +
'", "' +
Tokenizer.stringify(node) +
node +
'" is weird'
);
}
node = node.nodes[0];
if (node.type !== 'selector' || node.nodes.length !== 1) {
node = node.first;
if (node.type !== 'selector' || node.length !== 1) {
throw new Error(
'composition is only allowed when selector is single :local class name not in "' +
Tokenizer.stringify(selectors) +
root +
'", "' +
Tokenizer.stringify(node) +
node +
'" is weird'
);
}
node = node.nodes[0];
node = node.first;
if (node.type !== 'class') {
// 'id' is not possible, because you can't compose ids
throw new Error(
'composition is only allowed when selector is single :local class name not in "' +
Tokenizer.stringify(selectors) +
root +
'", "' +
Tokenizer.stringify(node) +
node +
'" is weird'
);
}
return node.name;
return node.value;
});
}

Expand All @@ -74,40 +72,45 @@ const processor = postcss.plugin('postcss-modules-scope', function(options) {
}

function localizeNode(node) {
const newNode = Object.create(node);
switch (node.type) {
case 'selector':
newNode.nodes = node.nodes.map(localizeNode);
return newNode;
node.nodes = node.map(localizeNode);
return node;
case 'class':
return selectorParser.className({
value: exportScopedName(node.value),
});
case 'id': {
newNode.name = exportScopedName(node.name);
return newNode;
return selectorParser.id({
value: exportScopedName(node.value),
});
}
}
throw new Error(
node.type +
' ("' +
Tokenizer.stringify(node) +
'") is not allowed in a :local block'
`${node.type} ("${node}") is not allowed in a :local block`
);
}

function traverseNode(node) {
switch (node.type) {
case 'nested-pseudo-class':
if (node.name === 'local') {
case 'pseudo':
if (node.value === ':local') {
if (node.nodes.length !== 1) {
throw new Error('Unexpected comma (",") in :local block');
}
return localizeNode(node.nodes[0]);
const selector = localizeNode(node.first, node.spaces);
// move the spaces that were around the psuedo selector to the first
// non-container node
selector.first.spaces = node.spaces;

node.replaceWith(selector);
return;
}
/* falls through */
case 'selectors':
case 'root':
case 'selector': {
const newNode = Object.create(node);
newNode.nodes = node.nodes.map(traverseNode);
return newNode;
node.each(traverseNode);
break;
}
}
return node;
Expand All @@ -125,14 +128,16 @@ const processor = postcss.plugin('postcss-modules-scope', function(options) {

// Find any :local classes
css.walkRules(rule => {
const selector = Tokenizer.parse(rule.selector);
const newSelector = traverseNode(selector);
rule.selector = Tokenizer.stringify(newSelector);
let parsedSelector = selectorParser().astSync(rule);

rule.selector = traverseNode(parsedSelector.clone()).toString();
// console.log(rule.selector);
rule.walkDecls(/composes|compose-with/, decl => {
const localNames = getSingleLocalNamesForComposes(selector);
const localNames = getSingleLocalNamesForComposes(parsedSelector);
const classes = decl.value.split(/\s+/);
classes.forEach(className => {
const global = /^global\(([^\)]+)\)$/.exec(className);

if (global) {
localNames.forEach(exportedName => {
exports[exportedName].push(global[1]);
Expand Down Expand Up @@ -196,7 +201,7 @@ const processor = postcss.plugin('postcss-modules-scope', function(options) {
exportRule.append({
prop: exportedName,
value: exports[exportedName].join(' '),
raws: { before: '\n ' }
raws: { before: '\n ' },
})
);
css.append(exportRule);
Expand All @@ -209,7 +214,7 @@ processor.generateScopedName = function(exportedName, path) {
.replace(/\.[^\.\/\\]+$/, '')
.replace(/[\W_]+/g, '_')
.replace(/^_|_$/g, '');
return `_${sanitisedPath}__${exportedName}`;
return `_${sanitisedPath}__${exportedName}`.trim();
};

module.exports = processor;
39 changes: 28 additions & 11 deletions test/test-cases.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,48 @@ var generateScopedName = processor.generateScopedName;
describe('test-cases', function() {
var testDir = path.join(__dirname, 'test-cases');
fs.readdirSync(testDir).forEach(function(testCase) {
if(fs.existsSync(path.join(testDir, testCase, 'source.css'))) {
if (fs.existsSync(path.join(testDir, testCase, 'source.css'))) {
it('should ' + testCase.replace(/-/g, ' '), function() {
var input = normalize(fs.readFileSync(path.join(testDir, testCase, 'source.css'), 'utf-8'));
var input = normalize(
fs.readFileSync(path.join(testDir, testCase, 'source.css'), 'utf-8')
);

var expected, expectedError;
if(fs.existsSync(path.join(testDir, testCase, 'expected.error.txt'))) {
expectedError = normalize(fs.readFileSync(path.join(testDir, testCase, 'expected.error.txt'), 'utf-8'))
.split('\n')[0];
if (fs.existsSync(path.join(testDir, testCase, 'expected.error.txt'))) {
expectedError = normalize(
fs.readFileSync(
path.join(testDir, testCase, 'expected.error.txt'),
'utf-8'
)
).split('\n')[0];
} else {
expected = normalize(fs.readFileSync(path.join(testDir, testCase, 'expected.css'), 'utf-8'));
expected = normalize(
fs.readFileSync(
path.join(testDir, testCase, 'expected.css'),
'utf-8'
)
);
}
var config = { from: '/input' };
var options = {
generateScopedName: function(exportedName, inputPath) {
var normalizedPath = inputPath.replace(/^[A-Z]:/, '');
return generateScopedName(exportedName, normalizedPath);
}
},
};
if(fs.existsSync(path.join(testDir, testCase, 'config.json'))) {
config = JSON.parse(fs.readFileSync(path.join(testDir, testCase, 'config.json'), 'utf-8'));
if (fs.existsSync(path.join(testDir, testCase, 'config.json'))) {
config = JSON.parse(
fs.readFileSync(
path.join(testDir, testCase, 'config.json'),
'utf-8'
)
);
}
if(fs.existsSync(path.join(testDir, testCase, 'options.js'))) {
if (fs.existsSync(path.join(testDir, testCase, 'options.js'))) {
options = require(path.join(testDir, testCase, 'options.js'));
}
var pipeline = postcss([generateInvalidCSS, processor(options)]);
if(expectedError) {
if (expectedError) {
assert.throws(function() {
// eslint-ignore-next-line no-unused-vars
const result = pipeline.process(input, config).css;
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
element \("body"\) is not allowed in a :local block
tag \("body"\) is not allowed in a :local block
4 changes: 2 additions & 2 deletions test/test-cases/options-generateScopedName/options.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
generateScopedName: function(name, path) {
return '_' + name + '_';
}
return '_' + name + '_';
},
};
68 changes: 23 additions & 45 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -541,21 +541,10 @@ cryptiles@0.2.x:
dependencies:
boom "0.4.x"

css-selector-parser@^1.0.4:
version "1.3.0"
resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.3.0.tgz#5f1ad43e2d8eefbfdc304fcd39a521664943e3eb"

css-selector-tokenizer@^0.7.0:
version "0.7.1"
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz#a177271a8bca5019172f4f891fc6eed9cbf68d5d"
dependencies:
cssesc "^0.1.0"
fastparse "^1.1.1"
regexpu-core "^1.0.0"

cssesc@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
cssesc@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==

ctype@0.5.3:
version "0.5.3"
Expand Down Expand Up @@ -873,10 +862,6 @@ fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"

fastparse@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"

figures@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
Expand Down Expand Up @@ -1175,6 +1160,11 @@ imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"

indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=

inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
Expand Down Expand Up @@ -1431,10 +1421,6 @@ jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"

jsesc@~0.5.0:
version "0.5.0"
resolved "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"

json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
Expand Down Expand Up @@ -2031,6 +2017,15 @@ posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"

postcss-selector-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==
dependencies:
cssesc "^2.0.0"
indexes-of "^1.0.1"
uniq "^1.0.1"

postcss@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.6.tgz#6dcaa1e999cdd4a255dcd7d4d9547f4ca010cdc2"
Expand Down Expand Up @@ -2128,10 +2123,6 @@ readdirp@^2.0.0:
micromatch "^3.1.10"
readable-stream "^2.0.2"

regenerate@^1.2.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"

regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
Expand All @@ -2143,24 +2134,6 @@ regexpp@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"

regexpu-core@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
dependencies:
regenerate "^1.2.1"
regjsgen "^0.2.0"
regjsparser "^0.1.4"

regjsgen@^0.2.0:
version "0.2.0"
resolved "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"

regjsparser@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
dependencies:
jsesc "~0.5.0"

release-zalgo@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730"
Expand Down Expand Up @@ -2681,6 +2654,11 @@ union-value@^1.0.0:
is-extendable "^0.1.1"
set-value "^0.4.3"

uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=

unset-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
Expand Down