Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 87edc0d

Browse files
committedDec 29, 2023
update dependencies and general cleanup
1 parent 7ffb641 commit 87edc0d

39 files changed

+4672
-121
lines changed
 

‎.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
node: [12, 14, 16]
14+
node: [16, 18, 'lts/*']
1515
steps:
16-
- uses: actions/checkout@v2
17-
- uses: actions/setup-node@v2
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-node@v4
1818
with:
1919
node-version: ${{ matrix.node }}
2020

‎.gitignore

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
.*
2-
!.editorconfig
3-
!.gitignore
4-
!.github
5-
!.rollup.js
6-
!.tape.js
7-
!.travis.yml
1+
.eslintcache
82
*.log*
93
*.result.css
10-
/index.*
114
node_modules
12-
package-lock.json
13-
dist

‎.tape.js

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

‎dist/index.cjs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
var nesting = require('postcss-nesting');
2+
3+
// functional selector match
4+
const functionalSelectorMatch = /(^|[^\w-])(%[_a-zA-Z]+[_a-zA-Z0-9-]*)([^\w-]|$)/i;
5+
6+
// plugin
7+
const postcssExtendRule = rawopts => {
8+
// options ( onFunctionalSelector, onRecursiveExtend, onUnusedExtend)
9+
const opts = Object(rawopts);
10+
let extendMatch = /^extend$/i;
11+
if (opts.name instanceof RegExp) {
12+
extendMatch = opts.name;
13+
} else if ('name' in opts) {
14+
extendMatch = new RegExp(`^${opts.name}$`, 'i');
15+
}
16+
return {
17+
postcssPlugin: 'postcss-extend-rule',
18+
OnceExit(root, {
19+
postcss,
20+
result
21+
}) {
22+
const extendedAtRules = new WeakMap();
23+
24+
// for each extend at-rule
25+
root.walkAtRules(extendMatch, extendAtRule => {
26+
let parent = extendAtRule.parent;
27+
while (parent.parent && parent.parent !== root) {
28+
parent = parent.parent;
29+
}
30+
31+
// do not revisit visited extend at-rules
32+
if (!extendedAtRules.has(extendAtRule)) {
33+
extendedAtRules.set(extendAtRule, true);
34+
35+
// selector identifier
36+
const selectorIdMatch = getSelectorIdMatch(extendAtRule.params, postcss);
37+
38+
// extending rules
39+
const extendingRules = getExtendingRules(selectorIdMatch, extendAtRule);
40+
41+
// if there are extending rules
42+
if (extendingRules.length) {
43+
// replace the extend at-rule with the extending rules
44+
extendAtRule.replaceWith(extendingRules);
45+
46+
// transform any nesting at-rules
47+
const cloneRoot = postcss.root().append(parent.clone());
48+
49+
// apply nesting (sync)
50+
postcss([nesting({
51+
noIsPseudoSelector: true
52+
})]).process(cloneRoot).sync();
53+
parent.replaceWith(cloneRoot);
54+
} else {
55+
// manage unused extend at-rules
56+
const unusedExtendMessage = `Unused extend at-rule "${extendAtRule.params}"`;
57+
if (opts.onUnusedExtend === 'throw') {
58+
throw extendAtRule.error(unusedExtendMessage, {
59+
word: extendAtRule.name
60+
});
61+
} else if (opts.onUnusedExtend === 'warn') {
62+
extendAtRule.warn(result, unusedExtendMessage);
63+
} else if (opts.onUnusedExtend !== 'ignore') {
64+
extendAtRule.remove();
65+
}
66+
}
67+
} else {
68+
// manage revisited extend at-rules
69+
const revisitedExtendMessage = `Revisited extend at-rule "${extendAtRule.params}"`;
70+
if (opts.onRecursiveExtend === 'throw') {
71+
throw extendAtRule.error(revisitedExtendMessage, {
72+
word: extendAtRule.name
73+
});
74+
} else if (opts.onRecursiveExtend === 'warn') {
75+
extendAtRule.warn(result, revisitedExtendMessage);
76+
} else if (opts.onRecursiveExtend !== 'ignore') {
77+
extendAtRule.remove();
78+
}
79+
}
80+
});
81+
root.walkRules(functionalSelectorMatch, functionalRule => {
82+
// manage encountered functional selectors
83+
const functionalSelectorMessage = `Encountered functional selector "${functionalRule.selector}"`;
84+
if (opts.onFunctionalSelector === 'throw') {
85+
throw functionalRule.error(functionalSelectorMessage, {
86+
word: functionalRule.selector.match(functionalSelectorMatch)[1]
87+
});
88+
} else if (opts.onFunctionalSelector === 'warn') {
89+
functionalRule.warn(result, functionalSelectorMessage);
90+
} else if (opts.onFunctionalSelector !== 'ignore') {
91+
functionalRule.remove();
92+
}
93+
});
94+
}
95+
};
96+
};
97+
function getExtendingRules(selectorIdMatch, extendAtRule) {
98+
// extending rules
99+
const extendingRules = [];
100+
101+
// for each rule found from root of the extend at-rule with a matching selector identifier
102+
extendAtRule.root().walkRules(selectorIdMatch, matchingRule => {
103+
// nesting selectors for the selectors matching the selector identifier
104+
const nestingSelectors = matchingRule.selectors.filter(selector => selectorIdMatch.test(selector)).map(selector => selector.replace(selectorIdMatch, '$1&$3')).join(',');
105+
106+
// matching rule’s cloned nodes
107+
const nestingNodes = matchingRule.clone().nodes;
108+
109+
// clone the matching rule as a nested rule
110+
let clone = extendAtRule.clone({
111+
name: 'nest',
112+
params: nestingSelectors,
113+
nodes: nestingNodes,
114+
// empty the extending rules, as they are likely non-conforming
115+
raws: {}
116+
});
117+
118+
// preserve nesting of parent rules and at-rules
119+
let parent = matchingRule.parent;
120+
while (parent && (parent.type === 'rule' || parent.type === 'atrule')) {
121+
clone = parent.clone().removeAll().append([clone]);
122+
parent = parent.parent;
123+
}
124+
125+
// push the matching rule to the extending rules
126+
extendingRules.push(clone);
127+
});
128+
129+
// return the extending rules
130+
return extendingRules;
131+
}
132+
function getSelectorIdMatch(selectorIds, postcss) {
133+
// escape the contents of the selector id to avoid being parsed as regex
134+
const escapedSelectorIds = postcss.list.comma(selectorIds).map(selectorId => selectorId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
135+
136+
// selector unattached to an existing selector
137+
return new RegExp(`(^|[^\\w-]!.!#)(${escapedSelectorIds})([^\\w-]|$)`, '');
138+
}
139+
postcssExtendRule.postcss = true;
140+
141+
module.exports = postcssExtendRule;

‎dist/index.mjs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import nesting from 'postcss-nesting';
2+
3+
// functional selector match
4+
const functionalSelectorMatch = /(^|[^\w-])(%[_a-zA-Z]+[_a-zA-Z0-9-]*)([^\w-]|$)/i;
5+
6+
// plugin
7+
const postcssExtendRule = rawopts => {
8+
// options ( onFunctionalSelector, onRecursiveExtend, onUnusedExtend)
9+
const opts = Object(rawopts);
10+
let extendMatch = /^extend$/i;
11+
if (opts.name instanceof RegExp) {
12+
extendMatch = opts.name;
13+
} else if ('name' in opts) {
14+
extendMatch = new RegExp(`^${opts.name}$`, 'i');
15+
}
16+
return {
17+
postcssPlugin: 'postcss-extend-rule',
18+
OnceExit(root, {
19+
postcss,
20+
result
21+
}) {
22+
const extendedAtRules = new WeakMap();
23+
24+
// for each extend at-rule
25+
root.walkAtRules(extendMatch, extendAtRule => {
26+
let parent = extendAtRule.parent;
27+
while (parent.parent && parent.parent !== root) {
28+
parent = parent.parent;
29+
}
30+
31+
// do not revisit visited extend at-rules
32+
if (!extendedAtRules.has(extendAtRule)) {
33+
extendedAtRules.set(extendAtRule, true);
34+
35+
// selector identifier
36+
const selectorIdMatch = getSelectorIdMatch(extendAtRule.params, postcss);
37+
38+
// extending rules
39+
const extendingRules = getExtendingRules(selectorIdMatch, extendAtRule);
40+
41+
// if there are extending rules
42+
if (extendingRules.length) {
43+
// replace the extend at-rule with the extending rules
44+
extendAtRule.replaceWith(extendingRules);
45+
46+
// transform any nesting at-rules
47+
const cloneRoot = postcss.root().append(parent.clone());
48+
49+
// apply nesting (sync)
50+
postcss([nesting({
51+
noIsPseudoSelector: true
52+
})]).process(cloneRoot).sync();
53+
parent.replaceWith(cloneRoot);
54+
} else {
55+
// manage unused extend at-rules
56+
const unusedExtendMessage = `Unused extend at-rule "${extendAtRule.params}"`;
57+
if (opts.onUnusedExtend === 'throw') {
58+
throw extendAtRule.error(unusedExtendMessage, {
59+
word: extendAtRule.name
60+
});
61+
} else if (opts.onUnusedExtend === 'warn') {
62+
extendAtRule.warn(result, unusedExtendMessage);
63+
} else if (opts.onUnusedExtend !== 'ignore') {
64+
extendAtRule.remove();
65+
}
66+
}
67+
} else {
68+
// manage revisited extend at-rules
69+
const revisitedExtendMessage = `Revisited extend at-rule "${extendAtRule.params}"`;
70+
if (opts.onRecursiveExtend === 'throw') {
71+
throw extendAtRule.error(revisitedExtendMessage, {
72+
word: extendAtRule.name
73+
});
74+
} else if (opts.onRecursiveExtend === 'warn') {
75+
extendAtRule.warn(result, revisitedExtendMessage);
76+
} else if (opts.onRecursiveExtend !== 'ignore') {
77+
extendAtRule.remove();
78+
}
79+
}
80+
});
81+
root.walkRules(functionalSelectorMatch, functionalRule => {
82+
// manage encountered functional selectors
83+
const functionalSelectorMessage = `Encountered functional selector "${functionalRule.selector}"`;
84+
if (opts.onFunctionalSelector === 'throw') {
85+
throw functionalRule.error(functionalSelectorMessage, {
86+
word: functionalRule.selector.match(functionalSelectorMatch)[1]
87+
});
88+
} else if (opts.onFunctionalSelector === 'warn') {
89+
functionalRule.warn(result, functionalSelectorMessage);
90+
} else if (opts.onFunctionalSelector !== 'ignore') {
91+
functionalRule.remove();
92+
}
93+
});
94+
}
95+
};
96+
};
97+
function getExtendingRules(selectorIdMatch, extendAtRule) {
98+
// extending rules
99+
const extendingRules = [];
100+
101+
// for each rule found from root of the extend at-rule with a matching selector identifier
102+
extendAtRule.root().walkRules(selectorIdMatch, matchingRule => {
103+
// nesting selectors for the selectors matching the selector identifier
104+
const nestingSelectors = matchingRule.selectors.filter(selector => selectorIdMatch.test(selector)).map(selector => selector.replace(selectorIdMatch, '$1&$3')).join(',');
105+
106+
// matching rule’s cloned nodes
107+
const nestingNodes = matchingRule.clone().nodes;
108+
109+
// clone the matching rule as a nested rule
110+
let clone = extendAtRule.clone({
111+
name: 'nest',
112+
params: nestingSelectors,
113+
nodes: nestingNodes,
114+
// empty the extending rules, as they are likely non-conforming
115+
raws: {}
116+
});
117+
118+
// preserve nesting of parent rules and at-rules
119+
let parent = matchingRule.parent;
120+
while (parent && (parent.type === 'rule' || parent.type === 'atrule')) {
121+
clone = parent.clone().removeAll().append([clone]);
122+
parent = parent.parent;
123+
}
124+
125+
// push the matching rule to the extending rules
126+
extendingRules.push(clone);
127+
});
128+
129+
// return the extending rules
130+
return extendingRules;
131+
}
132+
function getSelectorIdMatch(selectorIds, postcss) {
133+
// escape the contents of the selector id to avoid being parsed as regex
134+
const escapedSelectorIds = postcss.list.comma(selectorIds).map(selectorId => selectorId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
135+
136+
// selector unattached to an existing selector
137+
return new RegExp(`(^|[^\\w-]!.!#)(${escapedSelectorIds})([^\\w-]|$)`, '');
138+
}
139+
postcssExtendRule.postcss = true;
140+
141+
export { postcssExtendRule as default };

0 commit comments

Comments
 (0)
Please sign in to comment.