Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions apps/sim/lib/execution/code-placeholders/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import {
type CodePlaceholderRuntimeBinding,
compileCodePlaceholders,
} from '@/lib/execution/code-placeholders'
import {
applySourceEdits,
CodePlaceholderCompileError,
CodePlaceholderInvariantError,
} from '@/lib/execution/code-placeholders/shared'
import { CodeLanguage } from '@/lib/execution/languages'

const installedGlobals = new Set<string>()
Expand Down Expand Up @@ -1543,3 +1548,29 @@ describe('python true positives survive the dot guard', () => {
expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY'])
})
})

describe('compiler failure classification', () => {
it('reports overlapping source edits as a compiler invariant, not a user compile error', () => {
const applyOverlappingEdits = () =>
applySourceEdits('return value', [
{ start: 0, end: 6, text: 'yield' },
{ start: 3, end: 9, text: 'x' },
])

expect(applyOverlappingEdits).toThrow(CodePlaceholderInvariantError)
expect(applyOverlappingEdits).not.toThrow(CodePlaceholderCompileError)
})

it('reports an exhausted JavaScript sentinel space as a user compile error', async () => {
/** Every `$xyz$` sentinel a five-character `{{a}}` could take, packed under the code cap. */
const alphabet = [...'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ']
const payloads = alphabet.flatMap((a) =>
alphabet.flatMap((b) => alphabet.map((c) => `${a}${b}${c}`))
)
const code = `const taken = '$${payloads.join('$')}$'\nreturn {{a}}`

await expect(
compileCodePlaceholders({ code, language: CodeLanguage.JavaScript, params: { a: 1 } })
).rejects.toBeInstanceOf(CodePlaceholderCompileError)
})
})
3 changes: 2 additions & 1 deletion apps/sim/lib/execution/code-placeholders/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { sha256Hex } from '@sim/security/hash'
import {
applySourceEdits,
CodePlaceholderCompileError,
CodePlaceholderInvariantError,
createCodePlaceholderCompilationContext,
isOffsetInRanges,
type SourceEdit,
Expand Down Expand Up @@ -243,7 +244,7 @@ function createSentinel(code: string): string {
const sentinel = `__sim_placeholder_${digest}__`
if (!code.includes(sentinel)) return sentinel
}
throw new CodePlaceholderCompileError('Unable to allocate a collision-free Python marker')
throw new CodePlaceholderInvariantError('Unable to allocate a collision-free Python marker')
}

function pythonTriviaGap(
Expand Down
14 changes: 13 additions & 1 deletion apps/sim/lib/execution/code-placeholders/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ export class CodePlaceholderCompileError extends Error {
}
}

/**
* A broken compiler invariant: Sim's own compilation failed, not the user's code.
* Deliberately not a {@link CodePlaceholderCompileError}, so callers that answer a
* user compile error with a client status treat this as a server failure.
*/
export class CodePlaceholderInvariantError extends Error {
constructor(message: string) {
super(message)
this.name = 'CodePlaceholderInvariantError'
}
}

/**
* Scans `{{name}}` placeholders, accepting exactly what `/\{\{([^}]+)\}\}/g` accepts —
* a name may contain `{`, because parameter keys are arbitrary strings rather than
Expand Down Expand Up @@ -270,7 +282,7 @@ export function applySourceEdits(code: string, edits: SourceEdit[]): string {
let output = ''
for (const edit of sorted) {
if (edit.start < cursor || edit.end < edit.start || edit.end > code.length) {
throw new CodePlaceholderCompileError('Overlapping code placeholder transformations')
throw new CodePlaceholderInvariantError('Overlapping code placeholder transformations')
}
output += code.slice(cursor, edit.start)
output += edit.text
Expand Down
Loading