@@ -29,6 +29,58 @@ export function formatVariantSelector(current, ...others) {
29
29
return current
30
30
}
31
31
32
+ /**
33
+ * Given any node in a selector this gets the "simple" selector it's a part of
34
+ * A simple selector is just a list of nodes without any combinators
35
+ * Technically :is(), :not(), :has(), etc… can have combinators but those are nested
36
+ * inside the relevant node and won't be picked up so they're fine to ignore
37
+ *
38
+ * @param {import('postcss-selector-parser').Node } node
39
+ * @returns {import('postcss-selector-parser').Node[] }
40
+ **/
41
+ function simpleSelectorForNode ( node ) {
42
+ /** @type {import('postcss-selector-parser').Node[] } */
43
+ let nodes = [ ]
44
+
45
+ // Walk backwards until we hit a combinator node (or the start)
46
+ while ( node . prev ( ) && node . prev ( ) . type !== 'combinator' ) {
47
+ node = node . prev ( )
48
+ }
49
+
50
+ // Now record all non-combinator nodes until we hit one (or the end)
51
+ while ( node && node . type !== 'combinator' ) {
52
+ nodes . push ( node )
53
+ node = node . next ( )
54
+ }
55
+
56
+ return nodes
57
+ }
58
+
59
+ /**
60
+ * Resorts the nodes in a selector to ensure they're in the correct order
61
+ * Tags go before classes, and pseudo classes go after classes
62
+ *
63
+ * @param {import('postcss-selector-parser').Selector } sel
64
+ * @returns {import('postcss-selector-parser').Selector }
65
+ **/
66
+ function resortSelector ( sel ) {
67
+ sel . sort ( ( a , b ) => {
68
+ if ( a . type === 'tag' && b . type === 'class' ) {
69
+ return - 1
70
+ } else if ( a . type === 'class' && b . type === 'tag' ) {
71
+ return 1
72
+ } else if ( a . type === 'class' && b . type === 'pseudo' && b . value !== ':merge' ) {
73
+ return - 1
74
+ } else if ( a . type === 'pseudo' && a . value !== ':merge' && b . type === 'class' ) {
75
+ return 1
76
+ }
77
+
78
+ return sel . index ( a ) - sel . index ( b )
79
+ } )
80
+
81
+ return sel
82
+ }
83
+
32
84
export function finalizeSelector (
33
85
format ,
34
86
{
@@ -88,12 +140,47 @@ export function finalizeSelector(
88
140
}
89
141
} )
90
142
143
+ let simpleStart = selectorParser . comment ( { value : '/*__simple__*/' } )
144
+ let simpleEnd = selectorParser . comment ( { value : '/*__simple__*/' } )
145
+
91
146
// We can safely replace the escaped base now, since the `base` section is
92
147
// now in a normalized escaped value.
93
148
ast . walkClasses ( ( node ) => {
94
- if ( node . value === base ) {
95
- node . replaceWith ( ...formatAst . nodes )
149
+ if ( node . value !== base ) {
150
+ return
151
+ }
152
+
153
+ let parent = node . parent
154
+ let formatNodes = formatAst . nodes [ 0 ] . nodes
155
+
156
+ // Perf optimization: if the parent is a single class we can just replace it and be done
157
+ if ( parent . nodes . length === 1 ) {
158
+ node . replaceWith ( ...formatNodes )
159
+ return
96
160
}
161
+
162
+ let simpleSelector = simpleSelectorForNode ( node )
163
+ parent . insertBefore ( simpleSelector [ 0 ] , simpleStart )
164
+ parent . insertAfter ( simpleSelector [ simpleSelector . length - 1 ] , simpleEnd )
165
+
166
+ for ( let child of formatNodes ) {
167
+ parent . insertBefore ( simpleSelector [ 0 ] , child )
168
+ }
169
+
170
+ node . remove ( )
171
+
172
+ // Re-sort the simple selector to ensure it's in the correct order
173
+ simpleSelector = simpleSelectorForNode ( simpleStart )
174
+ let firstNode = parent . index ( simpleStart )
175
+
176
+ parent . nodes . splice (
177
+ firstNode ,
178
+ simpleSelector . length ,
179
+ ...resortSelector ( selectorParser . selector ( { nodes : simpleSelector } ) ) . nodes
180
+ )
181
+
182
+ simpleStart . remove ( )
183
+ simpleEnd . remove ( )
97
184
} )
98
185
99
186
// This will make sure to move pseudo's to the correct spot (the end for
0 commit comments