Skip to content

Commit 3bd6274

Browse files
authored
Merge pull request #69 from tbela99/feature/css_nesting_no_match_pseudo_element
nesting selector cannot match pseudo element #67
2 parents c4a2a1b + b11c316 commit 3bd6274

File tree

17 files changed

+263
-50
lines changed

17 files changed

+263
-50
lines changed

dist/index-umd-web.js

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
EnumToken[EnumToken["MediaFeatureAndTokenType"] = 89] = "MediaFeatureAndTokenType";
108108
EnumToken[EnumToken["MediaFeatureOrTokenType"] = 90] = "MediaFeatureOrTokenType";
109109
EnumToken[EnumToken["PseudoPageTokenType"] = 91] = "PseudoPageTokenType";
110+
EnumToken[EnumToken["PseudoElementTokenType"] = 92] = "PseudoElementTokenType";
110111
/* aliases */
111112
EnumToken[EnumToken["Time"] = 25] = "Time";
112113
EnumToken[EnumToken["Iden"] = 7] = "Iden";
@@ -3881,8 +3882,9 @@
38813882
return '';
38823883
}
38833884
case exports.EnumToken.PseudoClassTokenType:
3885+
case exports.EnumToken.PseudoElementTokenType:
38843886
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos
3885-
if (token.typ == exports.EnumToken.PseudoClassTokenType && ['::before', '::after', '::first-line', '::first-letter'].includes(token.val)) {
3887+
if (token.typ == exports.EnumToken.PseudoElementTokenType && ['::before', '::after', '::first-line', '::first-letter'].includes(token.val)) {
38863888
return token.val.slice(1);
38873889
}
38883890
case exports.EnumToken.UrlTokenTokenType:
@@ -12009,8 +12011,8 @@
1200912011
tokens.shift();
1201012012
consumeWhitespace(tokens);
1201112013
}
12012-
while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.PseudoClassTokenType) {
12013-
const isPseudoElement = tokens[0].val.startsWith('::');
12014+
while (tokens.length > 0 && (tokens[0].typ == exports.EnumToken.PseudoElementTokenType || tokens[0].typ == exports.EnumToken.PseudoClassTokenType)) {
12015+
const isPseudoElement = tokens[0].typ == exports.EnumToken.PseudoElementTokenType;
1201412016
if (
1201512017
// https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions#pseudo-elements
1201612018
!(isPseudoElement && tokens[0].val.startsWith('::-webkit-')) &&
@@ -16319,11 +16321,11 @@
1631916321
const node = {
1632016322
typ: exports.EnumToken.AtRuleNodeType,
1632116323
nam: renderToken(atRule, { removeComments: true }),
16322-
tokens: t,
16324+
// tokens: t,
1632316325
val: raw.join('')
1632416326
};
1632516327
Object.defineProperties(node, {
16326-
tokens: { ...definedPropertySettings, enumerable: true, value: tokens.slice() },
16328+
tokens: { ...definedPropertySettings, enumerable: false, value: tokens.slice() },
1632716329
raw: { ...definedPropertySettings, value: raw }
1632816330
});
1632916331
if (delim.typ == exports.EnumToken.BlockStartTokenType) {
@@ -16448,7 +16450,7 @@
1644816450
};
1644916451
Object.defineProperty(node, 'tokens', {
1645016452
...definedPropertySettings,
16451-
enumerable: true,
16453+
enumerable: false,
1645216454
value: tokens.slice()
1645316455
});
1645416456
let raw = [...uniq.values()];
@@ -16921,10 +16923,16 @@
1692116923
val: val.slice(0, -1),
1692216924
chi: []
1692316925
}
16924-
: {
16925-
typ: exports.EnumToken.PseudoClassTokenType,
16926+
: (
16927+
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos
16928+
val.startsWith('::') || [':before', ':after', ':first-line', ':first-letter'].includes(val) ? {
16929+
typ: exports.EnumToken.PseudoElementTokenType,
1692616930
val
16927-
};
16931+
} :
16932+
{
16933+
typ: exports.EnumToken.PseudoClassTokenType,
16934+
val
16935+
});
1692816936
}
1692916937
if (isAtKeyword(val)) {
1693016938
return {
@@ -17618,7 +17626,14 @@
1761817626
rule.sel = splitRule(ast.sel).reduce((a, b) => a.concat([b.join('') + r]), []).join(',');
1761917627
}
1762017628
else {
17621-
selRule.forEach(arr => combinators.includes(arr[0].charAt(0)) ? arr.unshift(ast.sel) : arr.unshift(ast.sel, ' '));
17629+
// selRule = splitRule(selRule.reduce((acc, curr) => acc + (acc.length > 0 ? ',' : '') + curr.join(''), ''));
17630+
const arSelf = splitRule(ast.sel).filter(r => r.every(t => t != ':before' && t != ':after' && !t.startsWith('::'))).reduce((acc, curr) => acc.concat([curr.join('')]), []).join(',');
17631+
if (arSelf.length == 0) {
17632+
ast.chi.splice(i--, 1);
17633+
continue;
17634+
}
17635+
//
17636+
selRule.forEach(arr => combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, ' '));
1762217637
rule.sel = selRule.reduce((acc, curr) => {
1762317638
acc.push(curr.join(''));
1762417639
return acc;
@@ -17629,8 +17644,14 @@
1762917644
let childSelectorCompound = [];
1763017645
let withCompound = [];
1763117646
let withoutCompound = [];
17632-
const rules = splitRule(ast.sel);
17647+
// pseudo elements cannot be used with '&'
17648+
// https://www.w3.org/TR/css-nesting-1/#example-7145ff1e
17649+
const rules = splitRule(ast.sel).filter(r => r.every(t => t != ':before' && t != ':after' && !t.startsWith('::')));
1763317650
const parentSelector = !node.sel.includes('&');
17651+
if (rules.length == 0) {
17652+
ast.chi.splice(i--, 1);
17653+
continue;
17654+
}
1763417655
for (const sel of (rule.raw ?? splitRule(rule.sel))) {
1763517656
const s = sel.join('');
1763617657
if (s.includes('&') || parentSelector) {
@@ -19327,7 +19348,7 @@
1932719348
return s.join('');
1932819349
}).join(',');
1932919350
// @ts-ignore
19330-
let sel = wrap ? node.optimized.optimized[0] + `:is(${rule})` : rule;
19351+
let sel = wrap ? node.optimized.optimized.join('') + `:is(${rule})` : rule;
1933119352
if (rule.includes('&')) {
1933219353
// @ts-ignore
1933319354
rule = replaceCompound(rule, node.optimized.optimized[0]);

dist/index.cjs

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ exports.EnumToken = void 0;
106106
EnumToken[EnumToken["MediaFeatureAndTokenType"] = 89] = "MediaFeatureAndTokenType";
107107
EnumToken[EnumToken["MediaFeatureOrTokenType"] = 90] = "MediaFeatureOrTokenType";
108108
EnumToken[EnumToken["PseudoPageTokenType"] = 91] = "PseudoPageTokenType";
109+
EnumToken[EnumToken["PseudoElementTokenType"] = 92] = "PseudoElementTokenType";
109110
/* aliases */
110111
EnumToken[EnumToken["Time"] = 25] = "Time";
111112
EnumToken[EnumToken["Iden"] = 7] = "Iden";
@@ -3880,8 +3881,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
38803881
return '';
38813882
}
38823883
case exports.EnumToken.PseudoClassTokenType:
3884+
case exports.EnumToken.PseudoElementTokenType:
38833885
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos
3884-
if (token.typ == exports.EnumToken.PseudoClassTokenType && ['::before', '::after', '::first-line', '::first-letter'].includes(token.val)) {
3886+
if (token.typ == exports.EnumToken.PseudoElementTokenType && ['::before', '::after', '::first-line', '::first-letter'].includes(token.val)) {
38853887
return token.val.slice(1);
38863888
}
38873889
case exports.EnumToken.UrlTokenTokenType:
@@ -12008,8 +12010,8 @@ function validateCompoundSelector(tokens, root, options) {
1200812010
tokens.shift();
1200912011
consumeWhitespace(tokens);
1201012012
}
12011-
while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.PseudoClassTokenType) {
12012-
const isPseudoElement = tokens[0].val.startsWith('::');
12013+
while (tokens.length > 0 && (tokens[0].typ == exports.EnumToken.PseudoElementTokenType || tokens[0].typ == exports.EnumToken.PseudoClassTokenType)) {
12014+
const isPseudoElement = tokens[0].typ == exports.EnumToken.PseudoElementTokenType;
1201312015
if (
1201412016
// https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions#pseudo-elements
1201512017
!(isPseudoElement && tokens[0].val.startsWith('::-webkit-')) &&
@@ -16318,11 +16320,11 @@ async function parseNode(results, context, stats, options, errors, src, map, raw
1631816320
const node = {
1631916321
typ: exports.EnumToken.AtRuleNodeType,
1632016322
nam: renderToken(atRule, { removeComments: true }),
16321-
tokens: t,
16323+
// tokens: t,
1632216324
val: raw.join('')
1632316325
};
1632416326
Object.defineProperties(node, {
16325-
tokens: { ...definedPropertySettings, enumerable: true, value: tokens.slice() },
16327+
tokens: { ...definedPropertySettings, enumerable: false, value: tokens.slice() },
1632616328
raw: { ...definedPropertySettings, value: raw }
1632716329
});
1632816330
if (delim.typ == exports.EnumToken.BlockStartTokenType) {
@@ -16447,7 +16449,7 @@ async function parseNode(results, context, stats, options, errors, src, map, raw
1644716449
};
1644816450
Object.defineProperty(node, 'tokens', {
1644916451
...definedPropertySettings,
16450-
enumerable: true,
16452+
enumerable: false,
1645116453
value: tokens.slice()
1645216454
});
1645316455
let raw = [...uniq.values()];
@@ -16920,10 +16922,16 @@ function getTokenType(val, hint) {
1692016922
val: val.slice(0, -1),
1692116923
chi: []
1692216924
}
16923-
: {
16924-
typ: exports.EnumToken.PseudoClassTokenType,
16925+
: (
16926+
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos
16927+
val.startsWith('::') || [':before', ':after', ':first-line', ':first-letter'].includes(val) ? {
16928+
typ: exports.EnumToken.PseudoElementTokenType,
1692516929
val
16926-
};
16930+
} :
16931+
{
16932+
typ: exports.EnumToken.PseudoClassTokenType,
16933+
val
16934+
});
1692716935
}
1692816936
if (isAtKeyword(val)) {
1692916937
return {
@@ -17617,7 +17625,14 @@ function expandRule(node) {
1761717625
rule.sel = splitRule(ast.sel).reduce((a, b) => a.concat([b.join('') + r]), []).join(',');
1761817626
}
1761917627
else {
17620-
selRule.forEach(arr => combinators.includes(arr[0].charAt(0)) ? arr.unshift(ast.sel) : arr.unshift(ast.sel, ' '));
17628+
// selRule = splitRule(selRule.reduce((acc, curr) => acc + (acc.length > 0 ? ',' : '') + curr.join(''), ''));
17629+
const arSelf = splitRule(ast.sel).filter(r => r.every(t => t != ':before' && t != ':after' && !t.startsWith('::'))).reduce((acc, curr) => acc.concat([curr.join('')]), []).join(',');
17630+
if (arSelf.length == 0) {
17631+
ast.chi.splice(i--, 1);
17632+
continue;
17633+
}
17634+
//
17635+
selRule.forEach(arr => combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, ' '));
1762117636
rule.sel = selRule.reduce((acc, curr) => {
1762217637
acc.push(curr.join(''));
1762317638
return acc;
@@ -17628,8 +17643,14 @@ function expandRule(node) {
1762817643
let childSelectorCompound = [];
1762917644
let withCompound = [];
1763017645
let withoutCompound = [];
17631-
const rules = splitRule(ast.sel);
17646+
// pseudo elements cannot be used with '&'
17647+
// https://www.w3.org/TR/css-nesting-1/#example-7145ff1e
17648+
const rules = splitRule(ast.sel).filter(r => r.every(t => t != ':before' && t != ':after' && !t.startsWith('::')));
1763217649
const parentSelector = !node.sel.includes('&');
17650+
if (rules.length == 0) {
17651+
ast.chi.splice(i--, 1);
17652+
continue;
17653+
}
1763317654
for (const sel of (rule.raw ?? splitRule(rule.sel))) {
1763417655
const s = sel.join('');
1763517656
if (s.includes('&') || parentSelector) {
@@ -19326,7 +19347,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co
1932619347
return s.join('');
1932719348
}).join(',');
1932819349
// @ts-ignore
19329-
let sel = wrap ? node.optimized.optimized[0] + `:is(${rule})` : rule;
19350+
let sel = wrap ? node.optimized.optimized.join('') + `:is(${rule})` : rule;
1933019351
if (rule.includes('&')) {
1933119352
// @ts-ignore
1933219353
rule = replaceCompound(rule, node.optimized.optimized[0]);

dist/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ declare enum EnumToken {
9191
MediaFeatureAndTokenType = 89,
9292
MediaFeatureOrTokenType = 90,
9393
PseudoPageTokenType = 91,
94+
PseudoElementTokenType = 92,
9495
Time = 25,
9596
Iden = 7,
9697
EOF = 48,
@@ -517,6 +518,12 @@ export declare interface PseudoClassToken extends BaseToken {
517518
val: string;
518519
}
519520

521+
export declare interface PseudoElementToken extends BaseToken {
522+
523+
typ: EnumToken.PseudoElementTokenType;
524+
val: string;
525+
}
526+
520527
export declare interface PseudoPageToken extends BaseToken {
521528

522529
typ: EnumToken.PseudoPageTokenType;
@@ -812,6 +819,7 @@ export declare type Token =
812819
ListToken
813820
| PseudoClassToken
814821
| PseudoPageToken
822+
| PseudoElementToken
815823
| PseudoClassFunctionToken
816824
| DelimToken
817825
| BinaryExpressionToken

dist/lib/ast/expand.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,14 @@ function expandRule(node) {
6969
rule.sel = splitRule(ast.sel).reduce((a, b) => a.concat([b.join('') + r]), []).join(',');
7070
}
7171
else {
72-
selRule.forEach(arr => combinators.includes(arr[0].charAt(0)) ? arr.unshift(ast.sel) : arr.unshift(ast.sel, ' '));
72+
// selRule = splitRule(selRule.reduce((acc, curr) => acc + (acc.length > 0 ? ',' : '') + curr.join(''), ''));
73+
const arSelf = splitRule(ast.sel).filter(r => r.every(t => t != ':before' && t != ':after' && !t.startsWith('::'))).reduce((acc, curr) => acc.concat([curr.join('')]), []).join(',');
74+
if (arSelf.length == 0) {
75+
ast.chi.splice(i--, 1);
76+
continue;
77+
}
78+
//
79+
selRule.forEach(arr => combinators.includes(arr[0].charAt(0)) ? arr.unshift(arSelf) : arr.unshift(arSelf, ' '));
7380
rule.sel = selRule.reduce((acc, curr) => {
7481
acc.push(curr.join(''));
7582
return acc;
@@ -80,8 +87,14 @@ function expandRule(node) {
8087
let childSelectorCompound = [];
8188
let withCompound = [];
8289
let withoutCompound = [];
83-
const rules = splitRule(ast.sel);
90+
// pseudo elements cannot be used with '&'
91+
// https://www.w3.org/TR/css-nesting-1/#example-7145ff1e
92+
const rules = splitRule(ast.sel).filter(r => r.every(t => t != ':before' && t != ':after' && !t.startsWith('::')));
8493
const parentSelector = !node.sel.includes('&');
94+
if (rules.length == 0) {
95+
ast.chi.splice(i--, 1);
96+
continue;
97+
}
8598
for (const sel of (rule.raw ?? splitRule(rule.sel))) {
8699
const s = sel.join('');
87100
if (s.includes('&') || parentSelector) {

dist/lib/ast/minify.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co
227227
return s.join('');
228228
}).join(',');
229229
// @ts-ignore
230-
let sel = wrap ? node.optimized.optimized[0] + `:is(${rule})` : rule;
230+
let sel = wrap ? node.optimized.optimized.join('') + `:is(${rule})` : rule;
231231
if (rule.includes('&')) {
232232
// @ts-ignore
233233
rule = replaceCompound(rule, node.optimized.optimized[0]);

dist/lib/ast/types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ var EnumToken;
101101
EnumToken[EnumToken["MediaFeatureAndTokenType"] = 89] = "MediaFeatureAndTokenType";
102102
EnumToken[EnumToken["MediaFeatureOrTokenType"] = 90] = "MediaFeatureOrTokenType";
103103
EnumToken[EnumToken["PseudoPageTokenType"] = 91] = "PseudoPageTokenType";
104+
EnumToken[EnumToken["PseudoElementTokenType"] = 92] = "PseudoElementTokenType";
104105
/* aliases */
105106
EnumToken[EnumToken["Time"] = 25] = "Time";
106107
EnumToken[EnumToken["Iden"] = 7] = "Iden";

dist/lib/parser/parse.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -455,11 +455,11 @@ async function parseNode(results, context, stats, options, errors, src, map, raw
455455
const node = {
456456
typ: EnumToken.AtRuleNodeType,
457457
nam: renderToken(atRule, { removeComments: true }),
458-
tokens: t,
458+
// tokens: t,
459459
val: raw.join('')
460460
};
461461
Object.defineProperties(node, {
462-
tokens: { ...definedPropertySettings, enumerable: true, value: tokens.slice() },
462+
tokens: { ...definedPropertySettings, enumerable: false, value: tokens.slice() },
463463
raw: { ...definedPropertySettings, value: raw }
464464
});
465465
if (delim.typ == EnumToken.BlockStartTokenType) {
@@ -584,7 +584,7 @@ async function parseNode(results, context, stats, options, errors, src, map, raw
584584
};
585585
Object.defineProperty(node, 'tokens', {
586586
...definedPropertySettings,
587-
enumerable: true,
587+
enumerable: false,
588588
value: tokens.slice()
589589
});
590590
let raw = [...uniq.values()];
@@ -1057,10 +1057,16 @@ function getTokenType(val, hint) {
10571057
val: val.slice(0, -1),
10581058
chi: []
10591059
}
1060-
: {
1061-
typ: EnumToken.PseudoClassTokenType,
1060+
: (
1061+
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos
1062+
val.startsWith('::') || [':before', ':after', ':first-line', ':first-letter'].includes(val) ? {
1063+
typ: EnumToken.PseudoElementTokenType,
10621064
val
1063-
};
1065+
} :
1066+
{
1067+
typ: EnumToken.PseudoClassTokenType,
1068+
val
1069+
});
10641070
}
10651071
if (isAtKeyword(val)) {
10661072
return {

dist/lib/renderer/render.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
589589
return '';
590590
}
591591
case EnumToken.PseudoClassTokenType:
592+
case EnumToken.PseudoElementTokenType:
592593
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos
593-
if (token.typ == EnumToken.PseudoClassTokenType && ['::before', '::after', '::first-line', '::first-letter'].includes(token.val)) {
594+
if (token.typ == EnumToken.PseudoElementTokenType && ['::before', '::after', '::first-line', '::first-letter'].includes(token.val)) {
594595
return token.val.slice(1);
595596
}
596597
case EnumToken.UrlTokenTokenType:

dist/lib/validation/syntaxes/compound-selector.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ function validateCompoundSelector(tokens, root, options) {
8080
tokens.shift();
8181
consumeWhitespace(tokens);
8282
}
83-
while (tokens.length > 0 && tokens[0].typ == EnumToken.PseudoClassTokenType) {
84-
const isPseudoElement = tokens[0].val.startsWith('::');
83+
while (tokens.length > 0 && (tokens[0].typ == EnumToken.PseudoElementTokenType || tokens[0].typ == EnumToken.PseudoClassTokenType)) {
84+
const isPseudoElement = tokens[0].typ == EnumToken.PseudoElementTokenType;
8585
if (
8686
// https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions#pseudo-elements
8787
!(isPseudoElement && tokens[0].val.startsWith('::-webkit-')) &&

0 commit comments

Comments
 (0)