Skip to content

Commit 6d65b2f

Browse files
authored
fix: improve parsing of :nth-of-type(xn+b) (#9970)
closes #9969 this also fixes the following along the way: the + in nth-of-type(+xn-b) would be parsed as a combinator. invalid cases like these are not allowed anymore: b(+/-)b -ax -ax-b -b
1 parent 1e33ed5 commit 6d65b2f

File tree

4 files changed

+298
-19
lines changed

4 files changed

+298
-19
lines changed

.changeset/three-suits-grin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: parse `:nth-of-type(xn+y)` correctly

packages/svelte/src/compiler/phases/1-parse/read/style.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const REGEX_ATTRIBUTE_FLAGS = /^[a-zA-Z]+/; // only `i` and `s` are valid today,
66
const REGEX_COMBINATOR_WHITESPACE = /^\s*(\+|~|>|\|\|)\s*/;
77
const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/;
88
const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
9-
const REGEX_NTH_OF = /^\s*(even|odd|(-?[0-9]?n?(\s*\+\s*[0-9]+)?))(\s*(?=[,)])|\s+of\s+)/;
9+
const REGEX_NTH_OF =
10+
/^\s*(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))(\s*(?=[,)])|\s+of\s+)/;
1011
const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
1112
const REGEX_BRACE_OR_SEMICOLON = /[{;]/;
1213
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
@@ -277,6 +278,14 @@ function read_selector(parser, inside_pseudo_class = false) {
277278
value,
278279
flags
279280
});
281+
} else if (inside_pseudo_class && parser.match_regex(REGEX_NTH_OF)) {
282+
// nth of matcher must come before combinator matcher to prevent collision else the '+' in '+2n-1' would be parsed as a combinator
283+
children.push({
284+
type: 'Nth',
285+
value: /** @type {string} */ (parser.read(REGEX_NTH_OF)),
286+
start,
287+
end: parser.index
288+
});
280289
} else if (parser.match_regex(REGEX_COMBINATOR_WHITESPACE)) {
281290
parser.allow_whitespace();
282291
const start = parser.index;
@@ -294,13 +303,6 @@ function read_selector(parser, inside_pseudo_class = false) {
294303
start,
295304
end: parser.index
296305
});
297-
} else if (inside_pseudo_class && parser.match_regex(REGEX_NTH_OF)) {
298-
children.push({
299-
type: 'Nth',
300-
value: /** @type {string} */ (parser.read(REGEX_NTH_OF)),
301-
start,
302-
end: parser.index
303-
});
304306
} else {
305307
let name = read_identifier(parser);
306308
if (parser.match('|')) {

packages/svelte/tests/parser-modern/samples/css-nth-syntax/input.svelte

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@
2828
}
2929
h1:global(nav) {
3030
background: red;
31+
}
32+
h1:nth-of-type(10n+1){
33+
background: red;
34+
}
35+
h1:nth-of-type(-2n+3){
36+
background: red;
37+
}
38+
h1:nth-of-type(+12){
39+
background: red;
40+
}
41+
h1:nth-of-type(+3n){
42+
background: red;
3143
}
3244
</style>
3345

packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json

Lines changed: 271 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"css": {
33
"type": "Style",
44
"start": 0,
5-
"end": 586,
5+
"end": 806,
66
"attributes": [],
77
"children": [
88
{
@@ -601,41 +601,301 @@
601601
},
602602
"start": 530,
603603
"end": 577
604+
},
605+
{
606+
"type": "Rule",
607+
"prelude": {
608+
"type": "SelectorList",
609+
"start": 580,
610+
"end": 601,
611+
"children": [
612+
{
613+
"type": "Selector",
614+
"start": 580,
615+
"end": 601,
616+
"children": [
617+
{
618+
"type": "TypeSelector",
619+
"name": "h1",
620+
"start": 580,
621+
"end": 582
622+
},
623+
{
624+
"type": "PseudoClassSelector",
625+
"name": "nth-of-type",
626+
"args": {
627+
"type": "SelectorList",
628+
"start": 595,
629+
"end": 600,
630+
"children": [
631+
{
632+
"type": "Selector",
633+
"start": 595,
634+
"end": 600,
635+
"children": [
636+
{
637+
"type": "Nth",
638+
"value": "10n+1",
639+
"start": 595,
640+
"end": 600
641+
}
642+
]
643+
}
644+
]
645+
},
646+
"start": 582,
647+
"end": 601
648+
}
649+
]
650+
}
651+
]
652+
},
653+
"block": {
654+
"type": "Block",
655+
"start": 601,
656+
"end": 633,
657+
"children": [
658+
{
659+
"type": "Declaration",
660+
"start": 611,
661+
"end": 626,
662+
"property": "background",
663+
"value": "red"
664+
}
665+
]
666+
},
667+
"start": 580,
668+
"end": 633
669+
},
670+
{
671+
"type": "Rule",
672+
"prelude": {
673+
"type": "SelectorList",
674+
"start": 636,
675+
"end": 657,
676+
"children": [
677+
{
678+
"type": "Selector",
679+
"start": 636,
680+
"end": 657,
681+
"children": [
682+
{
683+
"type": "TypeSelector",
684+
"name": "h1",
685+
"start": 636,
686+
"end": 638
687+
},
688+
{
689+
"type": "PseudoClassSelector",
690+
"name": "nth-of-type",
691+
"args": {
692+
"type": "SelectorList",
693+
"start": 651,
694+
"end": 656,
695+
"children": [
696+
{
697+
"type": "Selector",
698+
"start": 651,
699+
"end": 656,
700+
"children": [
701+
{
702+
"type": "Nth",
703+
"value": "-2n+3",
704+
"start": 651,
705+
"end": 656
706+
}
707+
]
708+
}
709+
]
710+
},
711+
"start": 638,
712+
"end": 657
713+
}
714+
]
715+
}
716+
]
717+
},
718+
"block": {
719+
"type": "Block",
720+
"start": 657,
721+
"end": 689,
722+
"children": [
723+
{
724+
"type": "Declaration",
725+
"start": 667,
726+
"end": 682,
727+
"property": "background",
728+
"value": "red"
729+
}
730+
]
731+
},
732+
"start": 636,
733+
"end": 689
734+
},
735+
{
736+
"type": "Rule",
737+
"prelude": {
738+
"type": "SelectorList",
739+
"start": 692,
740+
"end": 711,
741+
"children": [
742+
{
743+
"type": "Selector",
744+
"start": 692,
745+
"end": 711,
746+
"children": [
747+
{
748+
"type": "TypeSelector",
749+
"name": "h1",
750+
"start": 692,
751+
"end": 694
752+
},
753+
{
754+
"type": "PseudoClassSelector",
755+
"name": "nth-of-type",
756+
"args": {
757+
"type": "SelectorList",
758+
"start": 707,
759+
"end": 710,
760+
"children": [
761+
{
762+
"type": "Selector",
763+
"start": 707,
764+
"end": 710,
765+
"children": [
766+
{
767+
"type": "Nth",
768+
"value": "+12",
769+
"start": 707,
770+
"end": 710
771+
}
772+
]
773+
}
774+
]
775+
},
776+
"start": 694,
777+
"end": 711
778+
}
779+
]
780+
}
781+
]
782+
},
783+
"block": {
784+
"type": "Block",
785+
"start": 711,
786+
"end": 743,
787+
"children": [
788+
{
789+
"type": "Declaration",
790+
"start": 721,
791+
"end": 736,
792+
"property": "background",
793+
"value": "red"
794+
}
795+
]
796+
},
797+
"start": 692,
798+
"end": 743
799+
},
800+
{
801+
"type": "Rule",
802+
"prelude": {
803+
"type": "SelectorList",
804+
"start": 746,
805+
"end": 765,
806+
"children": [
807+
{
808+
"type": "Selector",
809+
"start": 746,
810+
"end": 765,
811+
"children": [
812+
{
813+
"type": "TypeSelector",
814+
"name": "h1",
815+
"start": 746,
816+
"end": 748
817+
},
818+
{
819+
"type": "PseudoClassSelector",
820+
"name": "nth-of-type",
821+
"args": {
822+
"type": "SelectorList",
823+
"start": 761,
824+
"end": 764,
825+
"children": [
826+
{
827+
"type": "Selector",
828+
"start": 761,
829+
"end": 764,
830+
"children": [
831+
{
832+
"type": "Nth",
833+
"value": "+3n",
834+
"start": 761,
835+
"end": 764
836+
}
837+
]
838+
}
839+
]
840+
},
841+
"start": 748,
842+
"end": 765
843+
}
844+
]
845+
}
846+
]
847+
},
848+
"block": {
849+
"type": "Block",
850+
"start": 765,
851+
"end": 797,
852+
"children": [
853+
{
854+
"type": "Declaration",
855+
"start": 775,
856+
"end": 790,
857+
"property": "background",
858+
"value": "red"
859+
}
860+
]
861+
},
862+
"start": 746,
863+
"end": 797
604864
}
605865
],
606866
"content": {
607867
"start": 7,
608-
"end": 578,
609-
"styles": "\n /* test that all these are parsed correctly */\n\th1:nth-of-type(2n+1){\n background: red;\n }\n h1:nth-child(-n + 3 of li.important) {\n background: red;\n }\n h1:nth-child(1) {\n background: red;\n }\n h1:nth-child(p) {\n background: red;\n }\n h1:nth-child(n+7) {\n background: red;\n }\n h1:nth-child(even) {\n background: red;\n }\n h1:nth-child(odd) {\n background: red;\n }\n h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n"
868+
"end": 798,
869+
"styles": "\n /* test that all these are parsed correctly */\n\th1:nth-of-type(2n+1){\n background: red;\n }\n h1:nth-child(-n + 3 of li.important) {\n background: red;\n }\n h1:nth-child(1) {\n background: red;\n }\n h1:nth-child(p) {\n background: red;\n }\n h1:nth-child(n+7) {\n background: red;\n }\n h1:nth-child(even) {\n background: red;\n }\n h1:nth-child(odd) {\n background: red;\n }\n h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n\t\th1:nth-of-type(10n+1){\n background: red;\n }\n\t\th1:nth-of-type(-2n+3){\n background: red;\n }\n\t\th1:nth-of-type(+12){\n background: red;\n }\n\t\th1:nth-of-type(+3n){\n background: red;\n }\n"
610870
}
611871
},
612872
"js": [],
613-
"start": 588,
614-
"end": 600,
873+
"start": 808,
874+
"end": 820,
615875
"type": "Root",
616876
"fragment": {
617877
"type": "Fragment",
618878
"nodes": [
619879
{
620880
"type": "Text",
621-
"start": 586,
622-
"end": 588,
881+
"start": 806,
882+
"end": 808,
623883
"raw": "\n\n",
624884
"data": "\n\n"
625885
},
626886
{
627887
"type": "RegularElement",
628-
"start": 588,
629-
"end": 600,
888+
"start": 808,
889+
"end": 820,
630890
"name": "h1",
631891
"attributes": [],
632892
"fragment": {
633893
"type": "Fragment",
634894
"nodes": [
635895
{
636896
"type": "Text",
637-
"start": 592,
638-
"end": 595,
897+
"start": 812,
898+
"end": 815,
639899
"raw": "Foo",
640900
"data": "Foo"
641901
}

0 commit comments

Comments
 (0)