ts2workflows 0.2.0 → 0.4.0

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.
@@ -1,105 +1,89 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
2
- import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
- import { AssignStepAST, CallStepAST, ForStepAST, JumpTargetAST, NextStepAST, ParallelStepAST, RaiseStepAST, ReturnStepAST, StepsStepAST, SwitchStepAST, TryStepAST, stepWithLabel, } from '../ast/steps.js';
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
2
+ import { AssignStepAST, CallStepAST, ForStepAST, JumpTargetAST, NextStepAST, ParallelStepAST, RaiseStepAST, ReturnStepAST, StepsStepAST, SwitchStepAST, TryStepAST, } from '../ast/steps.js';
4
3
  import { BinaryExpression, FunctionInvocationExpression, PrimitiveExpression, VariableReferenceExpression, isExpression, isFullyQualifiedName, isLiteral, } from '../ast/expressions.js';
5
4
  import { InternalTranspilingError, WorkflowSyntaxError } from '../errors.js';
6
5
  import { isRecord } from '../utils.js';
7
6
  import { transformAST } from './transformations.js';
8
- import { assertType } from './asserts.js';
9
- import { convertExpression, convertMemberExpression, convertObjectExpression, convertObjectAsExpressionValues, } from './expressions.js';
7
+ import { convertExpression, convertMemberExpression, convertObjectExpression, convertObjectAsExpressionValues, isMagicFunction, throwIfSpread, } from './expressions.js';
10
8
  import { blockingFunctions } from './generated/functionMetadata.js';
