@@ -4,7 +4,23 @@ const selectorParser = require("postcss-selector-parser");
44
55const hasOwnProperty = Object . prototype . hasOwnProperty ;
66
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+
824 return root . nodes . map ( ( node ) => {
925 if ( node . type !== "selector" || node . nodes . length !== 1 ) {
1026 throw new Error (
@@ -91,7 +107,7 @@ const plugin = (options = {}) => {
91107 Once ( root , { rule } ) {
92108 const exports = Object . create ( null ) ;
93109
94- function exportScopedName ( name , rawName , node ) {
110+ function exportScopedName ( name , rawName , node , needExport = true ) {
95111 const scopedName = generateScopedName (
96112 rawName ? rawName : name ,
97113 root . source . input . from ,
@@ -107,6 +123,10 @@ const plugin = (options = {}) => {
107123 ) ;
108124 const { key, value } = exportEntry ;
109125
126+ if ( ! needExport ) {
127+ return scopedName ;
128+ }
129+
110130 exports [ key ] = exports [ key ] || [ ] ;
111131
112132 if ( exports [ key ] . indexOf ( value ) < 0 ) {
@@ -116,25 +136,27 @@ const plugin = (options = {}) => {
116136 return scopedName ;
117137 }
118138
119- function localizeNode ( node ) {
139+ function localizeNode ( node , needExport = true ) {
120140 switch ( node . type ) {
121141 case "selector" :
122- node . nodes = node . map ( localizeNode ) ;
142+ node . nodes = node . map ( ( item ) => localizeNode ( item , needExport ) ) ;
123143 return node ;
124144 case "class" :
125145 return selectorParser . className ( {
126146 value : exportScopedName (
127147 node . value ,
128148 node . raws && node . raws . value ? node . raws . value : null ,
129- node
149+ node ,
150+ needExport
130151 ) ,
131152 } ) ;
132153 case "id" : {
133154 return selectorParser . id ( {
134155 value : exportScopedName (
135156 node . value ,
136157 node . raws && node . raws . value ? node . raws . value : null ,
137- node
158+ node ,
159+ needExport
138160 ) ,
139161 } ) ;
140162 }
@@ -144,7 +166,7 @@ const plugin = (options = {}) => {
144166 attribute : node . attribute ,
145167 operator : node . operator ,
146168 quoteMark : "'" ,
147- value : exportScopedName ( node . value ) ,
169+ value : exportScopedName ( node . value , null , null , needExport ) ,
148170 } ) ;
149171 }
150172 }
@@ -155,15 +177,15 @@ const plugin = (options = {}) => {
155177 ) ;
156178 }
157179
158- function traverseNode ( node ) {
180+ function traverseNode ( node , needExport = true ) {
159181 switch ( node . type ) {
160182 case "pseudo" :
161183 if ( node . value === ":local" ) {
162184 if ( node . nodes . length !== 1 ) {
163185 throw new Error ( 'Unexpected comma (",") in :local block' ) ;
164186 }
165187
166- const selector = localizeNode ( node . first , node . spaces ) ;
188+ const selector = localizeNode ( node . first , needExport ) ;
167189 // move the spaces that were around the pseudo selector to the first
168190 // non-container node
169191 selector . first . spaces = node . spaces ;
@@ -186,12 +208,12 @@ const plugin = (options = {}) => {
186208 /* falls through */
187209 case "root" :
188210 case "selector" : {
189- node . each ( traverseNode ) ;
211+ node . each ( ( item ) => traverseNode ( item , needExport ) ) ;
190212 break ;
191213 }
192214 case "id" :
193215 case "class" :
194- if ( exportGlobals ) {
216+ if ( needExport && exportGlobals ) {
195217 exports [ node . value ] = [ node . value ] ;
196218 }
197219 break ;
@@ -215,7 +237,10 @@ const plugin = (options = {}) => {
215237 rule . selector = traverseNode ( parsedSelector . clone ( ) ) . toString ( ) ;
216238
217239 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+ ) ;
219244 const classes = decl . value . split ( / \s + / ) ;
220245
221246 classes . forEach ( ( className ) => {
@@ -291,6 +316,25 @@ const plugin = (options = {}) => {
291316 atRule . params = exportScopedName ( localMatch [ 1 ] ) ;
292317 } ) ;
293318
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+
294338 // If we found any :locals, insert an :export rule
295339 const exportedNames = Object . keys ( exports ) ;
296340
0 commit comments