@@ -138,23 +138,151 @@ ${signalScript.content}
138138/**
139139 * Extract exported variable names from setup script.
140140 * Returns variables that should be exposed to the template.
141+ * Only extracts TOP-LEVEL declarations, not variables inside nested functions.
141142 */
142143function extractExports ( setupContent : string ) : string {
143- const exports : string [ ] = [ ]
144+ const code = setupContent
145+ const names : string [ ] = [ ]
146+ const seen = new Set < string > ( )
147+
148+ // Track brace depth to only capture top-level declarations
149+ let depth = 0
150+ let i = 0
151+ const len = code . length
152+
153+ // Skip string literals
154+ const skipString = ( quote : string ) : void => {
155+ i ++ // Skip opening quote
156+ while ( i < len ) {
157+ if ( code [ i ] === '\\' ) {
158+ i += 2 // Skip escaped character
159+ continue
160+ }
161+ if ( code [ i ] === quote ) {
162+ i ++ // Skip closing quote
163+ return
164+ }
165+ i ++
166+ }
167+ }
168+
169+ // Skip template literals with nested expressions
170+ const skipTemplateLiteral = ( ) : void => {
171+ i ++ // Skip opening backtick
172+ while ( i < len ) {
173+ if ( code [ i ] === '\\' ) {
174+ i += 2
175+ continue
176+ }
177+ if ( code [ i ] === '`' ) {
178+ i ++
179+ return
180+ }
181+ if ( code [ i ] === '$' && code [ i + 1 ] === '{' ) {
182+ i += 2
183+ let templateDepth = 1
184+ while ( i < len && templateDepth > 0 ) {
185+ if ( code [ i ] === '{' ) templateDepth ++
186+ else if ( code [ i ] === '}' ) templateDepth --
187+ else if ( code [ i ] === '\'' || code [ i ] === '"' ) skipString ( code [ i ] )
188+ else if ( code [ i ] === '`' ) skipTemplateLiteral ( )
189+ else i ++
190+ }
191+ continue
192+ }
193+ i ++
194+ }
195+ }
196+
197+ // Skip comments
198+ const skipComment = ( ) : boolean => {
199+ if ( code [ i ] === '/' && code [ i + 1 ] === '/' ) {
200+ while ( i < len && code [ i ] !== '\n' ) i ++
201+ return true
202+ }
203+ if ( code [ i ] === '/' && code [ i + 1 ] === '*' ) {
204+ i += 2
205+ while ( i < len - 1 && ! ( code [ i ] === '*' && code [ i + 1 ] === '/' ) ) i ++
206+ i += 2
207+ return true
208+ }
209+ return false
210+ }
211+
212+ // Check for variable/function declaration at current position (only at depth 0)
213+ const checkDeclaration = ( ) : void => {
214+ if ( depth !== 0 ) return
215+
216+ // Check for const/let/var declarations
217+ const declMatch = code . slice ( i ) . match ( / ^ ( c o n s t | l e t | v a r ) \s + ( [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * ) \s * = / )
218+ if ( declMatch ) {
219+ const varName = declMatch [ 2 ]
220+ if ( ! seen . has ( varName ) ) {
221+ names . push ( varName )
222+ seen . add ( varName )
223+ }
224+ return
225+ }
144226
145- // Match const/let/function declarations
146- const constMatches = setupContent . matchAll ( / \b (?: c o n s t | l e t ) \s + ( [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * ) \s * = / g)
147- for ( const match of constMatches ) {
148- exports . push ( match [ 1 ] )
227+ // Check for function declarations
228+ const funcMatch = code . slice ( i ) . match ( / ^ f u n c t i o n \s + ( [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * ) \s * \( / )
229+ if ( funcMatch ) {
230+ const funcName = funcMatch [ 1 ]
231+ if ( ! seen . has ( funcName ) ) {
232+ names . push ( funcName )
233+ seen . add ( funcName )
234+ }
235+ return
236+ }
237+
238+ // Check for async function declarations
239+ const asyncMatch = code . slice ( i ) . match ( / ^ a s y n c \s + f u n c t i o n \s + ( [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * ) \s * \( / )
240+ if ( asyncMatch ) {
241+ const funcName = asyncMatch [ 1 ]
242+ if ( ! seen . has ( funcName ) ) {
243+ names . push ( funcName )
244+ seen . add ( funcName )
245+ }
246+ }
149247 }
150248
151- // Match function declarations
152- const funcMatches = setupContent . matchAll ( / \b f u n c t i o n \s + ( [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * ) \s * \( / g)
153- for ( const match of funcMatches ) {
154- exports . push ( match [ 1 ] )
249+ while ( i < len ) {
250+ // Skip comments
251+ if ( skipComment ( ) ) continue
252+
253+ // Skip string literals
254+ if ( code [ i ] === '\'' || code [ i ] === '"' ) {
255+ skipString ( code [ i ] )
256+ continue
257+ }
258+
259+ // Skip template literals
260+ if ( code [ i ] === '`' ) {
261+ skipTemplateLiteral ( )
262+ continue
263+ }
264+
265+ // Track brace depth
266+ if ( code [ i ] === '{' ) {
267+ depth ++
268+ i ++
269+ continue
270+ }
271+ if ( code [ i ] === '}' ) {
272+ depth --
273+ i ++
274+ continue
275+ }
276+
277+ // Check for declarations at word boundaries (only at depth 0)
278+ if ( depth === 0 && / [ a - z ] / i. test ( code [ i ] ) && ( i === 0 || / \s | [ ; { } ( ) ] / . test ( code [ i - 1 ] ) ) ) {
279+ checkDeclaration ( )
280+ }
281+
282+ i ++
155283 }
156284
157- return exports . join ( ', ' )
285+ return names . join ( ', ' )
158286}
159287
160288/**
0 commit comments