Skip to content

Commit e716220

Browse files
committed
code scan context
1 parent aa93229 commit e716220

2 files changed

Lines changed: 170 additions & 12 deletions

File tree

apps/sim/executor/variables/resolver.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,38 @@ describe('VariableResolver function block inputs', () => {
133133
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
134134
})
135135

136+
it('keeps JavaScript block references inside template expressions executable', () => {
137+
const { block, ctx, resolver } = createResolver('javascript')
138+
139+
const result = resolver.resolveInputsForFunctionBlock(
140+
ctx,
141+
'function',
142+
{ code: 'return `${String(<Producer.result>)}`' },
143+
block
144+
)
145+
146+
expect(result.resolvedInputs.code).toBe('return `${String(globalThis["__blockRef_0"])}`')
147+
expect(result.displayInputs.code).toBe('return `${String("hello world")}`')
148+
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
149+
})
150+
151+
it('ignores JavaScript comment quotes before later block references', () => {
152+
const { block, ctx, resolver } = createResolver('javascript')
153+
154+
const result = resolver.resolveInputsForFunctionBlock(
155+
ctx,
156+
'function',
157+
{ code: "// don't confuse quote tracking\nreturn <Producer.result>" },
158+
block
159+
)
160+
161+
expect(result.resolvedInputs.code).toBe(
162+
'// don\'t confuse quote tracking\nreturn globalThis["__blockRef_0"]'
163+
)
164+
expect(result.displayInputs.code).toBe('// don\'t confuse quote tracking\nreturn "hello world"')
165+
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
166+
})
167+
136168
it('breaks Python string literals around quoted block references', () => {
137169
const { block, ctx, resolver } = createResolver('python')
138170

@@ -150,6 +182,23 @@ describe('VariableResolver function block inputs', () => {
150182
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
151183
})
152184

185+
it('ignores Python comment quotes before later block references', () => {
186+
const { block, ctx, resolver } = createResolver('python')
187+
188+
const result = resolver.resolveInputsForFunctionBlock(
189+
ctx,
190+
'function',
191+
{ code: "# don't confuse quote tracking\nreturn <Producer.result>" },
192+
block
193+
)
194+
195+
expect(result.resolvedInputs.code).toBe(
196+
'# don\'t confuse quote tracking\nreturn globals()["__blockRef_0"]'
197+
)
198+
expect(result.displayInputs.code).toBe('# don\'t confuse quote tracking\nreturn "hello world"')
199+
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
200+
})
201+
153202
it('uses separate Python context variables for repeated mutable references', () => {
154203
const { block, ctx, resolver } = createResolver('python')
155204

apps/sim/executor/variables/resolver.ts

Lines changed: 121 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ const logger = createLogger('VariableResolver')
2525

2626
type ShellQuoteContext = 'single' | 'double' | null
2727
type CodeStringQuoteContext = ShellQuoteContext | 'template'
28+
type CodeScanMode =
29+
| { type: 'normal' }
30+
| { type: 'single' }
31+
| { type: 'double' }
32+
| { type: 'template' }
33+
| { type: 'template-expression'; depth: number }
34+
| { type: 'line-comment' }
35+
| { type: 'block-comment' }
2836

2937
export class VariableResolver {
3038
private resolvers: Resolver[]
@@ -353,7 +361,7 @@ export class VariableResolver {
353361
): string {
354362
if (language === 'python') {
355363
const expression = `globals()[${JSON.stringify(varName)}]`
356-
const quoteContext = this.getCodeStringQuoteContext(template, matchIndex)
364+
const quoteContext = this.getCodeStringQuoteContext(template, matchIndex, language)
357365
if (quoteContext === 'single' || quoteContext === 'double') {
358366
const quote = quoteContext === 'single' ? "'" : '"'
359367
return `${quote} + json.dumps(${expression}) + ${quote}`
@@ -366,7 +374,7 @@ export class VariableResolver {
366374
}
367375

368376
const expression = `globalThis[${JSON.stringify(varName)}]`
369-
const quoteContext = this.getCodeStringQuoteContext(template, matchIndex)
377+
const quoteContext = this.getCodeStringQuoteContext(template, matchIndex, language)
370378
if (quoteContext === 'template') {
371379
return `\${JSON.stringify(${expression})}`
372380
}
@@ -413,25 +421,126 @@ export class VariableResolver {
413421
return JSON.stringify(value)
414422
}
415423

416-
private getCodeStringQuoteContext(template: string, index: number): CodeStringQuoteContext {
417-
let quoteContext: CodeStringQuoteContext = null
424+
private getCodeStringQuoteContext(
425+
template: string,
426+
index: number,
427+
language: string | undefined
428+
): CodeStringQuoteContext {
429+
const isPython = language === 'python'
430+
const modes: CodeScanMode[] = [{ type: 'normal' }]
418431

419432
for (let i = 0; i < index; i++) {
420433
const char = template[i]
421-
if (quoteContext !== null && char === '\\') {
434+
const next = template[i + 1]
435+
const mode = modes[modes.length - 1]
436+
437+
if (mode.type === 'line-comment') {
438+
if (char === '\n') {
439+
modes.pop()
440+
}
441+
continue
442+
}
443+
444+
if (mode.type === 'block-comment') {
445+
if (char === '*' && next === '/') {
446+
modes.pop()
447+
i++
448+
}
449+
continue
450+
}
451+
452+
if (mode.type === 'single' || mode.type === 'double') {
453+
const quote = mode.type === 'single' ? "'" : '"'
454+
if (char === '\\') {
455+
i++
456+
continue
457+
}
458+
if (char === quote || char === '\n') {
459+
modes.pop()
460+
}
461+
continue
462+
}
463+
464+
if (mode.type === 'template') {
465+
if (char === '\\') {
466+
i++
467+
continue
468+
}
469+
if (char === '`') {
470+
modes.pop()
471+
continue
472+
}
473+
if (char === '$' && next === '{') {
474+
modes.push({ type: 'template-expression', depth: 1 })
475+
i++
476+
}
477+
continue
478+
}
479+
480+
if (mode.type === 'template-expression') {
481+
if (!isPython && char === '/' && next === '/') {
482+
modes.push({ type: 'line-comment' })
483+
i++
484+
continue
485+
}
486+
if (!isPython && char === '/' && next === '*') {
487+
modes.push({ type: 'block-comment' })
488+
i++
489+
continue
490+
}
491+
if (char === "'") {
492+
modes.push({ type: 'single' })
493+
continue
494+
}
495+
if (char === '"') {
496+
modes.push({ type: 'double' })
497+
continue
498+
}
499+
if (!isPython && char === '`') {
500+
modes.push({ type: 'template' })
501+
continue
502+
}
503+
if (char === '{') {
504+
mode.depth += 1
505+
continue
506+
}
507+
if (char === '}') {
508+
mode.depth -= 1
509+
if (mode.depth === 0) {
510+
modes.pop()
511+
}
512+
}
513+
continue
514+
}
515+
516+
if (isPython && char === '#') {
517+
modes.push({ type: 'line-comment' })
518+
continue
519+
}
520+
if (!isPython && char === '/' && next === '/') {
521+
modes.push({ type: 'line-comment' })
422522
i++
423523
continue
424524
}
425-
if (char === "'" && quoteContext !== 'double' && quoteContext !== 'template') {
426-
quoteContext = quoteContext === 'single' ? null : 'single'
427-
} else if (char === '"' && quoteContext !== 'single' && quoteContext !== 'template') {
428-
quoteContext = quoteContext === 'double' ? null : 'double'
429-
} else if (char === '`' && quoteContext !== 'single' && quoteContext !== 'double') {
430-
quoteContext = quoteContext === 'template' ? null : 'template'
525+
if (!isPython && char === '/' && next === '*') {
526+
modes.push({ type: 'block-comment' })
527+
i++
528+
continue
529+
}
530+
if (char === "'") {
531+
modes.push({ type: 'single' })
532+
} else if (char === '"') {
533+
modes.push({ type: 'double' })
534+
} else if (!isPython && char === '`') {
535+
modes.push({ type: 'template' })
431536
}
432537
}
433538

434-
return quoteContext
539+
const mode = modes[modes.length - 1]
540+
if (mode.type === 'single' || mode.type === 'double' || mode.type === 'template') {
541+
return mode.type
542+
}
543+
return null
435544
}
436545

437546
private formatShellContextVariableReference(

0 commit comments

Comments
 (0)