Skip to content

Commit 8f2084c

Browse files
committed
resolver fix for quoted js literals
1 parent 427a530 commit 8f2084c

2 files changed

Lines changed: 90 additions & 2 deletions

File tree

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,57 @@ describe('VariableResolver function block inputs', () => {
9999
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
100100
})
101101

102+
it('breaks JavaScript string literals around quoted block references', () => {
103+
const { block, ctx, resolver } = createResolver('javascript')
104+
105+
const result = resolver.resolveInputsForFunctionBlock(
106+
ctx,
107+
'function',
108+
{ code: "const rawEmail = '<Producer.result>';\nreturn rawEmail" },
109+
block
110+
)
111+
112+
expect(result.resolvedInputs.code).toBe(
113+
"const rawEmail = '' + JSON.stringify(globalThis[\"__blockRef_0\"]) + '';\nreturn rawEmail"
114+
)
115+
expect(result.displayInputs.code).toBe('const rawEmail = \'"hello world"\';\nreturn rawEmail')
116+
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
117+
})
118+
119+
it('uses template interpolation for JavaScript template literal block references', () => {
120+
const { block, ctx, resolver } = createResolver('javascript')
121+
122+
const result = resolver.resolveInputsForFunctionBlock(
123+
ctx,
124+
'function',
125+
{ code: 'return `value: <Producer.result>`' },
126+
block
127+
)
128+
129+
expect(result.resolvedInputs.code).toBe(
130+
'return `value: ${JSON.stringify(globalThis["__blockRef_0"])}`'
131+
)
132+
expect(result.displayInputs.code).toBe('return `value: "hello world"`')
133+
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
134+
})
135+
136+
it('breaks Python string literals around quoted block references', () => {
137+
const { block, ctx, resolver } = createResolver('python')
138+
139+
const result = resolver.resolveInputsForFunctionBlock(
140+
ctx,
141+
'function',
142+
{ code: "raw_email = '<Producer.result>'\nreturn raw_email" },
143+
block
144+
)
145+
146+
expect(result.resolvedInputs.code).toBe(
147+
"raw_email = '' + json.dumps(globals()[\"__blockRef_0\"]) + ''\nreturn raw_email"
148+
)
149+
expect(result.displayInputs.code).toBe('raw_email = \'"hello world"\'\nreturn raw_email')
150+
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
151+
})
152+
102153
it('uses separate Python context variables for repeated mutable references', () => {
103154
const { block, ctx, resolver } = createResolver('python')
104155

apps/sim/executor/variables/resolver.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const FUNCTION_BLOCK_DISPLAY_CODE_KEY = '_runtimeDisplayCode'
2424
const logger = createLogger('VariableResolver')
2525

2626
type ShellQuoteContext = 'single' | 'double' | null
27+
type CodeStringQuoteContext = ShellQuoteContext | 'template'
2728

2829
export class VariableResolver {
2930
private resolvers: Resolver[]
@@ -351,14 +352,29 @@ export class VariableResolver {
351352
value: unknown
352353
): string {
353354
if (language === 'python') {
354-
return `globals()[${JSON.stringify(varName)}]`
355+
const expression = `globals()[${JSON.stringify(varName)}]`
356+
const quoteContext = this.getCodeStringQuoteContext(template, matchIndex)
357+
if (quoteContext === 'single' || quoteContext === 'double') {
358+
const quote = quoteContext === 'single' ? "'" : '"'
359+
return `${quote} + json.dumps(${expression}) + ${quote}`
360+
}
361+
return expression
355362
}
356363

357364
if (language === 'shell') {
358365
return this.formatShellContextVariableReference(varName, template, matchIndex, value)
359366
}
360367

361-
return `globalThis[${JSON.stringify(varName)}]`
368+
const expression = `globalThis[${JSON.stringify(varName)}]`
369+
const quoteContext = this.getCodeStringQuoteContext(template, matchIndex)
370+
if (quoteContext === 'template') {
371+
return `\${JSON.stringify(${expression})}`
372+
}
373+
if (quoteContext === 'single' || quoteContext === 'double') {
374+
const quote = quoteContext === 'single' ? "'" : '"'
375+
return `${quote} + JSON.stringify(${expression}) + ${quote}`
376+
}
377+
return expression
362378
}
363379

364380
private formatDisplayValueForCodeContext(
@@ -397,6 +413,27 @@ export class VariableResolver {
397413
return JSON.stringify(value)
398414
}
399415

416+
private getCodeStringQuoteContext(template: string, index: number): CodeStringQuoteContext {
417+
let quoteContext: CodeStringQuoteContext = null
418+
419+
for (let i = 0; i < index; i++) {
420+
const char = template[i]
421+
if (quoteContext !== null && char === '\\') {
422+
i++
423+
continue
424+
}
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'
431+
}
432+
}
433+
434+
return quoteContext
435+
}
436+
400437
private formatShellContextVariableReference(
401438
varName: string,
402439
template: string,

0 commit comments

Comments
 (0)