@output.ai/core 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/package.json +7 -3
  2. package/src/configs.js +1 -1
  3. package/src/consts.js +3 -3
  4. package/src/index.d.ts +302 -30
  5. package/src/index.js +3 -2
  6. package/src/interface/metadata.js +3 -3
  7. package/src/interface/step.js +3 -3
  8. package/src/interface/webhook.js +13 -14
  9. package/src/interface/workflow.js +22 -42
  10. package/src/internal_activities/index.js +16 -5
  11. package/src/worker/catalog_workflow/catalog.js +105 -0
  12. package/src/worker/catalog_workflow/index.js +21 -0
  13. package/src/worker/catalog_workflow/index.spec.js +139 -0
  14. package/src/worker/catalog_workflow/workflow.js +13 -0
  15. package/src/worker/index.js +37 -5
  16. package/src/worker/internal_utils.js +54 -0
  17. package/src/worker/internal_utils.spec.js +134 -0
  18. package/src/worker/loader.js +30 -44
  19. package/src/worker/loader.spec.js +68 -0
  20. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +117 -0
  21. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +77 -0
  22. package/src/worker/webpack_loaders/workflow_rewriter/consts.js +3 -0
  23. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +56 -0
  24. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +129 -0
  25. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +64 -0
  26. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +33 -0
  27. package/src/worker/webpack_loaders/workflow_rewriter/tools.js +225 -0
  28. package/src/worker/webpack_loaders/workflow_rewriter/tools.spec.js +144 -0
  29. package/src/errors.d.ts +0 -3
  30. package/src/worker/temp/__workflows_entrypoint.js +0 -6
