diff --git a/src/lib/machine.test.ts b/src/lib/machine.test.ts index 3854bf2..bd9f266 100644 --- a/src/lib/machine.test.ts +++ b/src/lib/machine.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { detectFormat, parseSketchCode, parseMermaidCode, machineToGraph, getEventCategory } from './machine'; +import { detectFormat, parseSketchCode, parseMermaidCode, parseXStateMachineCode, machineToGraph, getEventCategory } from './machine'; describe('detectFormat', () => { it('detects Sketch DSL before any explicit mode selection', () => { @@ -33,6 +33,34 @@ describe('parseSketchCode', () => { }); }); +describe('parseXStateMachineCode', () => { + const machine = `setup({}).createMachine({ + id: 'test', + initial: 'idle', + states: { idle: {} }, +})`; + + it.each([ + // xstate imports + [`import { setup } from 'xstate';\n${machine}`], + [`import * as xstate from 'xstate';\n${machine}`], + // export default + [`export default ${machine}`], + // export const/let/var/function/class + [`export const m = ${machine}`], + [`export function getMachine() { return ${machine} }\ngetMachine()`], + // export { ... } + [`const m = ${machine};\nexport { m };`], + // export * from + [`export * from 'some-module';\n${machine}`], + ])('strips imports and exports: %s', (code) => { + const result = parseXStateMachineCode(code); + expect(result.error).toBeNull(); + expect(result.machines).toHaveLength(1); + expect(result.machines[0]?.id).toBe('test'); + }); +}); + describe('parseMermaidCode', () => { it('converts <> nodes to always transitions with guards', () => { const result = parseMermaidCode(`stateDiagram-v2 diff --git a/src/lib/machine.ts b/src/lib/machine.ts index 316f1d2..4028fd7 100644 --- a/src/lib/machine.ts +++ b/src/lib/machine.ts @@ -327,12 +327,23 @@ const XSTATE_PARAM_NAMES = [ ] as const; /** - * Strip import statements for 'xstate' since we inject bindings as fn params. + * Strip xstate imports (we inject bindings as fn params) and ESM export syntax + * so code can be evaluated via `new Function()`. */ -function stripXStateImports(code: string): string { +function stripImportsAndExports(code: string): string { return code + // import { ... } from 'xstate' .replace(/import\s*\{[^}]*\}\s*from\s*['"]xstate['"]\s*;?/g, '') - .replace(/import\s*\*\s*as\s+\w+\s*from\s*['"]xstate['"]\s*;?/g, ''); + // import * as x from 'xstate' + .replace(/import\s*\*\s*as\s+\w+\s*from\s*['"]xstate['"]\s*;?/g, '') + // export default ... + .replace(/^\s*export\s+default\s+/gm, '') + // export const/let/var/function/class + .replace(/^\s*export\s+(const|let|var|function|class)\b/gm, '$1') + // export { ... } + .replace(/^\s*export\s*\{[^}]*\}\s*;?\s*$/gm, '') + // export * from '...' + .replace(/^\s*export\s*\*\s*from\s*['"][^'"]+['"]\s*;?\s*$/gm, ''); } /** @@ -369,7 +380,7 @@ export function parseXStateMachineCode(code: string): { } as any; const jsCode = tsBlankSpace(code); - const strippedCode = stripXStateImports(jsCode); + const strippedCode = stripImportsAndExports(jsCode); const fn = new Function(...XSTATE_PARAM_NAMES, strippedCode); fn(