@@ -4,7 +4,23 @@ const selectorParser = require("postcss-selector-parser");
4
4
5
5
const hasOwnProperty = Object . prototype . hasOwnProperty ;
6
6
7
- function getSingleLocalNamesForComposes ( root ) {
7
+ function isNestedRule ( rule ) {
8
+ if ( ! rule . parent || rule . parent . type === "root" ) {
9
+ return false ;
10
+ }
11
+
12
+ if ( rule . parent . type === "rule" ) {
13
+ return true ;
14
+ }
15
+
16
+ return isNestedRule ( rule . parent ) ;
17
+ }
18
+
19
+ function getSingleLocalNamesForComposes ( root , rule ) {
20
+ if ( isNestedRule ( rule ) ) {
21
+ throw new Error ( `composition is not allowed in nested rule \n\n${ rule } ` ) ;
22
+ }
23
+
8
24
return root . nodes . map ( ( node ) => {
9
25
if ( node . type !== "selector" || node . nodes . length !== 1 ) {
10
26
throw new Error (
@@ -91,7 +107,7 @@ const plugin = (options = {}) => {
91
107
Once ( root , { rule } ) {
92
108
const exports = Object . create ( null ) ;
93
109
94
- function exportScopedName ( name , rawName , node ) {
110
+ function exportScopedName ( name , rawName , node , needExport = true ) {
95
111
const scopedName = generateScopedName (
96
112
rawName ? rawName : name ,
97
113
root . source . input . from ,
@@ -107,6 +123,10 @@ const plugin = (options = {}) => {
107
123
) ;
108
124
const { key, value } = exportEntry ;
109
125
126
+ if ( ! needExport ) {
127
+ return scopedName ;
128
+ }
129
+
110
130
exports [ key ] = exports [ key ] || [ ] ;
111
131
112
132
if ( exports [ key ] . indexOf ( value ) < 0 ) {
@@ -116,25 +136,27 @@ const plugin = (options = {}) => {
116
136
return scopedName ;
117
137
}
118
138
119
- function localizeNode ( node ) {
139
+ function localizeNode ( node , needExport = true ) {
120
140
switch ( node . type ) {
121
141
case "selector" :
122
- node . nodes = node . map ( localizeNode ) ;
142
+ node . nodes = node . map ( ( item ) => localizeNode ( item , needExport ) ) ;
123
143
return node ;
124
144
case "class" :
125
145
return selectorParser . className ( {
126
146
value : exportScopedName (
127
147
node . value ,
128
148
node . raws && node . raws . value ? node . raws . value : null ,
129
- node
149
+ node ,
150
+ needExport
130
151
) ,
131
152
} ) ;
132
153
case "id" : {
133
154
return selectorParser . id ( {
134
155
value : exportScopedName (
135
156
node . value ,
136
157
node . raws && node . raws . value ? node . raws . value : null ,
137
- node
158
+ node ,
159
+ needExport
138
160
) ,
139
161
} ) ;
140
162
}
@@ -144,7 +166,7 @@ const plugin = (options = {}) => {
144
166
attribute : node . attribute ,
145
167
operator : node . operator ,
146
168
quoteMark : "'" ,
147
- value : exportScopedName ( node . value ) ,
169
+ value : exportScopedName ( node . value , null , null , needExport ) ,
148
170
} ) ;
149
171
}
150
172
}
@@ -155,15 +177,15 @@ const plugin = (options = {}) => {
155
177
) ;
156
178
}
157
179
158
- function traverseNode ( node ) {
180
+ function traverseNode ( node , needExport = true ) {
159
181
switch ( node . type ) {
160
182
case "pseudo" :
161
183
if ( node . value === ":local" ) {
162
184
if ( node . nodes . length !== 1 ) {
163
185
throw new Error ( 'Unexpected comma (",") in :local block' ) ;
164
186
}
165
187
166
- const selector = localizeNode ( node . first , node . spaces ) ;
188
+ const selector = localizeNode ( node . first , needExport ) ;
167
189
// move the spaces that were around the pseudo selector to the first
168
190
// non-container node
169
191
selector . first . spaces = node . spaces ;
@@ -186,12 +208,12 @@ const plugin = (options = {}) => {
186
208
/* falls through */
187
209
case "root" :
188
210
case "selector" : {
189
- node . each ( traverseNode ) ;
211
+ node . each ( ( item ) => traverseNode ( item , needExport ) ) ;
190
212
break ;
191
213
}
192
214
case "id" :
193
215
case "class" :
194
- if ( exportGlobals ) {
216
+ if ( needExport && exportGlobals ) {
195
217
exports [ node . value ] = [ node . value ] ;
196
218
}
197
219
break ;
@@ -215,7 +237,10 @@ const plugin = (options = {}) => {
215
237
rule . selector = traverseNode ( parsedSelector . clone ( ) ) . toString ( ) ;
216
238
217
239
rule . walkDecls ( / c o m p o s e s | c o m p o s e - w i t h / i, ( decl ) => {
218
- const localNames = getSingleLocalNamesForComposes ( parsedSelector ) ;
240
+ const localNames = getSingleLocalNamesForComposes (
241
+ parsedSelector ,
242
+ decl . parent
243
+ ) ;
219
244
const classes = decl . value . split ( / \s + / ) ;
220
245
221
246
classes . forEach ( ( className ) => {
@@ -291,6 +316,25 @@ const plugin = (options = {}) => {
291
316
atRule . params = exportScopedName ( localMatch [ 1 ] ) ;
292
317
} ) ;
293
318
319
+ root . walkAtRules ( / s c o p e $ / i, ( atRule ) => {
320
+ atRule . params = atRule . params
321
+ . split ( "to" )
322
+ . map ( ( item ) => {
323
+ const selector = item . trim ( ) . slice ( 1 , - 1 ) . trim ( ) ;
324
+
325
+ const localMatch = / ^ \s * : l o c a l \s * \( ( .+ ?) \) \s * $ / . exec ( selector ) ;
326
+
327
+ if ( ! localMatch ) {
328
+ return `(${ selector } )` ;
329
+ }
330
+
331
+ let parsedSelector = selectorParser ( ) . astSync ( selector ) ;
332
+
333
+ return `(${ traverseNode ( parsedSelector , false ) . toString ( ) } )` ;
334
+ } )
335
+ . join ( " to " ) ;
336
+ } ) ;
337
+
294
338
// If we found any :locals, insert an :export rule
295
339
const exportedNames = Object . keys ( exports ) ;
296
340
0 commit comments