-
Notifications
You must be signed in to change notification settings - Fork 135
/
param-parser.ts
152 lines (143 loc) · 3.84 KB
/
param-parser.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import { createTokenizer, Token, TokenizerFlags } from './function-tokenizer'
/**
* A parameter for a function.
*/
export interface Parameter {
/**
* Parameter name.
*/
name: string
/**
* True if the parameter is optional.
*/
optional: boolean
}
/*
* Parses the parameter list of a function string, including ES6 class constructors.
*
* @param {string} source
* The source of a function to extract the parameter list from
*
* @return {Array<Parameter> | null}
* Returns an array of parameters, or `null` if no
* constructor was found for a class.
*/
export function parseParameterList(source: string): Array<Parameter> | null {
const { next: _next, done } = createTokenizer(source)
const params: Array<Parameter> = []
let t: Token = null!
nextToken()
while (!done()) {
switch (t.type) {
case 'class':
skipUntilConstructor()
// If we didn't find a constructor token, then we know that there
// are no dependencies in the defined class.
if (!isConstructorToken()) {
return null
}
// Next token is the constructor identifier.
nextToken()
break
case 'function': {
const next = nextToken()
if (next.type === 'ident' || next.type === '*') {
// This is the function name or a generator star. Skip it.
nextToken()
}
break
}
case '(':
// Start parsing parameter names.
parseParams()
break
case ')':
// We're now out of the parameter list.
return params
case 'ident': {
// Likely a paren-less arrow function
// which can have no default args.
const param = { name: t.value!, optional: false }
if (t.value === 'async') {
// Given it's the very first token, we can assume it's an async function,
// so skip the async keyword if the next token is not an equals sign, in which
// case it is a single-arg arrow func.
const next = nextToken()
if (next && next.type !== '=') {
break
}
}
params.push(param)
return params
}
/* istanbul ignore next */
default:
throw unexpected()
}
}
return params
/**
* After having been placed within the parameter list of
* a function, parses the parameters.
*/
function parseParams() {
// Current token is a left-paren
let param: Parameter = { name: '', optional: false }
while (!done()) {
nextToken()
switch (t.type) {
case 'ident':
param.name = t.value!
break
case '=':
param.optional = true
break
case ',':
params.push(param)
param = { name: '', optional: false }
break
case ')':
if (param.name) {
params.push(param)
}
return
/* istanbul ignore next */
default:
throw unexpected()
}
}
}
/**
* Skips until we reach the constructor identifier.
*/
function skipUntilConstructor() {
while (!isConstructorToken() && !done()) {
nextToken(TokenizerFlags.Dumb)
}
}
/**
* Determines if the current token represents a constructor, and the next token after it is a paren
* @return {boolean}
*/
function isConstructorToken(): boolean {
return t.type === 'ident' && t.value === 'constructor'
}
/**
* Advances the tokenizer and stores the previous token in history
*/
function nextToken(flags = TokenizerFlags.None) {
t = _next(flags)
return t
}
/**
* Returns an error describing an unexpected token.
*/
/* istanbul ignore next */
function unexpected() {
return new SyntaxError(
`Parsing parameter list, did not expect ${t.type} token${
t.value ? ` (${t.value})` : ''
}`,
)
}
}