@@ -0,0 +1,117 @@
1
+ import traverseModule from '@babel/traverse';
2
+ import {
3
+ buildWorkflowNameMap,
4
+ getLocalNameFromDestructuredProperty,
5
+ isStepsPath,
6
+ isWorkflowPath,
7
+ buildStepsNameMap,
8
+ toAbsolutePath
9
+ } from './tools.js';
10
+ import {
11
+ isCallExpression,
12
+ isIdentifier,
13
+ isImportDefaultSpecifier,
14
+ isImportSpecifier,
15
+ isObjectPattern,
16
+ isObjectProperty,
17
+ isStringLiteral,
18
+ isVariableDeclaration
19
+ } from '@babel/types';
20
+
21
+ // Handle CJS/ESM interop for Babel packages when executed as a webpack loader
22
+ const traverse = traverseModule.default ?? traverseModule;
23
+
24
+ /**
25
+ * Collect and strip target imports and requires from an AST, producing
26
+ * step/workflow import mappings for later rewrites.
27
+ *
28
+ * Mutates the AST by removing matching import declarations and require declarators.
29
+ *
30
+ * @param {import('@babel/types').File} ast - Parsed file AST.
31
+ * @param {string} fileDir - Absolute directory of the file represented by `ast`.
32
+ * @param {{ stepsNameCache: Map<string,Map<string,string>>, workflowNameCache: Map<string,{default:(string|null),named:Map<string,string>}> }} caches
33
+ * Resolved-name caches to avoid re-reading same modules.
34
+ * @returns {{ stepImports: Array<{localName:string,stepName:string}>,
35
+ * flowImports: Array<{localName:string,workflowName:string}> }} Collected info mappings.
36
+ */
37
+ export default function collectTargetImports( ast, fileDir, { stepsNameCache, workflowNameCache } ) {
38
+ const stepImports = [];
39
+ const flowImports = [];
40
+
41
+ traverse( ast, {
42
+ ImportDeclaration: path => {
43
+ const src = path.node.source.value;
44
+ // Ignore other imports
45
+ if ( !isStepsPath( src ) && !isWorkflowPath( src ) ) { return; }
46
+
47
+ const absolutePath = toAbsolutePath( fileDir, src );
48
+ if ( isStepsPath( src ) ) {
49
+ const nameMap = buildStepsNameMap( absolutePath, stepsNameCache );
50
+ for ( const s of path.node.specifiers.filter( s => isImportSpecifier( s ) ) ) {
51
+ const importedName = s.imported.name;
52
+ const localName = s.local.name;
53
+ const stepName = nameMap.get( importedName );
54
+ if ( stepName ) { stepImports.push( { localName, stepName } ); }
55
+ }
56
+ }
57
+ if ( isWorkflowPath( src ) ) {
58
+ const { named, default: defName } = buildWorkflowNameMap( absolutePath, workflowNameCache );
59
+ for ( const s of path.node.specifiers ) {
60
+ if ( isImportDefaultSpecifier( s ) ) {
61
+ const localName = s.local.name;
62
+ flowImports.push( { localName, workflowName: defName ?? localName } );
63
+ } else if ( isImportSpecifier( s ) ) {
64
+ const importedName = s.imported.name;
65
+ const localName = s.local.name;
66
+ const workflowName = named.get( importedName );
67
+ if ( workflowName ) { flowImports.push( { localName, workflowName } ); }
68
+ }
69
+ }
70
+ }
71
+ path.remove();
72
+ },
73
+ VariableDeclarator: path => {
74
+ const init = path.node.init;
75
+ // Not a require call
76
+ if ( !isCallExpression( init ) ) { return; }
77
+ // Different callee
78
+ if ( !isIdentifier( init.callee, { name: 'require' } ) ) { return; }
79
+ const firstArgument = init.arguments[0];
80
+ // Dynamic require is not supported
81
+ if ( !isStringLiteral( firstArgument ) ) { return; }
82
+
83
+ const req = firstArgument.value;
84
+ // Must be steps/workflows module
85
+ if ( !isStepsPath( req ) && !isWorkflowPath( req ) ) { return; }
86
+
87
+ const absolutePath = toAbsolutePath( fileDir, req );
88
+ if ( isStepsPath( req ) && isObjectPattern( path.node.id ) ) {
89
+ const nameMap = buildStepsNameMap( absolutePath, stepsNameCache );
90
+ for ( const prop of path.node.id.properties.filter( prop => isObjectProperty( prop ) && isIdentifier( prop.key ) ) ) {
91
+ const importedName = prop.key.name;
92
+ const localName = getLocalNameFromDestructuredProperty( prop );
93
+ if ( localName ) {
94
+ const stepName = nameMap.get( importedName );
95
+ if ( stepName ) { stepImports.push( { localName, stepName } ); }
96
+ }
97
+ }
98
+ if ( isVariableDeclaration( path.parent ) && path.parent.declarations.length === 1 ) {
99
+ path.parentPath.remove();
100
+ } else {
101
+ path.remove();
102
+ }
103
+ } else if ( isWorkflowPath( req ) && isIdentifier( path.node.id ) ) {
104
+ const { default: defName } = buildWorkflowNameMap( absolutePath, workflowNameCache );
105
+ const localName = path.node.id.name;
106
+ flowImports.push( { localName, workflowName: defName ?? localName } );
107
+ if ( isVariableDeclaration( path.parent ) && path.parent.declarations.length === 1 ) {
108
+ path.parentPath.remove();
109
+ } else {
110
+ path.remove();
111
+ }
112
+ }
113
+ }
114
+ } );
115
+
116
+ return { stepImports, flowImports };
117
+ };
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { parse } from './tools.js';
6
+ import collectTargetImports from './collect_target_imports.js';
7
+
8
+ function makeAst( source, filename ) {
9
+ return parse( source, filename );
10
+ }
11
+
12
+ describe( 'collect_target_imports', () => {
13
+ it( 'collects ESM imports for steps and workflows and flags changes', () => {
14
+ const dir = mkdtempSync( join( tmpdir(), 'collect-esm-' ) );
15
+ writeFileSync( join( dir, 'steps.js' ), [
16
+ 'export const StepA = step({ name: "step.a" })',
17
+ 'export const StepB = step({ name: "step.b" })'
18
+ ].join( '\n' ) );
19
+ writeFileSync( join( dir, 'workflow.js' ), [
20
+ 'export const FlowA = workflow({ name: "flow.a" })',
21
+ 'export default workflow({ name: "flow.def" })'
22
+ ].join( '\n' ) );
23
+
24
+ const source = [
25
+ 'import { StepA } from "./steps.js";',
26
+ 'import WF, { FlowA } from "./workflow.js";',
27
+ 'const x = 1;'
28
+ ].join( '\n' );
29
+
30
+ const ast = makeAst( source, join( dir, 'file.js' ) );
31
+ const { stepImports, flowImports } = collectTargetImports(
32
+ ast,
33
+ dir,
34
+ { stepsNameCache: new Map(), workflowNameCache: new Map() }
35
+ );
36
+
37
+ expect( stepImports ).toEqual( [ { localName: 'StepA', stepName: 'step.a' } ] );
38
+ expect( flowImports ).toEqual( [
39
+ { localName: 'WF', workflowName: 'flow.def' },
40
+ { localName: 'FlowA', workflowName: 'flow.a' }
41
+ ] );
42
+ // Import declarations should have been removed
43
+ expect( ast.program.body.find( n => n.type === 'ImportDeclaration' ) ).toBeUndefined();
44
+
45
+ rmSync( dir, { recursive: true, force: true } );
46
+ } );
47
+
48
+ it( 'collects CJS requires and removes declarators (steps + default workflow)', () => {
49
+ const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-' ) );
50
+ writeFileSync( join( dir, 'steps.js' ), 'export const StepB = step({ name: "step.b" })\n' );
51
+ writeFileSync( join( dir, 'workflow.js' ), 'export default workflow({ name: "flow.c" })\n' );
52
+
53
+ const source = [
54
+ 'const { StepB } = require("./steps.js");',
55
+ 'const WF = require("./workflow.js");',
56
+ 'const obj = {};'
57
+ ].join( '\n' );
58
+
59
+ const ast = makeAst( source, join( dir, 'file.js' ) );
60
+ const { stepImports, flowImports } = collectTargetImports(
61
+ ast,
62
+ dir,
63
+ { stepsNameCache: new Map(), workflowNameCache: new Map() }
64
+ );
65
+
66
+ expect( stepImports ).toEqual( [ { localName: 'StepB', stepName: 'step.b' } ] );
67
+ expect( flowImports ).toEqual( [ { localName: 'WF', workflowName: 'flow.c' } ] );
68
+ // All require-based declarators should have been removed (only non-require decls may remain)
69
+ const hasRequireDecl = ast.program.body.some( n =>
70
+ n.type === 'VariableDeclaration' && n.declarations.some( d => d.init && d.init.type === 'CallExpression' )
71
+ );
72
+ expect( hasRequireDecl ).toBe( false );
73
+
74
+ rmSync( dir, { recursive: true, force: true } );
75
+ } );
76
+ } );
77
+
@@ -0,0 +1,3 @@
1
+ export const NodeType = {
2
+ CONST: 'const'
3
+ };
@@ -0,0 +1,56 @@
1
+ import { dirname } from 'node:path';
2
+ import generatorModule from '@babel/generator';
3
+ import { parse } from './tools.js';
4
+
5
+ import rewriteFnBodies from './rewrite_fn_bodies.js';
6
+ import collectTargetImports from './collect_target_imports.js';
7
+
8
+ // Handle CJS/ESM interop for Babel packages when executed as a webpack loader
9
+ const generate = generatorModule.default ?? generatorModule;
10
+
11
+ // Caches to avoid re-reading files during a build
12
+ const stepsNameCache = new Map(); // path -> Map<exported, stepName>
13
+ const workflowNameCache = new Map(); // path -> { default?: name, named: Map<exported, flowName> }
14
+
15
+ /**
16
+ * Webpack loader that rewrites step/workflow calls by reading names from
17
+ * the respective modules and transforming `fn` bodies accordingly.
18
+ * Preserves sourcemaps.
19
+ *
20
+ * @param {string|Buffer} source - Module source code.
21
+ * @param {any} inputMap - Incoming source map.
22
+ * @this {import('webpack').LoaderContext<{}>}
23
+ * @returns {void}
24
+ */
25
+ export default function stepImportRewriterAstLoader( source, inputMap ) {
26
+ this.cacheable?.( true );
27
+ const callback = this.async?.() ?? this.callback;
28
+ const cache = { stepsNameCache, workflowNameCache };
29
+
30
+ try {
31
+ const filename = this.resourcePath;
32
+ const ast = parse( String( source ), filename );
33
+ const fileDir = dirname( filename );
34
+ const { stepImports, flowImports } = collectTargetImports( ast, fileDir, cache );
35
+
36
+ // No imports
37
+ if ( stepImports.length + flowImports.length === 0 ) {
38
+ return callback( null, source, inputMap );
39
+ }
40
+
41
+ const rewrote = rewriteFnBodies( ast, stepImports, flowImports );
42
+ // No edits performed
43
+ if ( !rewrote ) { return callback( null, source, inputMap ); }
44
+
45
+ const { code, map } = generate( ast, {
46
+ sourceMaps: true,
47
+ sourceFileName: filename,
48
+ quotes: 'single',
49
+ jsescOption: { quotes: 'single' }
50
+ }, String( source ) );
51
+ return callback( null, code, map ?? inputMap );
52
+ } catch ( err ) {
53
+ // Fail gracefully as loader error
54
+ return callback( err );
55
+ }
56
+ };
@@ -0,0 +1,129 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import loader from './index.mjs';
6
+
7
+ function runLoader( source, resourcePath ) {
8
+ return new Promise( ( resolve, reject ) => {
9
+ const ctx = {
10
+ resourcePath,
11
+ cacheable: () => {},
12
+ async: () => ( err, code, map ) => ( err ? reject( err ) : resolve( { code, map } ) ),
13
+ callback: ( err, code, map ) => ( err ? reject( err ) : resolve( { code, map } ) )
14
+ };
15
+ loader.call( ctx, source, null );
16
+ } );
17
+ }
18
+
19
+ describe( 'workflows_rewriter Webpack loader spec', () => {
20
+ it( 'rewrites ESM imports and converts fn arrow to function', async () => {
21
+ const dir = mkdtempSync( join( tmpdir(), 'ast-loader-esm-' ) );
22
+ writeFileSync( join( dir, 'steps.js' ), 'export const StepA = step({ name: \'step.a\' })\n' );
23
+ writeFileSync( join( dir, 'workflow.js' ), [
24
+ 'export const FlowA = workflow({ name: \'flow.a\' })',
25
+ 'export default workflow({ name: \'flow.def\' })'
26
+ ].join( '\n' ) );
27
+
28
+ const source = [
29
+ 'import { StepA } from \'./steps.js\';',
30
+ 'import FlowDef, { FlowA } from \'./workflow.js\';',
31
+ '',
32
+ 'const obj = {',
33
+ ' fn: async (x) => {',
34
+ ' StepA(1);',
35
+ ' FlowA(2);',
36
+ ' FlowDef(3);',
37
+ ' }',
38
+ '}',
39
+ ''
40
+ ].join( '\n' );
41
+
42
+ const { code } = await runLoader( source, join( dir, 'file.js' ) );
43
+
44
+ expect( code ).not.toMatch( /from '\.\/steps\.js'/ );
45
+ expect( code ).not.toMatch( /from '\.\/workflow\.js'/ );
46
+ expect( code ).toMatch( /fn:\s*async function \(x\)/ );
47
+ expect( code ).toMatch( /this\.invokeStep\('step\.a',\s*1\)/ );
48
+ expect( code ).toMatch( /this\.startWorkflow\('flow\.a',\s*2\)/ );
49
+ expect( code ).toMatch( /this\.startWorkflow\('flow\.def',\s*3\)/ );
50
+
51
+ rmSync( dir, { recursive: true, force: true } );
52
+ } );
53
+
54
+ it( 'rewrites CJS requires and converts fn arrow to function', async () => {
55
+ const dir = mkdtempSync( join( tmpdir(), 'ast-loader-cjs-' ) );
56
+ writeFileSync( join( dir, 'steps.js' ), 'export const StepB = step({ name: \'step.b\' })\n' );
57
+ writeFileSync( join( dir, 'workflow.js' ), 'export default workflow({ name: \'flow.c\' })\n' );
58
+
59
+ const source = [
60
+ 'const { StepB } = require(\'./steps.js\');',
61
+ 'const FlowDefault = require(\'./workflow.js\');',
62
+ '',
63
+ 'const obj = {',
64
+ ' fn: async (y) => {',
65
+ ' StepB();',
66
+ ' FlowDefault();',
67
+ ' }',
68
+ '}',
69
+ ''
70
+ ].join( '\n' );
71
+
72
+ const { code } = await runLoader( source, join( dir, 'file.js' ) );
73
+
74
+ expect( code ).not.toMatch( /require\('\.\/steps\.js'\)/ );
75
+ expect( code ).not.toMatch( /require\('\.\/workflow\.js'\)/ );
76
+ expect( code ).toMatch( /fn:\s*async function \(y\)/ );
77
+ expect( code ).toMatch( /this\.invokeStep\('step\.b'\)/ );
78
+ expect( code ).toMatch( /this\.startWorkflow\('flow\.c'\)/ );
79
+
80
+ rmSync( dir, { recursive: true, force: true } );
81
+ } );
82
+
83
+ it( 'resolves top-level const name variables', async () => {
84
+ const dir = mkdtempSync( join( tmpdir(), 'ast-loader-const-' ) );
85
+ writeFileSync( join( dir, 'steps.js' ), [
86
+ 'const NAME = \'step.const\'',
87
+ 'export const StepC = step({ name: NAME })'
88
+ ].join( '\n' ) );
89
+ writeFileSync( join( dir, 'workflow.js' ), [
90
+ 'const WF = \'wf.const\'',
91
+ 'export const FlowC = workflow({ name: WF })',
92
+ 'const D = \'wf.def\'',
93
+ 'export default workflow({ name: D })'
94
+ ].join( '\n' ) );
95
+
96
+ const source = [
97
+ 'import { StepC } from \'./steps.js\';',
98
+ 'import FlowDef, { FlowC } from \'./workflow.js\';',
99
+ 'const obj = { fn: async () => { StepC(); FlowC(); FlowDef(); } }'
100
+ ].join( '\n' );
101
+
102
+ const { code } = await runLoader( source, join( dir, 'file.js' ) );
103
+ expect( code ).toMatch( /this\.invokeStep\('step\.const'\)/ );
104
+ expect( code ).toMatch( /this\.startWorkflow\('wf\.const'\)/ );
105
+ expect( code ).toMatch( /this\.startWorkflow\('wf\.def'\)/ );
106
+ rmSync( dir, { recursive: true, force: true } );
107
+ } );
108
+
109
+ it( 'throws on non-static name', async () => {
110
+ const dir = mkdtempSync( join( tmpdir(), 'ast-loader-error-' ) );
111
+ writeFileSync( join( dir, 'steps.js' ), [
112
+ 'function n() { return \'x\' }',
113
+ 'export const StepX = step({ name: n() })'
114
+ ].join( '\n' ) );
115
+ writeFileSync( join( dir, 'workflow.js' ), [
116
+ 'const base = \'a\'',
117
+ 'export default workflow({ name: `${base}-b` })'
118
+ ].join( '\n' ) );
119
+
120
+ const source = [
121
+ 'import { StepX } from \'./steps.js\';',
122
+ 'import WF from \'./workflow.js\';',
123
+ 'const obj = { fn: async () => { StepX(); WF(); } }'
124
+ ].join( '\n' );
125
+
126
+ await expect( runLoader( source, join( dir, 'file.js' ) ) ).rejects.toThrow( /Invalid (step|default workflow) name/ );
127
+ rmSync( dir, { recursive: true, force: true } );
128
+ } );
129
+ } );
@@ -0,0 +1,64 @@
1
+ import traverseModule from '@babel/traverse';
2
+ import {
3
+ isArrowFunctionExpression,
4
+ isIdentifier,
5
+ isFunctionExpression
6
+ } from '@babel/types';
7
+ import { toFunctionExpression, createThisMethodCall } from './tools.js';
8
+
9
+ // Handle CJS/ESM interop for Babel packages when executed as a webpack loader
10
+ const traverse = traverseModule.default ?? traverseModule;
11
+
12
+ /**
13
+ * Rewrite calls to imported steps/workflows within `fn` object properties.
14
+ * Converts arrow fns to functions and replaces `StepX(...)` with
15
+ * `this.invokeStep('name', ...)` and `FlowY(...)` with
16
+ * `this.startWorkflow('name', ...)`.
17
+ *
18
+ * @param {import('@babel/types').File} ast - Parsed file AST.
19
+ * @param {Array<{localName:string,stepName:string}>} stepImports - Step imports.
20
+ * @param {Array<{localName:string,workflowName:string}>} flowImports - Workflow imports.
21
+ * @returns {boolean} True if the AST was modified; false otherwise.
22
+ */
23
+ export default function rewriteFnBodies( ast, stepImports, flowImports ) {
24
+ const state = { rewrote: false };
25
+ traverse( ast, {
26
+ ObjectProperty: path => {
27
+ // If it is not fn property: skip
28
+ if ( !isIdentifier( path.node.key, { name: 'fn' } ) ) { return; }
29
+
30
+ const val = path.node.value;
31
+
32
+ // if it is not function, return
33
+ if ( !isFunctionExpression( val ) && !isArrowFunctionExpression( val ) ) { return; }
34
+
35
+ // replace arrow fn in favor of a function
36
+ if ( isArrowFunctionExpression( val ) ) {
37
+ const func = toFunctionExpression( val );
38
+ path.get( 'value' ).replaceWith( func );
39
+ state.rewrote = true;
40
+ }
41
+
42
+ path.get( 'value.body' ).traverse( {
43
+ CallExpression: cPath => {
44
+ const callee = cPath.node.callee;
45
+ if ( !isIdentifier( callee ) ) { return; } // Skip: complex callee not supported
46
+ const step = stepImports.find( x => x.localName === callee.name );
47
+ if ( step ) {
48
+ const args = cPath.node.arguments;
49
+ cPath.replaceWith( createThisMethodCall( 'invokeStep', step.stepName, args ) );
50
+ state.rewrote = true;
51
+ return; // Stop after rewriting as step call
52
+ }
53
+ const flow = flowImports.find( x => x.localName === callee.name );
54
+ if ( flow ) {
55
+ const args = cPath.node.arguments;
56
+ cPath.replaceWith( createThisMethodCall( 'startWorkflow', flow.workflowName, args ) );
57
+ state.rewrote = true;
58
+ }
59
+ }
60
+ } );
61
+ }
62
+ } );
63
+ return state.rewrote;
64
+ };
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parse } from './tools.js';
3
+ import rewriteFnBodies from './rewrite_fn_bodies.js';
4
+
5
+ describe( 'rewrite_fn_bodies', () => {
6
+ it( 'converts arrow to function and rewrites step/workflow calls', () => {
7
+ const src = [
8
+ 'const obj = {',
9
+ ' fn: async (x) => {',
10
+ ' StepA(1);',
11
+ ' FlowB(2);',
12
+ ' }',
13
+ '}'
14
+ ].join( '\n' );
15
+ const ast = parse( src, 'file.js' );
16
+ const stepImports = [ { localName: 'StepA', stepName: 'step.a' } ];
17
+ const flowImports = [ { localName: 'FlowB', workflowName: 'flow.b' } ];
18
+
19
+ const rewrote = rewriteFnBodies( ast, stepImports, flowImports );
20
+ expect( rewrote ).toBe( true );
21
+
22
+ const code = ast.program.body.map( n => n.type ).length; // smoke: ast mutated
23
+ expect( code ).toBeGreaterThan( 0 );
24
+ } );
25
+
26
+ it( 'does nothing when no matching calls are present', () => {
27
+ const src = [ 'const obj = { fn: function() { other(); } }' ].join( '\n' );
28
+ const ast = parse( src, 'file.js' );
29
+ const rewrote = rewriteFnBodies( ast, [], [] );
30
+ expect( rewrote ).toBe( false );
31
+ } );
32
+ } );
33
+