Skip to content

Commit be26716

Browse files
committed
use postcss-selector-parser
- postcss-selector-parser prevents various issues with selectors breaking - updated dev dependencies - switched to github workflows for CI - switched to tabs for source code a11y
1 parent 5032b4e commit be26716

File tree

11 files changed

+1838
-2125
lines changed

11 files changed

+1838
-2125
lines changed

plugins/postcss-selector-not/.babelrc

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
2-
"presets": [
3-
["@babel/preset-env", {
4-
"targets": {
5-
"node": 10
6-
}
7-
}]
8-
],
9-
"plugins": [
10-
]
2+
"presets": [
3+
["@babel/preset-env", {
4+
"targets": {
5+
"node": 10
6+
}
7+
}]
8+
],
9+
"plugins": [
10+
]
1111
}

plugins/postcss-selector-not/.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ end_of_line = lf
55
charset = utf-8
66
trim_trailing_whitespace = true
77
insert_final_newline = true
8+
indent_style = tab
9+
10+
[*.yml]
811
indent_style = space
912
indent_size = 2
1013

plugins/postcss-selector-not/.eslintignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

plugins/postcss-selector-not/.eslintrc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ parserOptions:
66
sourceType: "module"
77

88
rules:
9-
indent: [2, 2] # 2 spaces indentation
9+
indent: [2, "tab"] # 2 tab indentation
1010
max-len: [2, 80, 4]
1111
quotes: [2, "double"]
1212
semi: [2, "never"]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: test
2+
on:
3+
push:
4+
branches:
5+
- 'master'
6+
pull_request:
7+
8+
concurrency:
9+
group: branch-node-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
test:
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [ubuntu-latest]
19+
node: [10, 12, 14, 18]
20+
21+
steps:
22+
- uses: actions/checkout@v3
23+
- uses: actions/setup-node@v3
24+
with:
25+
node-version: ${{ matrix.node }}
26+
27+
- run: yarn install
28+
- run: yarn test

plugins/postcss-selector-not/.travis.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

plugins/postcss-selector-not/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Unreleased (TODO: update this before release)
2+
3+
- Fixed: doesn't consider attribute selectors ([#23](https://github.com/postcss/postcss-selector-not/issues/23))
4+
- Fixed: unexpected results when `:not` is not a pseudo class function ([#28](https://github.com/postcss/postcss-selector-not/issues/28))
5+
16
# 5.0.0 - 2021-01-31
27

38
- Added: Support for PostCSS v8.

plugins/postcss-selector-not/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020
"dist"
2121
],
2222
"dependencies": {
23-
"balanced-match": "^1.0.0"
23+
"postcss-selector-parser": "^6.0.10"
2424
},
2525
"peerDependencies": {
2626
"postcss": "^8.1.0"
2727
},
2828
"devDependencies": {
29-
"@babel/core": "^7.11.6",
3029
"@babel/cli": "^7.11.6",
30+
"@babel/core": "^7.11.6",
3131
"@babel/preset-env": "^7.11.5",
3232
"@babel/register": "^7.11.5",
3333
"eslint": "^7.9.0",
Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,74 @@
1-
import list from "postcss/lib/list"
1+
import parser from "postcss-selector-parser"
22

3-
import balancedMatch from "balanced-match"
3+
function cleanupWhitespace(node) {
4+
if (node.spaces) {
5+
node.spaces.after = ""
6+
node.spaces.before = ""
7+
}
48

5-
function explodeSelector(pseudoClass, selector) {
6-
const position = locatePseudoClass(selector, pseudoClass)
7-
if (selector && position > -1) {
8-
const pre = selector.slice(0, position)
9-
const matches = balancedMatch("(", ")", selector.slice(position))
9+
if (node.nodes && node.nodes.length > 0) {
10+
if (node.nodes[0] && node.nodes[0].spaces) {
11+
node.nodes[0].spaces.before = ""
12+
}
1013

11-
if (!matches) {
12-
return selector
13-
}
14-
15-
const bodySelectors = matches.body
16-
? list
17-
.comma(matches.body)
18-
.map(s => explodeSelector(pseudoClass, s))
19-
.join(`)${pseudoClass}(`)
20-
: ""
21-
const postSelectors = matches.post
22-
? explodeSelector(pseudoClass, matches.post)
23-
: ""
24-
25-
return `${pre}${pseudoClass}(${bodySelectors})${postSelectors}`
26-
}
27-
return selector
14+
if (
15+
node.nodes[node.nodes.length - 1] &&
16+
node.nodes[node.nodes.length - 1].spaces
17+
) {
18+
node.nodes[node.nodes.length - 1].spaces.after = ""
19+
}
20+
}
2821
}
2922

30-
const patternCache = {}
23+
const creator = () => {
24+
return {
25+
postcssPlugin: "postcss-selector-not",
26+
Rule: (rule, {result}) => {
27+
if (rule.selector && rule.selector.indexOf(":not(") > -1) {
28+
try {
29+
const selectorAST = parser().astSync(rule.selector)
30+
selectorAST.walkPseudos((pseudo) => {
31+
if (pseudo.value !== ":not") {
32+
return
33+
}
3134

32-
function locatePseudoClass(selector, pseudoClass) {
33-
patternCache[pseudoClass] = patternCache[pseudoClass]
34-
|| new RegExp(`([^\\\\]|^)${pseudoClass}`)
35+
if (!pseudo.nodes || pseudo.nodes.length < 2) {
36+
return
37+
}
3538

36-
// The regex is used to ensure that selectors with
37-
// escaped colons in them are treated properly
38-
// Ex: .foo\:not-bar is a valid CSS selector
39-
// But it is not a reference to a pseudo selector
40-
const pattern = patternCache[pseudoClass]
41-
const position = selector.search(pattern)
39+
const replacements = []
4240

43-
if (position === -1) {
44-
return -1
45-
}
41+
pseudo.nodes.forEach((node) => {
42+
cleanupWhitespace(node)
4643

47-
// The offset returned by the regex may be off by one because
48-
// of it including the negated character match in the position
49-
return position + selector.slice(position).indexOf(pseudoClass)
50-
}
44+
// Wrap each child selector in it's own `:not()`
45+
const newPseudo = parser.pseudo({
46+
value: ":not",
47+
nodes: [node],
48+
})
49+
replacements.push(newPseudo)
50+
})
51+
52+
// Replace the pseudo with the list of new pseudos
53+
pseudo.replaceWith(...replacements)
54+
})
5155

52-
function explodeSelectors(pseudoClass) {
53-
return () => {
54-
return {
55-
postcssPlugin: "postcss-selector-not",
56-
Rule: (rule) => {
57-
if (rule.selector && rule.selector.indexOf(pseudoClass) > -1) {
58-
rule.selector = explodeSelector(pseudoClass, rule.selector)
59-
}
60-
},
61-
}
62-
}
56+
const modifiedSelector = selectorAST.toString()
57+
if (modifiedSelector !== rule.selector) {
58+
rule.selector = modifiedSelector
59+
}
60+
}
61+
catch (_) {
62+
rule.warn(
63+
result,
64+
`Failed to parse selector "${rule.selector}"`
65+
)
66+
}
67+
}
68+
},
69+
}
6370
}
6471

65-
const creator = explodeSelectors(":not")
6672
creator.postcss = true
6773

6874
export default creator

0 commit comments

Comments
 (0)