-
-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathextract_main_method.ts
More file actions
139 lines (113 loc) · 3.43 KB
/
extract_main_method.ts
File metadata and controls
139 lines (113 loc) · 3.43 KB
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
130
131
132
133
134
135
136
137
138
import { guardIdentifier } from '@exercism/static-analysis'
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'
import { extractNamedFunction } from '~src/extracts/extract_named_function'
type Program = TSESTree.Program
type Node = TSESTree.Node
type ArrowFunctionExpression = TSESTree.ArrowFunctionExpression
type FunctionDeclaration = TSESTree.FunctionDeclaration
type FunctionExpression = TSESTree.FunctionExpression
type Identifier = TSESTree.Identifier
type AnyMainMethodNode =
| FunctionDeclaration
| ArrowFunctionExpression
| FunctionExpression
/**
* @deprecated use extractNamedFunction instead
*/
export type MainMethod<
T extends string = string,
TNode extends AnyMainMethodNode = AnyMainMethodNode
> = {
id: Identifier & { name: T }
parent: undefined | Node
} & TNode
export function extractMainMethod<T extends string = string>(
program: Program,
name: T
): MainMethod<T> | undefined {
const fn = extractNamedFunction(name, program)
if (!fn) {
return undefined
}
const { node } = fn
switch (node.type) {
case AST_NODE_TYPES.FunctionDeclaration: {
if (!guardIdentifier(node.id)) {
return undefined
}
return {
...node,
parent: undefined,
id: node.id as Identifier & { name: T },
}
}
case AST_NODE_TYPES.ArrowFunctionExpression: {
const { id, ...rest } = node
return {
...rest,
id: {
type: AST_NODE_TYPES.Identifier,
name,
loc: node.loc,
range: node.range,
},
} as MainMethod<T, ArrowFunctionExpression>
}
case AST_NODE_TYPES.FunctionExpression: {
const { id, ...rest } = node
return {
...rest,
id: {
type: AST_NODE_TYPES.Identifier,
name,
loc: node.loc,
range: node.range,
},
} as MainMethod<T, FunctionExpression>
}
}
return undefined
}
function isNewExpression(node: unknown): node is TSESTree.NewExpression {
return (
typeof node === 'object' &&
node !== null &&
(node as TSESTree.Node).type === 'NewExpression'
)
}
function isStubThrowStatement(statement: TSESTree.Statement): boolean {
if (statement.type !== 'ThrowStatement') return false
const argument = statement.argument
if (!isNewExpression(argument)) return false
const callee = argument.callee
if (callee.type !== 'Identifier' || callee.name !== 'Error') return false
const [firstArg] = argument.arguments
if (!firstArg || firstArg.type !== 'Literal') return false
if (typeof firstArg.value !== 'string') return false
return (
firstArg.value.includes('Please implement') ||
firstArg.value.includes('Remove this line and implement') ||
firstArg.value.includes('Implement the') ||
firstArg.value.includes('Remove this statement and implement')
)
}
export function hasStubThrow(fn: { body?: TSESTree.Node }): boolean {
if (!fn.body || fn.body.type !== 'BlockStatement') return false
const statements = fn.body.body
// Case 1: single-line stub throw
if (statements.length === 1 && isStubThrowStatement(statements[0])) {
return true
}
// Case 2: unreachable stub throw after return
for (let i = 0; i < statements.length - 1; i++) {
const current = statements[i]
const next = statements[i + 1]
if (
current.type === 'ReturnStatement' &&
isStubThrowStatement(next)
) {
return true
}
}
return false
}