11
- const { ArrayExpression, ArrowFunctionExpression, AssignmentExpression, BlockStatement, BreakStatement, CallExpression, ContinueStatement, DoWhileStatement, EmptyStatement, ExpressionStatement, ForInStatement, ForOfStatement, FunctionDeclaration, Identifier, IfStatement, LabeledStatement, MemberExpression, ObjectExpression, ReturnStatement, SwitchCase, SwitchStatement, ThrowStatement, TryStatement, TSDeclareFunction, TSTypeAliasDeclaration, TSInterfaceDeclaration, VariableDeclaration, VariableDeclarator, WhileStatement, } = AST_NODE_TYPES;
12
- export function parseBlockStatement(node, ctx) {
13
- assertType(node, BlockStatement);
14
- const body = node.body;
15
- return transformAST(body.flatMap((x) => parseStep(x, ctx)));
9
+ export function parseStatement(node, ctx, postSteps) {
10
+ const steps = parseStatementRecursively(node, ctx);
11
+ return transformAST(steps.concat(postSteps ?? []));
16
12
  }
17
- function parseStep(node, ctx) {
13
+ function parseStatementRecursively(node, ctx) {
18
14
  switch (node.type) {
19
- case VariableDeclaration:
20
- return convertVariableDeclarations(node.declarations);
21
- case ExpressionStatement:
22
- if (node.expression.type === AssignmentExpression) {
23
- return assignmentExpressionToSteps(node.expression);
15
+ case AST_NODE_TYPES.BlockStatement:
16
+ return node.body.flatMap((statement) => parseStatementRecursively(statement, ctx));
17
+ case AST_NODE_TYPES.VariableDeclaration:
18
+ return convertVariableDeclarations(node.declarations, ctx);
19
+ case AST_NODE_TYPES.ExpressionStatement:
20
+ if (node.expression.type === AST_NODE_TYPES.AssignmentExpression) {
21
+ return assignmentExpressionToSteps(node.expression, ctx);
24
22
  }
25
- else if (node.expression.type === CallExpression) {
26
- return [callExpressionToStep(node.expression, ctx)];
23
+ else if (node.expression.type === AST_NODE_TYPES.CallExpression) {
24
+ return [callExpressionToStep(node.expression, undefined, ctx)];
27
25
  }
28
26
  else {
29
27
  return [generalExpressionToAssignStep(node.expression)];
30
28
  }
31
- case ReturnStatement:
29
+ case AST_NODE_TYPES.ReturnStatement:
32
30
  return [returnStatementToReturnStep(node)];
33
- case ThrowStatement:
31
+ case AST_NODE_TYPES.ThrowStatement:
34
32
  return [throwStatementToRaiseStep(node)];
35
- case IfStatement:
33
+ case AST_NODE_TYPES.IfStatement:
36
34
  return [ifStatementToSwitchStep(node, ctx)];
37
- case SwitchStatement:
35
+ case AST_NODE_TYPES.SwitchStatement:
38
36
  return switchStatementToSteps(node, ctx);
39
- case ForInStatement:
37
+ case AST_NODE_TYPES.ForInStatement:
40
38
  throw new WorkflowSyntaxError('for...in is not a supported construct. Use for...of.', node.loc);
41
- case ForOfStatement:
39
+ case AST_NODE_TYPES.ForOfStatement:
42
40
  return [forOfStatementToForStep(node, ctx)];
43
- case DoWhileStatement:
41
+ case AST_NODE_TYPES.DoWhileStatement:
44
42
  return doWhileStatementSteps(node, ctx);
45
- case WhileStatement:
43
+ case AST_NODE_TYPES.WhileStatement:
46
44
  return whileStatementSteps(node, ctx);
47
- case BreakStatement:
45
+ case AST_NODE_TYPES.BreakStatement:
48
46
  return [breakStatementToNextStep(node, ctx)];
49
- case ContinueStatement:
47
+ case AST_NODE_TYPES.ContinueStatement:
50
48
  return [continueStatementToNextStep(node, ctx)];
51
- case TryStatement:
49
+ case AST_NODE_TYPES.TryStatement:
52
50
  return [tryStatementToTryStep(node, ctx)];
53
- case LabeledStatement:
51
+ case AST_NODE_TYPES.LabeledStatement:
54
52
  return labeledStep(node, ctx);
55
- case EmptyStatement:
53
+ case AST_NODE_TYPES.EmptyStatement:
56
54
  return [];
57
- case FunctionDeclaration:
55
+ case AST_NODE_TYPES.FunctionDeclaration:
58
56
  throw new WorkflowSyntaxError('Functions must be defined at the top level of a source file', node.loc);
59
- case TSInterfaceDeclaration:
60
- case TSTypeAliasDeclaration:
61
- case TSDeclareFunction:
57
+ case AST_NODE_TYPES.TSInterfaceDeclaration:
58
+ case AST_NODE_TYPES.TSTypeAliasDeclaration:
59
+ case AST_NODE_TYPES.TSDeclareFunction:
62
60
  // Ignore "type", "interface" and "declare function"
63
61
  return [];
64
62
  default:
65
63
  throw new WorkflowSyntaxError(`TODO: encountered unsupported type: ${node.type}`, node.loc);
66
64
  }
67
65
  }
68
- function convertVariableDeclarations(declarations) {
66
+ function convertVariableDeclarations(declarations, ctx) {
69
67
  return declarations.map((decl) => {
70
- if (decl.type !== VariableDeclarator) {
68
+ if (decl.type !== AST_NODE_TYPES.VariableDeclarator) {
71
69
  throw new WorkflowSyntaxError('Not a VariableDeclarator', decl.loc);
72
70
  }
73
- if (decl.id.type !== Identifier) {
71
+ if (decl.id.type !== AST_NODE_TYPES.Identifier) {
74
72
  throw new WorkflowSyntaxError('Expected Identifier', decl.loc);
75
73
  }
76
74
  const targetName = decl.id.name;
77
- if (!decl.init) {
78
- return new AssignStepAST([[targetName, new PrimitiveExpression(null)]]);
79
- }
80
- else if (decl.init.type === CallExpression) {
81
- const maybeBlockingcall = getBlockingCallParameters(decl.init);
82
- if (maybeBlockingcall.isBlockingCall) {
83
- // This branch could be removed. The transform step for extracting
84
- // blocking function would take care of this, but it creates an
85
- // unnecessary temporary variable assignment step.
86
- return blockingFunctionCallStep(maybeBlockingcall.functionName, maybeBlockingcall.argumentNames, decl.init.arguments, targetName);
87
- }
88
- else if (decl.init.callee.type === Identifier &&
89
- decl.init.callee.name === 'call_step') {
90
- return createCallStep(decl.init.arguments, targetName);
91
- }
92
- else {
93
- return new AssignStepAST([[targetName, convertExpression(decl.init)]]);
94
- }
75
+ if (decl.init?.type === AST_NODE_TYPES.CallExpression) {
76
+ return callExpressionToStep(decl.init, targetName, ctx);
95
77
  }
96
78
  else {
97
- return new AssignStepAST([[targetName, convertExpression(decl.init)]]);
79
+ const value = decl.init == null
80
+ ? new PrimitiveExpression(null)
81
+ : convertExpression(decl.init);
82
+ return new AssignStepAST([[targetName, value]]);
98
83
  }
99
84
  });
100
85
  }
101
- function assignmentExpressionToSteps(node) {
102
- assertType(node, AssignmentExpression);
86
+ function assignmentExpressionToSteps(node, ctx) {
103
87
  let compoundOperator = undefined;
104
88
  switch (node.operator) {
105
89
  case '=':
@@ -133,25 +117,31 @@ function assignmentExpressionToSteps(node) {
133
117
  if (!isFullyQualifiedName(targetExpression)) {
134
118
  throw new WorkflowSyntaxError('The left-hand side of an assignment must be an identifier or a property access', node.loc);
135
119
  }
120
+ let valueExpression;
121
+ const steps = [];
136
122
  const targetName = targetExpression.toString();
137
- let valueExpression = convertExpression(node.right);
123
+ if (node.right.type === AST_NODE_TYPES.CallExpression &&
124
+ node.right.callee.type === AST_NODE_TYPES.Identifier &&
125
+ isMagicFunction(node.right.callee.name)) {
126
+ const needsTempVariable = compoundOperator === undefined ||
127
+ node.left.type !== AST_NODE_TYPES.Identifier;
128
+ const resultVariable = needsTempVariable ? '__temp' : targetName;
129
+ steps.push(callExpressionToStep(node.right, resultVariable, ctx));
130
+ if (!needsTempVariable) {
131
+ return steps;
132
+ }
133
+ valueExpression = new VariableReferenceExpression('__temp');
134
+ }
135
+ else {
136
+ valueExpression = convertExpression(node.right);
137
+ }
138
138
  if (compoundOperator) {
139
139
  valueExpression = new BinaryExpression(new VariableReferenceExpression(targetName), compoundOperator, valueExpression);
140
140
  }
141
- return [new AssignStepAST([[targetName, valueExpression]])];
142
- }
143
- function getBlockingCallParameters(node) {
144
- if (node?.type === CallExpression) {
145
- const functionName = convertExpression(node.callee).toString();
146
- const argumentNames = blockingFunctions.get(functionName);
147
- if (argumentNames) {
148
- return { isBlockingCall: true, functionName, argumentNames };
149
- }
150
- }
151
- return { isBlockingCall: false };
141
+ steps.push(new AssignStepAST([[targetName, valueExpression]]));
142
+ return steps;
152
143
  }
153
- function callExpressionToStep(node, ctx) {
154
- assertType(node, CallExpression);
144
+ function callExpressionToStep(node, resultVariable, ctx) {
155
145
  const calleeExpression = convertExpression(node.callee);
156
146
  if (isFullyQualifiedName(calleeExpression)) {
157
147
  const calleeName = calleeExpression.toString();
@@ -160,46 +150,49 @@ function callExpressionToStep(node, ctx) {
160
150
  return callExpressionToParallelStep(node, ctx);
161
151
  }
162
152
  else if (calleeName === 'retry_policy') {
163
- return callExpressionToCallStep(calleeName, node.arguments);
153
+ return callExpressionToCallStep(calleeName, node.arguments, resultVariable);
164
154
  }
165
155
  else if (calleeName === 'call_step') {
166
- return createCallStep(node.arguments);
156
+ return createCallStep(node.arguments, resultVariable);
167
157
  }
168
158
  else if (blockingFunctions.has(calleeName)) {
169
159
  const argumentNames = blockingFunctions.get(calleeName) ?? [];
170
- return blockingFunctionCallStep(calleeName, argumentNames, node.arguments);
160
+ return blockingFunctionCallStep(calleeName, argumentNames, node.arguments, resultVariable);
171
161
  }
172
162
  else {
173
- return callExpressionAssignStep(calleeName, node.arguments);
163
+ return callExpressionAssignStep(calleeName, node.arguments, resultVariable);
174
164
  }
175
165
  }
176
166
  else {
177
167
  throw new WorkflowSyntaxError('Expeced a subworkflow or a standard library function name', node.callee.loc);
178
168
  }
179
169
  }
180
- function callExpressionAssignStep(functionName, argumentsNode) {
181
- const argumentExpressions = argumentsNode.map(convertExpression);
182
- const assignments = [
183
- ['', new FunctionInvocationExpression(functionName, argumentExpressions)],
184
- ];
185
- return new AssignStepAST(assignments);
170
+ function callExpressionAssignStep(functionName, argumentsNode, resultVariable) {
171
+ const argumentExpressions = throwIfSpread(argumentsNode).map(convertExpression);
172
+ return new AssignStepAST([
173
+ [
174
+ resultVariable ?? '__temp',
175
+ new FunctionInvocationExpression(functionName, argumentExpressions),
176
+ ],
177
+ ]);
186
178
  }
187
- function callExpressionToCallStep(functionName, argumentsNode) {
188
- if (argumentsNode.length < 1 || argumentsNode[0].type !== ObjectExpression) {
179
+ function callExpressionToCallStep(functionName, argumentsNode, resultVariable) {
180
+ if (argumentsNode.length < 1 ||
181
+ argumentsNode[0].type !== AST_NODE_TYPES.ObjectExpression) {
189
182
  throw new WorkflowSyntaxError('Expected one object parameter', argumentsNode[0].loc);
190
183
  }
191
184
  const workflowArguments = convertObjectAsExpressionValues(argumentsNode[0]);
192
- return new CallStepAST(functionName, workflowArguments);
185
+ return new CallStepAST(functionName, workflowArguments, resultVariable);
193
186
  }
194
187
  function createCallStep(argumentsNode, resultVariable) {
195
188
  if (argumentsNode.length < 1) {
196
189
  throw new WorkflowSyntaxError('The first argument must be a Function', argumentsNode[0].loc);
197
190
  }
198
191
  let functionName;
199
- if (argumentsNode[0].type === Identifier) {
192
+ if (argumentsNode[0].type === AST_NODE_TYPES.Identifier) {
200
193
  functionName = argumentsNode[0].name;
201
194
  }
202
- else if (argumentsNode[0].type === MemberExpression) {
195
+ else if (argumentsNode[0].type === AST_NODE_TYPES.MemberExpression) {
203
196
  const memberExp = convertMemberExpression(argumentsNode[0]);
204
197
  if (!isFullyQualifiedName(memberExp)) {
205
198
  throw new WorkflowSyntaxError('Function name must be a fully-qualified name', argumentsNode[0].loc);
@@ -211,7 +204,7 @@ function createCallStep(argumentsNode, resultVariable) {
211
204
  }
212
205
  let args = {};
213
206
  if (argumentsNode.length >= 2) {
214
- if (argumentsNode[1].type !== ObjectExpression) {
207
+ if (argumentsNode[1].type !== AST_NODE_TYPES.ObjectExpression) {
215
208
  throw new WorkflowSyntaxError('The second argument must be an object', argumentsNode[1].loc);
216
209
  }
217
210
  args = convertObjectAsExpressionValues(argumentsNode[1]);
@@ -219,12 +212,12 @@ function createCallStep(argumentsNode, resultVariable) {
219
212
  return new CallStepAST(functionName, args, resultVariable);
220
213
  }
221
214
  function blockingFunctionCallStep(functionName, argumentNames, argumentsNode, resultName) {
222
- const argumentExpressions = argumentsNode.map(convertExpression);
215
+ const argumentExpressions = throwIfSpread(argumentsNode).map(convertExpression);
223
216
  const args = Object.fromEntries(argumentNames.flatMap((argName, i) => {
224
217
  if (i >= argumentExpressions.length) {
225
218
  return [];
226
219
  }
227
- else if (argumentsNode[i].type === Identifier &&
220
+ else if (argumentsNode[i].type === AST_NODE_TYPES.Identifier &&
228
221
  argumentsNode[i].name === 'undefined') {
229
222
  return [];
230
223
  }
@@ -235,18 +228,17 @@ function blockingFunctionCallStep(functionName, argumentNames, argumentsNode, re
235
228
  return new CallStepAST(functionName, args, resultName);
236
229
  }
237
230
  function callExpressionToParallelStep(node, ctx) {
238
- assertType(node, CallExpression);
239
- assertType(node.callee, Identifier);
240
- if (node.callee.name !== 'parallel') {
241
- throw new TypeError(`The parameter must be a call to "parallel"`);
231
+ if (node.callee.type !== AST_NODE_TYPES.Identifier ||
232
+ node.callee.name !== 'parallel') {
233
+ throw new InternalTranspilingError(`The parameter must be a call to "parallel"`);
242
234
  }
243
235
  let steps = {};
244
236
  if (node.arguments.length > 0) {
245
237
  switch (node.arguments[0].type) {
246
- case ArrayExpression:
238
+ case AST_NODE_TYPES.ArrayExpression:
247
239
  steps = parseParallelBranches(node.arguments[0]);
248
240
  break;
249
- case ArrowFunctionExpression:
241
+ case AST_NODE_TYPES.ArrowFunctionExpression:
250
242
  steps = parseParallelIteration(node.arguments[0], ctx);
251
243
  break;
252
244
  default:
@@ -265,18 +257,19 @@ function callExpressionToParallelStep(node, ctx) {
265
257
  return new ParallelStepAST(steps, shared, concurrencyLimit, exceptionPolicy);
266
258
  }
267
259
  function parseParallelBranches(node) {
268
- assertType(node, ArrayExpression);
269
- const nodeElements = node.elements;
270
- const stepsArray = nodeElements.map((arg, idx) => {
260
+ const stepsArray = node.elements.map((arg, idx) => {
271
261
  const branchName = `branch${idx + 1}`;
262
+ if (arg === null) {
263
+ throw new WorkflowSyntaxError('Argument should be a function call of type () => void', node.loc);
264
+ }
272
265
  switch (arg.type) {
273
- case Identifier:
266
+ case AST_NODE_TYPES.Identifier:
274
267
  return [branchName, new StepsStepAST([new CallStepAST(arg.name)])];
275
- case ArrowFunctionExpression:
276
- return [
277
- branchName,
278
- new StepsStepAST(parseBlockStatement(arg.body, {})),
279
- ];
268
+ case AST_NODE_TYPES.ArrowFunctionExpression:
269
+ if (arg.body.type !== AST_NODE_TYPES.BlockStatement) {
270
+ throw new WorkflowSyntaxError('The body must be a block statement', arg.body.loc);
271
+ }
272
+ return [branchName, new StepsStepAST(parseStatement(arg.body, {}))];
280
273
  default:
281
274
  throw new WorkflowSyntaxError('Argument should be a function call of type () => void', arg.loc);
282
275
  }
@@ -284,15 +277,15 @@ function parseParallelBranches(node) {
284
277
  return Object.fromEntries(stepsArray);
285
278
  }
286
279
  function parseParallelIteration(node, ctx) {
287
- assertType(node, ArrowFunctionExpression);
288
- if (node.body.body.length !== 1 ||
289
- node.body.body[0].type !== ForOfStatement) {
280
+ if (node.body.type !== AST_NODE_TYPES.BlockStatement ||
281
+ node.body.body.length !== 1 ||
282
+ node.body.body[0].type !== AST_NODE_TYPES.ForOfStatement) {
290
283
  throw new WorkflowSyntaxError('The parallel function body must be a single for...of statement', node.body.loc);
291
284
  }
292
285
  return forOfStatementToForStep(node.body.body[0], ctx);
293
286
  }
294
287
  function parseParallelOptions(node) {
295
- if (node.type !== ObjectExpression) {
288
+ if (node.type !== AST_NODE_TYPES.ObjectExpression) {
296
289
  throw new WorkflowSyntaxError('The second parameter must be an object', node.loc);
297
290
  }
298
291
  const parallelOptions = convertObjectExpression(node);
@@ -325,56 +318,45 @@ function parseParallelOptions(node) {
325
318
  };
326
319
  }
327
320
  function generalExpressionToAssignStep(node) {
328
- return new AssignStepAST([['', convertExpression(node)]]);
321
+ return new AssignStepAST([['__temp', convertExpression(node)]]);
329
322
  }
330
323
  function returnStatementToReturnStep(node) {
331
- assertType(node, ReturnStatement);
332
324
  const value = node.argument ? convertExpression(node.argument) : undefined;
333
325
  return new ReturnStepAST(value);
334
326
  }
335
327
  function throwStatementToRaiseStep(node) {
336
- assertType(node, ThrowStatement);
337
328
  return new RaiseStepAST(convertExpression(node.argument));
338
329
  }
339
330
  function ifStatementToSwitchStep(node, ctx) {
340
- assertType(node, IfStatement);
341
331
  return new SwitchStepAST(flattenIfBranches(node, ctx));
342
332
  }
343
333
  function flattenIfBranches(ifStatement, ctx) {
344
- assertType(ifStatement, IfStatement);
345
- assertType(ifStatement.consequent, BlockStatement);
346
334
  const branches = [
347
335
  {
348
336
  condition: convertExpression(ifStatement.test),
349
- steps: parseBlockStatement(ifStatement.consequent, ctx),
337
+ steps: parseStatement(ifStatement.consequent, ctx),
350
338
  },
351
339
  ];
352
340
  if (ifStatement.alternate) {
353
- if (ifStatement.alternate.type === BlockStatement) {
354
- branches.push({
355
- condition: new PrimitiveExpression(true),
356
- steps: parseBlockStatement(ifStatement.alternate, ctx),
357
- });
358
- }
359
- else if (ifStatement.alternate.type === IfStatement) {
341
+ if (ifStatement.alternate.type === AST_NODE_TYPES.IfStatement) {
360
342
  branches.push(...flattenIfBranches(ifStatement.alternate, ctx));
361
343
  }
362
344
  else {
363
- throw new InternalTranspilingError(`Expected BlockStatement or IfStatement, got ${ifStatement.alternate.type}`);
345
+ branches.push({
346
+ condition: new PrimitiveExpression(true),
347
+ steps: parseStatement(ifStatement.alternate, ctx),
348
+ });
364
349
  }
365
350
  }
366
351
  return branches;
367
352
  }
368
353
  function switchStatementToSteps(node, ctx) {
369
- assertType(node, SwitchStatement);
370
354
  const endOfSwitch = new JumpTargetAST();
371
355
  const switchCtx = Object.assign({}, ctx, { breakTarget: endOfSwitch.label });
372
356
  const steps = [];
373
357
  const branches = [];
374
358
  const discriminant = convertExpression(node.discriminant);
375
- const cases = node.cases;
376
- cases.forEach((caseNode) => {
377
- assertType(caseNode, SwitchCase);
359
+ node.cases.forEach((caseNode) => {
378
360
  let condition;
379
361
  if (caseNode.test) {
380
362
  const test = convertExpression(caseNode.test);
@@ -384,8 +366,7 @@ function switchStatementToSteps(node, ctx) {
384
366
  condition = new PrimitiveExpression(true);
385
367
  }
386
368
  const jumpTarget = new JumpTargetAST();
387
- const consequent = caseNode.consequent;
388
- const body = transformAST(consequent.flatMap((x) => parseStep(x, switchCtx)));
369
+ const body = transformAST(caseNode.consequent.flatMap((x) => parseStatement(x, switchCtx)));
389
370
  steps.push(jumpTarget);
390
371
  steps.push(...body);
391
372
  branches.push({
@@ -399,18 +380,16 @@ function switchStatementToSteps(node, ctx) {
399
380
  return steps;
400
381
  }
401
382
  function forOfStatementToForStep(node, ctx) {
402
- assertType(node, ForOfStatement);
403
- assertType(node.body, BlockStatement);
404
383
  const bodyCtx = Object.assign({}, ctx, {
405
384
  continueTarget: undefined,
406
385
  breakTarget: undefined,
407
386
  });
408
- const steps = parseBlockStatement(node.body, bodyCtx);
387
+ const steps = parseStatement(node.body, bodyCtx);
409
388
  let loopVariableName;
410
- if (node.left.type === Identifier) {
389
+ if (node.left.type === AST_NODE_TYPES.Identifier) {
411
390
  loopVariableName = node.left.name;
412
391
  }
413
- else if (node.left.type === VariableDeclaration) {
392
+ else if (node.left.type === AST_NODE_TYPES.VariableDeclaration) {
414
393
  if (node.left.declarations.length !== 1) {
415
394
  throw new WorkflowSyntaxError('Only one variable can be declared here', node.left.loc);
416
395
  }
@@ -418,7 +397,9 @@ function forOfStatementToForStep(node, ctx) {
418
397
  if (declaration.init !== null) {
419
398
  throw new WorkflowSyntaxError('Initial value not allowed', declaration.init.loc);
420
399
  }
421
- assertType(declaration.id, Identifier);
400
+ if (declaration.id.type !== AST_NODE_TYPES.Identifier) {
401
+ throw new WorkflowSyntaxError(`Expected identifier, got ${declaration.id.type}`, declaration.id.loc);
402
+ }
422
403
  loopVariableName = declaration.id.name;
423
404
  }
424
405
  else {
@@ -437,15 +418,14 @@ function forOfStatementToForStep(node, ctx) {
437
418
  return new ForStepAST(steps, loopVariableName, listExpression);
438
419
  }
439
420
  function whileStatementSteps(node, ctx) {
440
- assertType(node, WhileStatement);
441
421
  const startOfLoop = new JumpTargetAST();
442
422
  const endOfLoop = new JumpTargetAST();
443
423
  const ctx2 = Object.assign({}, ctx, {
444
424
  continueTarget: startOfLoop.label,
445
425
  breakTarget: endOfLoop.label,
446
426
  });
447
- const steps = parseBlockStatement(node.body, ctx2);
448
- steps.push(new NextStepAST(startOfLoop.label));
427
+ const postSteps = [new NextStepAST(startOfLoop.label)];
428
+ const steps = parseStatement(node.body, ctx2, postSteps);
449
429
  return [
450
430
  startOfLoop,
451
431
  new SwitchStepAST([
@@ -458,7 +438,6 @@ function whileStatementSteps(node, ctx) {
458
438
  ];
459
439
  }
460
440
  function doWhileStatementSteps(node, ctx) {
461
- assertType(node, DoWhileStatement);
462
441
  const startOfLoop = new JumpTargetAST();
463
442
  const endOfLoop = new JumpTargetAST();
464
443
  const ctx2 = Object.assign({}, ctx, {
@@ -466,7 +445,7 @@ function doWhileStatementSteps(node, ctx) {
466
445
  breakTarget: endOfLoop.label,
467
446
  });
468
447
  const steps = [startOfLoop];
469
- steps.push(...parseBlockStatement(node.body, ctx2));
448
+ steps.push(...parseStatement(node.body, ctx2));
470
449
  steps.push(new SwitchStepAST([
471
450
  {
472
451
  condition: convertExpression(node.test),
@@ -478,10 +457,8 @@ function doWhileStatementSteps(node, ctx) {
478
457
  return steps;
479
458
  }
480
459
  function breakStatementToNextStep(node, ctx) {
481
- assertType(node, BreakStatement);
482
460
  let target;
483
461
  if (node.label) {
484
- assertType(node.label, Identifier);
485
462
  target = node.label.name;
486
463
  }
487
464
  else if (ctx.breakTarget) {
@@ -493,10 +470,8 @@ function breakStatementToNextStep(node, ctx) {
493
470
  return new NextStepAST(target);
494
471
  }
495
472
  function continueStatementToNextStep(node, ctx) {
496
- assertType(node, ContinueStatement);
497
473
  let target;
498
474
  if (node.label) {
499
- assertType(node.label, Identifier);
500
475
  target = node.label.name;
501
476
  }
502
477
  else if (ctx.continueTarget) {
@@ -508,14 +483,18 @@ function continueStatementToNextStep(node, ctx) {
508
483
  return new NextStepAST(target);
509
484
  }
510
485
  function tryStatementToTryStep(node, ctx) {
511
- assertType(node, TryStatement);
512
- const steps = parseBlockStatement(node.block, ctx);
513
- const exceptSteps = parseBlockStatement(node.handler.body, ctx);
486
+ const steps = parseStatement(node.block, ctx);
487
+ let exceptSteps = [];
514
488
  let errorVariable = undefined;
515
- const handlerParam = node.handler.param;
516
- if (handlerParam) {
517
- assertType(handlerParam, Identifier);
518
- errorVariable = handlerParam.name;
489
+ if (node.handler) {
490
+ exceptSteps = parseStatement(node.handler.body, ctx);
491
+ const handlerParam = node.handler.param;
492
+ if (handlerParam) {
493
+ if (handlerParam.type !== AST_NODE_TYPES.Identifier) {
494
+ throw new WorkflowSyntaxError('The error variable must be an identifier', handlerParam.loc);
495
+ }
496
+ errorVariable = handlerParam.name;
497
+ }
519
498
  }
520
499
  if (node.finalizer !== null) {
521
500
  // TODO
@@ -524,11 +503,9 @@ function tryStatementToTryStep(node, ctx) {
524
503
  return new TryStepAST(steps, exceptSteps, undefined, errorVariable);
525
504
  }
526
505
  function labeledStep(node, ctx) {
527
- assertType(node, LabeledStatement);
528
- const steps = parseStep(node.body, ctx);
529
- if (steps.length > 0) {
530
- steps[0] = stepWithLabel(steps[0], node.label.name);
531
- return steps;
506
+ const steps = parseStatement(node.body, ctx);
507
+ if (steps.length > 0 && steps[0].tag !== 'jumptarget') {
508
+ steps[0] = steps[0].withLabel(node.label.name);
532
509
  }
533
510
  return steps;
534
511
  }
@@ -7,7 +7,7 @@ import { WorkflowStepAST } from '../ast/steps.js';
7
7
  */
8
8
  export declare function transformAST(steps: WorkflowStepAST[]): WorkflowStepAST[];
9
9
  /**
10
- * Flatten switch conditions that contain a single next step.
10
+ * Merge a next step to the previous step.
11
11
  *
12
12
  * For example, transforms this:
13
13
  *
@@ -1 +1 @@
1
- {"version":3,"file":"transformations.d.ts","sourceRoot":"","sources":["../../src/transpiler/transformations.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,eAAe,EAChB,MAAM,iBAAiB,CAAA;AAuBxB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAUxE;AA0JD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,eAAe,EAAE,GACvB,eAAe,EAAE,CAEnB"}
1
+ {"version":3,"file":"transformations.d.ts","sourceRoot":"","sources":["../../src/transpiler/transformations.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,eAAe,EAChB,MAAM,iBAAiB,CAAA;AAuBxB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAUxE;AA4JD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,eAAe,EAAE,GACvB,eAAe,EAAE,CAqBnB"}
@@ -23,8 +23,9 @@ function mergeAssignSteps(steps) {
23
23
  const prev = acc.length > 0 ? acc[acc.length - 1] : null;
24
24
  if (current.tag === 'assign' &&
25
25
  prev?.tag === 'assign' &&
26
- prev.assignments.length < 50) {
27
- const merged = new AssignStepAST(prev.assignments.concat(current.assignments));
26
+ prev.assignments.length < 50 &&
27
+ !prev.next) {
28
+ const merged = new AssignStepAST(prev.assignments.concat(current.assignments), current.next);
28
29
  acc.pop();
29
30
  acc.push(merged);
30
31
  }
@@ -124,7 +125,7 @@ function parseRetryPolicyNumber(record, keyName) {
124
125
  return primitiveValue;
125
126
  }
126
127
  /**
127
- * Flatten switch conditions that contain a single next step.
128
+ * Merge a next step to the previous step.
128
129
  *
129
130
  * For example, transforms this:
130
131
  *
@@ -142,9 +143,29 @@ function parseRetryPolicyNumber(record, keyName) {
142
143
  * next: target1
143
144
  */
144
145
  export function flattenPlainNextConditions(steps) {
145
- return steps.map((step) => (step.tag === 'switch' ? flattenNext(step) : step));
146
+ return steps.reduce((acc, step) => {
147
+ if (acc.length > 0 && step.tag === 'next') {
148
+ // Merge a "next" to the previous "assign" step
149
+ const prev = acc[acc.length - 1];
150
+ if (prev.tag === 'assign' && !prev.next) {
151
+ acc.pop();
152
+ acc.push(prev.withNext(step.target));
153
+ }
154
+ else {
155
+ acc.push(step);
156
+ }
157
+ }
158
+ else if (step.tag === 'switch') {
159
+ // If the condition steps consists of a single "next", merge it with the condition
160
+ acc.push(flattenNextToCondition(step));
161
+ }
162
+ else {
163
+ acc.push(step);
164
+ }
165
+ return acc;
166
+ }, []);
146
167
  }
147
- function flattenNext(step) {
168
+ function flattenNextToCondition(step) {
148
169
  const transformedBranches = step.branches.map((cond) => {
149
170
  if (!cond.next && cond.steps.length === 1 && cond.steps[0].tag === 'next') {
150
171
  const nextStep = cond.steps[0];
@@ -270,7 +291,7 @@ function transformExpressionsAssign(step, transform) {
270
291
  newSteps.push(...steps2);
271
292
  return [name, ex2];
272
293
  });
273
- newSteps.push(new AssignStepAST(newAssignments, step.label));
294
+ newSteps.push(new AssignStepAST(newAssignments, step.next, step.label));
274
295
  return newSteps;
275
296
  }
276
297
  else {