-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
129 lines (115 loc) · 3.75 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import {
parse,
TSESTree,
AST_NODE_TYPES,
} from '@typescript-eslint/typescript-estree'
import * as YAML from 'yaml'
import { SubworkflowAST } from '../ast/steps.js'
import { WorkflowSyntaxError } from '../errors.js'
import { WorkflowParameter } from '../ast/workflows.js'
import { generateStepNames } from '../ast/stepnames.js'
import { parseStatement } from './statements.js'
export function transpile(code: string): string {
const parserOptions = {
jsDocParsingMode: 'none' as const,
loc: true,
range: false,
}
const ast = parse(code, parserOptions)
const workflowAst = { subworkflows: ast.body.flatMap(parseTopLevelStatement) }
const workflow = generateStepNames(workflowAst)
return YAML.stringify(workflow.render(), { lineWidth: 100 })
}
function parseTopLevelStatement(
node: TSESTree.ProgramStatement,
): SubworkflowAST[] {
switch (node.type) {
case AST_NODE_TYPES.FunctionDeclaration:
return [parseSubworkflows(node)]
case AST_NODE_TYPES.ImportDeclaration:
if (
node.specifiers.some(
(spec) =>
spec.type === AST_NODE_TYPES.ImportNamespaceSpecifier ||
spec.type === AST_NODE_TYPES.ImportDefaultSpecifier,
)
) {
throw new WorkflowSyntaxError(
'Only named imports are allowed',
node.loc,
)
}
return []
case AST_NODE_TYPES.ExportNamedDeclaration:
// "export" keyword is ignored, but a possible function declaration is transpiled.
if (
node.declaration?.type === AST_NODE_TYPES.FunctionDeclaration &&
node.declaration.id?.type === AST_NODE_TYPES.Identifier
) {
// Why is "as" needed here?
return parseTopLevelStatement(
node.declaration as TSESTree.FunctionDeclarationWithName,
)
} else {
return []
}
case AST_NODE_TYPES.TSInterfaceDeclaration:
case AST_NODE_TYPES.TSTypeAliasDeclaration:
case AST_NODE_TYPES.TSDeclareFunction:
// Ignore "type", "interface" and "declare function" at the top-level
return []
default:
throw new WorkflowSyntaxError(
`Only function definitions, imports and type aliases allowed at the top level, encountered ${node.type}`,
node.loc,
)
}
}
function parseSubworkflows(
node: TSESTree.FunctionDeclarationWithName,
): SubworkflowAST {
const nodeParams = node.params
const workflowParams: WorkflowParameter[] = nodeParams.map((param) => {
switch (param.type) {
case AST_NODE_TYPES.Identifier:
return { name: param.name }
case AST_NODE_TYPES.AssignmentPattern:
if (param.left.type !== AST_NODE_TYPES.Identifier) {
throw new WorkflowSyntaxError(
'The default value must be an identifier',
param.left.loc,
)
}
if (param.right.type !== AST_NODE_TYPES.Literal) {
throw new WorkflowSyntaxError(
'The default value must be a literal',
param.right.loc,
)
}
if (
!(
typeof param.right.value === 'string' ||
typeof param.right.value === 'number' ||
typeof param.right.value === 'boolean' ||
param.right.value === null
)
) {
throw new WorkflowSyntaxError(
'The default value must be a string, number, boolean or null',
param.left.loc,
)
}
return {
name: param.left.name,
default: param.right.value,
}
default:
throw new WorkflowSyntaxError(
'Function parameter must be an identifier or an assignment',
param.loc,
)
}
})
const steps = parseStatement(node.body, {})
return new SubworkflowAST(node.id.name, steps, workflowParams)
}