yuku-ast 0.0.3
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.
- package/README.md +247 -0
- package/dist/identifier.d.mts +82 -0
- package/dist/identifier.mjs +156 -0
- package/dist/index.d.mts +641 -0
- package/dist/index.mjs +2306 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# yuku-ast
|
|
2
|
+
|
|
3
|
+
A fast, fully typed AST toolkit for [`yuku-parser`](https://www.npmjs.com/package/yuku-parser):
|
|
4
|
+
node builders, type guards, a walker, and identifier validators. No runtime dependencies.
|
|
5
|
+
|
|
6
|
+
| Import | Provides |
|
|
7
|
+
| --------------------- | ------------------------------------------------------------ |
|
|
8
|
+
| `yuku-ast` | `walk`, `walkAsync`, `b` (node builders), `is` (type guards) |
|
|
9
|
+
| `yuku-ast/identifier` | identifier and reserved-word validators |
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { parse } from "yuku-parser";
|
|
13
|
+
import { walk } from "yuku-ast";
|
|
14
|
+
|
|
15
|
+
const { program } = parse("const greet = (name) => `hi ${name}`;");
|
|
16
|
+
|
|
17
|
+
walk(program, {
|
|
18
|
+
Identifier(node) {
|
|
19
|
+
console.log(node.name); // greet, name, name
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
bun add yuku-ast yuku-parser
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`yuku-parser` is a peer dependency.
|
|
31
|
+
|
|
32
|
+
## Visitors
|
|
33
|
+
|
|
34
|
+
A visitor is keyed by node `type`, by an alias group, or by the universal
|
|
35
|
+
`enter` / `leave`. Each handler receives the typed node and a [`path`](#the-path).
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
walk(program, {
|
|
39
|
+
// a concrete node type, `node` is precisely typed
|
|
40
|
+
CallExpression(node, path) {
|
|
41
|
+
if (path.parent?.type === "AwaitExpression") return;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// an alias group fires for a whole family
|
|
45
|
+
Function(node) {
|
|
46
|
+
node.params; // FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | ...
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// enter/leave for any node type
|
|
50
|
+
MemberExpression: {
|
|
51
|
+
enter(node) {},
|
|
52
|
+
leave(node) {},
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// run for every node
|
|
56
|
+
enter(node) {},
|
|
57
|
+
leave(node) {},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Aliases: `Expression`, `Statement`, `Declaration`, `ModuleDeclaration`,
|
|
62
|
+
`Function`, `Class`, `Method`, `Loop`, `Pattern`, `JSX`, `TSType`.
|
|
63
|
+
|
|
64
|
+
When several handlers match a node, `enter` runs outermost first
|
|
65
|
+
(universal, alias, concrete) and `leave` runs in reverse.
|
|
66
|
+
|
|
67
|
+
## The path
|
|
68
|
+
|
|
69
|
+
Every visitor receives a `path` describing where it is and how to change the tree.
|
|
70
|
+
Each node has its own path, so it stays valid after the visit returns.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
node: T // the current node
|
|
74
|
+
parent: Node | null // owner, or null at the root
|
|
75
|
+
key: string | null // field on the parent holding this node
|
|
76
|
+
index: number | null // position within an array field, else null
|
|
77
|
+
ancestors: readonly Node[] // root down to the parent
|
|
78
|
+
state: S // value threaded through the walk
|
|
79
|
+
|
|
80
|
+
skip() // do not descend into this node
|
|
81
|
+
stop() // end the walk
|
|
82
|
+
replace(next) // swap this node, then walk the replacement
|
|
83
|
+
remove() // detach from the parent
|
|
84
|
+
insertBefore(node) // add a sibling before (array fields)
|
|
85
|
+
insertAfter(node) // add a sibling after (array fields)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
walk(program, {
|
|
90
|
+
DebuggerStatement(_node, path) {
|
|
91
|
+
path.remove();
|
|
92
|
+
},
|
|
93
|
+
Identifier(node, path) {
|
|
94
|
+
if (node.name === "foo") path.replace(b.identifier("bar"));
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`replace` walks into the replacement's children but does not re-run its own
|
|
100
|
+
`enter`. A synthetic replacement (one built with `b.*`) inherits the replaced
|
|
101
|
+
node's source span, so generated output still maps back to the original.
|
|
102
|
+
|
|
103
|
+
## Type guards (`is`)
|
|
104
|
+
|
|
105
|
+
A guard for every node `type`, the alias families, and the variants that share a
|
|
106
|
+
`type` string. All accept `null` / `undefined` and return `false`.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { is } from "yuku-ast";
|
|
110
|
+
|
|
111
|
+
is.CallExpression(node); // every concrete type
|
|
112
|
+
is.Expression(node); // families
|
|
113
|
+
is.StringLiteral(node); // Literal variants
|
|
114
|
+
is.StaticMemberExpression(node); // MemberExpression variants
|
|
115
|
+
is.BindingIdentifier(node); // Identifier roles, via `kind`
|
|
116
|
+
|
|
117
|
+
walk(program, {
|
|
118
|
+
Literal(node, path) {
|
|
119
|
+
if (is.ArrayExpression(path.parent)) {
|
|
120
|
+
// path.parent is narrowed to ArrayExpression
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Builders (`b`)
|
|
127
|
+
|
|
128
|
+
A builder for every node type. Required fields are positional, the rest go in a
|
|
129
|
+
trailing options object. Synthetic nodes get a zero span.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { b } from "yuku-ast";
|
|
133
|
+
|
|
134
|
+
b.identifier("x");
|
|
135
|
+
b.callExpression(b.identifier("f"), [b.numericLiteral(1)]);
|
|
136
|
+
b.arrowFunctionExpression([b.identifier("a", "binding")], b.identifier("a"));
|
|
137
|
+
b.variableDeclaration("const", [
|
|
138
|
+
b.variableDeclarator(b.identifier("x", "binding"), b.numericLiteral(0)),
|
|
139
|
+
]);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Transform and print
|
|
143
|
+
|
|
144
|
+
Pair it with [`yuku-codegen`](https://www.npmjs.com/package/yuku-codegen) to parse,
|
|
145
|
+
rewrite, and print back to source.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { parse } from "yuku-parser";
|
|
149
|
+
import { print } from "yuku-codegen";
|
|
150
|
+
import { is, b, walk } from "yuku-ast";
|
|
151
|
+
|
|
152
|
+
const { program } = parse("const x = 1; debugger; foo(x, 2);");
|
|
153
|
+
|
|
154
|
+
walk(program, {
|
|
155
|
+
DebuggerStatement(_node, path) {
|
|
156
|
+
path.remove();
|
|
157
|
+
},
|
|
158
|
+
Identifier(node, path) {
|
|
159
|
+
if (node.name === "foo") path.replace(b.identifier("bar"));
|
|
160
|
+
},
|
|
161
|
+
Literal(node, path) {
|
|
162
|
+
if (is.NumericLiteral(node)) path.replace(b.numericLiteral((node.value ?? 0) * 10));
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
console.log(print(program).code);
|
|
167
|
+
// const x = 10;
|
|
168
|
+
// bar(x, 20);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`walk` edits the tree in place, so a transform is just a reusable visitor object,
|
|
172
|
+
and passes compose: run as many as you like, then print once.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { b, walk, type Visitors } from "yuku-ast";
|
|
176
|
+
|
|
177
|
+
const stripDebugger: Visitors = {
|
|
178
|
+
DebuggerStatement: (_node, path) => path.remove(),
|
|
179
|
+
};
|
|
180
|
+
const inlineDevFlag: Visitors = {
|
|
181
|
+
Identifier(node, path) {
|
|
182
|
+
if (node.name === "__DEV__") path.replace(b.booleanLiteral(false));
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const { program } = parse("if (__DEV__) log(); debugger; run();");
|
|
187
|
+
for (const transform of [stripDebugger, inlineDevFlag]) {
|
|
188
|
+
walk(program, transform);
|
|
189
|
+
}
|
|
190
|
+
console.log(print(program).code);
|
|
191
|
+
// if (false) log();
|
|
192
|
+
// run();
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Replacements built with `b.*` inherit the replaced node's source span, so
|
|
196
|
+
`yuku-codegen` source maps still point back to the original input.
|
|
197
|
+
|
|
198
|
+
## Async
|
|
199
|
+
|
|
200
|
+
`walkAsync` is the same walk with awaitable visitors, run sequentially in
|
|
201
|
+
depth-first order. Reach for it only when a visitor must await I/O; `walk` is
|
|
202
|
+
faster otherwise.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { walkAsync } from "yuku-ast";
|
|
206
|
+
|
|
207
|
+
await walkAsync(program, {
|
|
208
|
+
async ImportDeclaration(node, path) {
|
|
209
|
+
if (!(await exists(node.source.value))) path.remove();
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Identifiers
|
|
215
|
+
|
|
216
|
+
Identifier and reserved-word helpers for raw strings and code points.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { isIdentifierName, isValidIdentifier } from "yuku-ast/identifier";
|
|
220
|
+
|
|
221
|
+
isIdentifierName("π"); // true - a well-formed IdentifierName
|
|
222
|
+
isIdentifierName("class"); // true - purely syntactic, keywords included
|
|
223
|
+
isValidIdentifier("class"); // false - rejects reserved words (use for bindings)
|
|
224
|
+
isValidIdentifier("π"); // true
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| Function | Checks |
|
|
228
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------- |
|
|
229
|
+
| `isIdentifierName(name)` | `name` is a syntactically valid `IdentifierName` |
|
|
230
|
+
| `isValidIdentifier(name, reserved?)` | a valid binding name; rejects reserved words unless `reserved` is `false` |
|
|
231
|
+
| `isIdentifierStart(cp)` / `isIdentifierChar(cp)` | a code point may start / continue an identifier |
|
|
232
|
+
| `isKeyword(word)` | `word` is a core grammar keyword (`if`, `class`, …) |
|
|
233
|
+
| `isReservedWord(word, inModule?)` | reserved everywhere (`enum`; `await` in modules) |
|
|
234
|
+
| `isStrictReservedWord(word, inModule?)` | also reserved in strict mode (`let`, `yield`, …) |
|
|
235
|
+
| `isStrictBindOnlyReservedWord(word)` | `eval` / `arguments` |
|
|
236
|
+
| `isStrictBindReservedWord(word, inModule?)` | strict reserved, plus `eval` / `arguments` |
|
|
237
|
+
|
|
238
|
+
## Performance
|
|
239
|
+
|
|
240
|
+
The walk reads a fixed table of child fields per node type rather than reflecting
|
|
241
|
+
over keys, and dispatches through a cached, per-type handler list. On a large
|
|
242
|
+
TypeScript file it walks several times faster than `@babel/traverse` and on par
|
|
243
|
+
with the lightest reflection walkers, while staying fully typed.
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//#region src/identifier/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Returns `true` if the Unicode code point `cp` can **start** an identifier:
|
|
4
|
+
* any character with the Unicode `ID_Start` property, plus `$` and `_`.
|
|
5
|
+
*
|
|
6
|
+
* Takes a numeric code point (as from {@link String.prototype.codePointAt}),
|
|
7
|
+
* mirroring Babel's `isIdentifierStart`. Returns `false` for values that are
|
|
8
|
+
* not a valid code point.
|
|
9
|
+
*/
|
|
10
|
+
declare function isIdentifierStart(cp: number): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Returns `true` if the Unicode code point `cp` can appear **after** the first
|
|
13
|
+
* character of an identifier: any character with the Unicode `ID_Continue`
|
|
14
|
+
* property, plus `$`, `_`, and the ZWNJ (U+200C) / ZWJ (U+200D) joiners.
|
|
15
|
+
*
|
|
16
|
+
* Takes a numeric code point, mirroring Babel's `isIdentifierChar`. Returns
|
|
17
|
+
* `false` for values that are not a valid code point.
|
|
18
|
+
*/
|
|
19
|
+
declare function isIdentifierChar(cp: number): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Returns `true` if `name` is a valid ECMAScript `IdentifierName`: the grammar
|
|
22
|
+
* an identifier token must satisfy.
|
|
23
|
+
*
|
|
24
|
+
* Unlike the `is.IdentifierName` node guard, this validates a raw string rather
|
|
25
|
+
* than an AST node.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* isIdentifierName("foo"); // true
|
|
29
|
+
* isIdentifierName("$_0"); // true
|
|
30
|
+
* isIdentifierName("π"); // true
|
|
31
|
+
* isIdentifierName("class"); // true - syntactically an IdentifierName
|
|
32
|
+
* isIdentifierName("1foo"); // false - starts with a digit
|
|
33
|
+
* isIdentifierName("foo-bar"); // false - `-` is not an identifier character
|
|
34
|
+
* isIdentifierName(""); // false
|
|
35
|
+
*/
|
|
36
|
+
declare function isIdentifierName(name: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Returns `true` if `word` is a reserved keyword of the core grammar (`if`,
|
|
39
|
+
* `class`, `typeof`, `return`, …). Does not include `enum`, `await`, or the
|
|
40
|
+
* strict-mode-only words; see {@link isReservedWord} and
|
|
41
|
+
* {@link isStrictReservedWord} for those.
|
|
42
|
+
*/
|
|
43
|
+
declare function isKeyword(word: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Returns `true` if `word` is unconditionally reserved: `enum` in any context,
|
|
46
|
+
* and `await` when `inModule` (module / async contexts).
|
|
47
|
+
*/
|
|
48
|
+
declare function isReservedWord(word: string, inModule?: boolean): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Returns `true` if `word` is reserved in strict mode: everything
|
|
51
|
+
* {@link isReservedWord} covers, plus `let`, `static`, `yield`, `implements`,
|
|
52
|
+
* and friends.
|
|
53
|
+
*/
|
|
54
|
+
declare function isStrictReservedWord(word: string, inModule?: boolean): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Returns `true` for `eval` and `arguments`, which are reserved only as
|
|
57
|
+
* assignment / binding targets in strict mode.
|
|
58
|
+
*/
|
|
59
|
+
declare function isStrictBindOnlyReservedWord(word: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Returns `true` if `word` is reserved as a strict-mode binding target:
|
|
62
|
+
* everything {@link isStrictReservedWord} covers, plus `eval` / `arguments`.
|
|
63
|
+
*/
|
|
64
|
+
declare function isStrictBindReservedWord(word: string, inModule?: boolean): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Returns `true` if `name` can be used as an identifier **binding**: a valid
|
|
67
|
+
* {@link isIdentifierName} that, when `reserved` is `true` (the default), is
|
|
68
|
+
* neither a keyword nor a strict-mode reserved word.
|
|
69
|
+
*
|
|
70
|
+
* This is the check to reach for when turning an arbitrary string (e.g. a
|
|
71
|
+
* module specifier) into a local binding name.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* isValidIdentifier("foo"); // true
|
|
75
|
+
* isValidIdentifier("class"); // false - reserved word
|
|
76
|
+
* isValidIdentifier("await"); // false - reserved in modules
|
|
77
|
+
* isValidIdentifier("eval"); // true - valid identifier (strict-bind only)
|
|
78
|
+
* isValidIdentifier("class", false); // true - reserved words allowed
|
|
79
|
+
*/
|
|
80
|
+
declare function isValidIdentifier(name: string, reserved?: boolean): boolean;
|
|
81
|
+
//#endregion
|
|
82
|
+
export { isIdentifierChar, isIdentifierName, isIdentifierStart, isKeyword, isReservedWord, isStrictBindOnlyReservedWord, isStrictBindReservedWord, isStrictReservedWord, isValidIdentifier };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
//#region src/identifier/index.ts
|
|
2
|
+
const ID_START = /^[$_\p{ID_Start}]$/u;
|
|
3
|
+
const ID_CONTINUE = /^[$\u200C\u200D\p{ID_Continue}]$/u;
|
|
4
|
+
const IDENTIFIER_NAME = /^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u;
|
|
5
|
+
const MAX_CODE_POINT = 1114111;
|
|
6
|
+
/**
|
|
7
|
+
* Returns `true` if the Unicode code point `cp` can **start** an identifier:
|
|
8
|
+
* any character with the Unicode `ID_Start` property, plus `$` and `_`.
|
|
9
|
+
*
|
|
10
|
+
* Takes a numeric code point (as from {@link String.prototype.codePointAt}),
|
|
11
|
+
* mirroring Babel's `isIdentifierStart`. Returns `false` for values that are
|
|
12
|
+
* not a valid code point.
|
|
13
|
+
*/
|
|
14
|
+
function isIdentifierStart(cp) {
|
|
15
|
+
if (!Number.isInteger(cp) || cp < 0 || cp > MAX_CODE_POINT) return false;
|
|
16
|
+
return ID_START.test(String.fromCodePoint(cp));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns `true` if the Unicode code point `cp` can appear **after** the first
|
|
20
|
+
* character of an identifier: any character with the Unicode `ID_Continue`
|
|
21
|
+
* property, plus `$`, `_`, and the ZWNJ (U+200C) / ZWJ (U+200D) joiners.
|
|
22
|
+
*
|
|
23
|
+
* Takes a numeric code point, mirroring Babel's `isIdentifierChar`. Returns
|
|
24
|
+
* `false` for values that are not a valid code point.
|
|
25
|
+
*/
|
|
26
|
+
function isIdentifierChar(cp) {
|
|
27
|
+
if (!Number.isInteger(cp) || cp < 0 || cp > MAX_CODE_POINT) return false;
|
|
28
|
+
return ID_CONTINUE.test(String.fromCodePoint(cp));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns `true` if `name` is a valid ECMAScript `IdentifierName`: the grammar
|
|
32
|
+
* an identifier token must satisfy.
|
|
33
|
+
*
|
|
34
|
+
* Unlike the `is.IdentifierName` node guard, this validates a raw string rather
|
|
35
|
+
* than an AST node.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* isIdentifierName("foo"); // true
|
|
39
|
+
* isIdentifierName("$_0"); // true
|
|
40
|
+
* isIdentifierName("π"); // true
|
|
41
|
+
* isIdentifierName("class"); // true - syntactically an IdentifierName
|
|
42
|
+
* isIdentifierName("1foo"); // false - starts with a digit
|
|
43
|
+
* isIdentifierName("foo-bar"); // false - `-` is not an identifier character
|
|
44
|
+
* isIdentifierName(""); // false
|
|
45
|
+
*/
|
|
46
|
+
function isIdentifierName(name) {
|
|
47
|
+
return IDENTIFIER_NAME.test(name);
|
|
48
|
+
}
|
|
49
|
+
const keywords = new Set([
|
|
50
|
+
"break",
|
|
51
|
+
"case",
|
|
52
|
+
"catch",
|
|
53
|
+
"continue",
|
|
54
|
+
"debugger",
|
|
55
|
+
"default",
|
|
56
|
+
"do",
|
|
57
|
+
"else",
|
|
58
|
+
"finally",
|
|
59
|
+
"for",
|
|
60
|
+
"function",
|
|
61
|
+
"if",
|
|
62
|
+
"return",
|
|
63
|
+
"switch",
|
|
64
|
+
"throw",
|
|
65
|
+
"try",
|
|
66
|
+
"var",
|
|
67
|
+
"const",
|
|
68
|
+
"while",
|
|
69
|
+
"with",
|
|
70
|
+
"new",
|
|
71
|
+
"this",
|
|
72
|
+
"super",
|
|
73
|
+
"class",
|
|
74
|
+
"extends",
|
|
75
|
+
"export",
|
|
76
|
+
"import",
|
|
77
|
+
"null",
|
|
78
|
+
"true",
|
|
79
|
+
"false",
|
|
80
|
+
"in",
|
|
81
|
+
"instanceof",
|
|
82
|
+
"typeof",
|
|
83
|
+
"void",
|
|
84
|
+
"delete"
|
|
85
|
+
]);
|
|
86
|
+
const strictReservedWords = new Set([
|
|
87
|
+
"implements",
|
|
88
|
+
"interface",
|
|
89
|
+
"let",
|
|
90
|
+
"package",
|
|
91
|
+
"private",
|
|
92
|
+
"protected",
|
|
93
|
+
"public",
|
|
94
|
+
"static",
|
|
95
|
+
"yield"
|
|
96
|
+
]);
|
|
97
|
+
const strictBindOnlyReservedWords = new Set(["eval", "arguments"]);
|
|
98
|
+
/**
|
|
99
|
+
* Returns `true` if `word` is a reserved keyword of the core grammar (`if`,
|
|
100
|
+
* `class`, `typeof`, `return`, …). Does not include `enum`, `await`, or the
|
|
101
|
+
* strict-mode-only words; see {@link isReservedWord} and
|
|
102
|
+
* {@link isStrictReservedWord} for those.
|
|
103
|
+
*/
|
|
104
|
+
function isKeyword(word) {
|
|
105
|
+
return keywords.has(word);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns `true` if `word` is unconditionally reserved: `enum` in any context,
|
|
109
|
+
* and `await` when `inModule` (module / async contexts).
|
|
110
|
+
*/
|
|
111
|
+
function isReservedWord(word, inModule = false) {
|
|
112
|
+
return inModule && word === "await" || word === "enum";
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Returns `true` if `word` is reserved in strict mode: everything
|
|
116
|
+
* {@link isReservedWord} covers, plus `let`, `static`, `yield`, `implements`,
|
|
117
|
+
* and friends.
|
|
118
|
+
*/
|
|
119
|
+
function isStrictReservedWord(word, inModule = false) {
|
|
120
|
+
return isReservedWord(word, inModule) || strictReservedWords.has(word);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Returns `true` for `eval` and `arguments`, which are reserved only as
|
|
124
|
+
* assignment / binding targets in strict mode.
|
|
125
|
+
*/
|
|
126
|
+
function isStrictBindOnlyReservedWord(word) {
|
|
127
|
+
return strictBindOnlyReservedWords.has(word);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Returns `true` if `word` is reserved as a strict-mode binding target:
|
|
131
|
+
* everything {@link isStrictReservedWord} covers, plus `eval` / `arguments`.
|
|
132
|
+
*/
|
|
133
|
+
function isStrictBindReservedWord(word, inModule = false) {
|
|
134
|
+
return isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Returns `true` if `name` can be used as an identifier **binding**: a valid
|
|
138
|
+
* {@link isIdentifierName} that, when `reserved` is `true` (the default), is
|
|
139
|
+
* neither a keyword nor a strict-mode reserved word.
|
|
140
|
+
*
|
|
141
|
+
* This is the check to reach for when turning an arbitrary string (e.g. a
|
|
142
|
+
* module specifier) into a local binding name.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* isValidIdentifier("foo"); // true
|
|
146
|
+
* isValidIdentifier("class"); // false - reserved word
|
|
147
|
+
* isValidIdentifier("await"); // false - reserved in modules
|
|
148
|
+
* isValidIdentifier("eval"); // true - valid identifier (strict-bind only)
|
|
149
|
+
* isValidIdentifier("class", false); // true - reserved words allowed
|
|
150
|
+
*/
|
|
151
|
+
function isValidIdentifier(name, reserved = true) {
|
|
152
|
+
if (reserved && (isKeyword(name) || isStrictReservedWord(name, true))) return false;
|
|
153
|
+
return isIdentifierName(name);
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { isIdentifierChar, isIdentifierName, isIdentifierStart, isKeyword, isReservedWord, isStrictBindOnlyReservedWord, isStrictBindReservedWord, isStrictReservedWord, isValidIdentifier };
|