Skip to content

Commit 415c89e

Browse files
committed
chore: wip
1 parent 5c83da9 commit 415c89e

File tree

2 files changed

+272
-22
lines changed

2 files changed

+272
-22
lines changed

packages/stx/src/process.ts

Lines changed: 138 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
142143
function 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(/^(const|let|var)\s+([a-zA-Z_$][a-zA-Z0-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(?:const|let)\s+([a-zA-Z_$][a-zA-Z0-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(/^function\s+([a-zA-Z_$][a-zA-Z0-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(/^async\s+function\s+([a-zA-Z_$][a-zA-Z0-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(/\bfunction\s+([a-zA-Z_$][a-zA-Z0-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
/**

packages/stx/src/utils.ts

Lines changed: 134 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,149 @@ const componentsCache = new LRUCache<string, string>(500)
4444

4545
/**
4646
* Extract variable names from JavaScript code for scope registration
47+
* Only extracts TOP-LEVEL declarations, not variables inside nested functions
4748
*/
4849
function extractVariableNames(code: string): string[] {
4950
const names: string[] = []
5051
const seen = new Set<string>()
5152

52-
// Match const/let declarations
53-
const constMatches = code.matchAll(/\b(?:const|let)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g)
54-
for (const match of constMatches) {
55-
if (!seen.has(match[1])) {
56-
names.push(match[1])
57-
seen.add(match[1])
53+
// Track brace depth to only capture top-level declarations
54+
let depth = 0
55+
let i = 0
56+
const len = code.length
57+
58+
// Skip string literals and track brace depth
59+
const skipString = (quote: string): void => {
60+
i++ // Skip opening quote
61+
while (i < len) {
62+
if (code[i] === '\\') {
63+
i += 2 // Skip escaped character
64+
continue
65+
}
66+
if (code[i] === quote) {
67+
i++ // Skip closing quote
68+
return
69+
}
70+
i++
5871
}
5972
}
6073

61-
// Match function declarations
62-
const funcMatches = code.matchAll(/\bfunction\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g)
63-
for (const match of funcMatches) {
64-
if (!seen.has(match[1])) {
65-
names.push(match[1])
66-
seen.add(match[1])
74+
// Skip template literals (backticks) with nested expressions
75+
const skipTemplateLiteral = (): void => {
76+
i++ // Skip opening backtick
77+
while (i < len) {
78+
if (code[i] === '\\') {
79+
i += 2
80+
continue
81+
}
82+
if (code[i] === '`') {
83+
i++
84+
return
85+
}
86+
if (code[i] === '$' && code[i + 1] === '{') {
87+
i += 2
88+
let templateDepth = 1
89+
while (i < len && templateDepth > 0) {
90+
if (code[i] === '{') templateDepth++
91+
else if (code[i] === '}') templateDepth--
92+
else if (code[i] === '\'' || code[i] === '"') skipString(code[i])
93+
else if (code[i] === '`') skipTemplateLiteral()
94+
else i++
95+
}
96+
continue
97+
}
98+
i++
99+
}
100+
}
101+
102+
// Skip comments
103+
const skipComment = (): boolean => {
104+
if (code[i] === '/' && code[i + 1] === '/') {
105+
// Single-line comment
106+
while (i < len && code[i] !== '\n') i++
107+
return true
108+
}
109+
if (code[i] === '/' && code[i + 1] === '*') {
110+
// Multi-line comment
111+
i += 2
112+
while (i < len - 1 && !(code[i] === '*' && code[i + 1] === '/')) i++
113+
i += 2
114+
return true
67115
}
116+
return false
117+
}
118+
119+
// Check for variable declaration at current position (only at depth 0)
120+
const checkDeclaration = (): void => {
121+
if (depth !== 0) return
122+
123+
// Check for const/let/var declarations
124+
const declMatch = code.slice(i).match(/^(const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/)
125+
if (declMatch) {
126+
const varName = declMatch[2]
127+
if (!seen.has(varName)) {
128+
names.push(varName)
129+
seen.add(varName)
130+
}
131+
return
132+
}
133+
134+
// Check for function declarations
135+
const funcMatch = code.slice(i).match(/^function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/)
136+
if (funcMatch) {
137+
const funcName = funcMatch[1]
138+
if (!seen.has(funcName)) {
139+
names.push(funcName)
140+
seen.add(funcName)
141+
}
142+
return
143+
}
144+
145+
// Check for async function declarations
146+
const asyncMatch = code.slice(i).match(/^async\s+function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/)
147+
if (asyncMatch) {
148+
const funcName = asyncMatch[1]
149+
if (!seen.has(funcName)) {
150+
names.push(funcName)
151+
seen.add(funcName)
152+
}
153+
}
154+
}
155+
156+
while (i < len) {
157+
// Skip comments
158+
if (skipComment()) continue
159+
160+
// Skip string literals
161+
if (code[i] === '\'' || code[i] === '"') {
162+
skipString(code[i])
163+
continue
164+
}
165+
166+
// Skip template literals
167+
if (code[i] === '`') {
168+
skipTemplateLiteral()
169+
continue
170+
}
171+
172+
// Track brace depth
173+
if (code[i] === '{') {
174+
depth++
175+
i++
176+
continue
177+
}
178+
if (code[i] === '}') {
179+
depth--
180+
i++
181+
continue
182+
}
183+
184+
// Check for declarations at word boundaries (only at depth 0)
185+
if (depth === 0 && /[a-z]/i.test(code[i]) && (i === 0 || /\s|[;{}()]/.test(code[i - 1]))) {
186+
checkDeclaration()
187+
}
188+
189+
i++
68190
}
69191

70192
return names

0 commit comments

Comments
 (0)