@jbrowse/jexl 2.3.1

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/LICENSE.txt +19 -0
  3. package/README.md +216 -0
  4. package/dist/Expression.d.ts +38 -0
  5. package/dist/Expression.js +75 -0
  6. package/dist/Expression.js.map +1 -0
  7. package/dist/Jexl.d.ts +148 -0
  8. package/dist/Jexl.js +208 -0
  9. package/dist/Jexl.js.map +1 -0
  10. package/dist/Lexer.d.ts +130 -0
  11. package/dist/Lexer.js +321 -0
  12. package/dist/Lexer.js.map +1 -0
  13. package/dist/PromiseSync.d.ts +13 -0
  14. package/dist/PromiseSync.js +80 -0
  15. package/dist/PromiseSync.js.map +1 -0
  16. package/dist/evaluator/Evaluator.d.ts +92 -0
  17. package/dist/evaluator/Evaluator.js +153 -0
  18. package/dist/evaluator/Evaluator.js.map +1 -0
  19. package/dist/evaluator/handlers.d.ts +111 -0
  20. package/dist/evaluator/handlers.js +216 -0
  21. package/dist/evaluator/handlers.js.map +1 -0
  22. package/dist/grammar.d.ts +25 -0
  23. package/dist/grammar.js +179 -0
  24. package/dist/grammar.js.map +1 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +20 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/package.json +1 -0
  29. package/dist/parser/Parser.d.ts +112 -0
  30. package/dist/parser/Parser.js +233 -0
  31. package/dist/parser/Parser.js.map +1 -0
  32. package/dist/parser/handlers.d.ts +112 -0
  33. package/dist/parser/handlers.js +302 -0
  34. package/dist/parser/handlers.js.map +1 -0
  35. package/dist/parser/states.d.ts +47 -0
  36. package/dist/parser/states.js +195 -0
  37. package/dist/parser/states.js.map +1 -0
  38. package/dist/types.d.ts +77 -0
  39. package/dist/types.js +7 -0
  40. package/dist/types.js.map +1 -0
  41. package/esm/Expression.d.ts +38 -0
  42. package/esm/Expression.js +70 -0
  43. package/esm/Expression.js.map +1 -0
  44. package/esm/Jexl.d.ts +148 -0
  45. package/esm/Jexl.js +202 -0
  46. package/esm/Jexl.js.map +1 -0
  47. package/esm/Lexer.d.ts +130 -0
  48. package/esm/Lexer.js +319 -0
  49. package/esm/Lexer.js.map +1 -0
  50. package/esm/PromiseSync.d.ts +13 -0
  51. package/esm/PromiseSync.js +78 -0
  52. package/esm/PromiseSync.js.map +1 -0
  53. package/esm/evaluator/Evaluator.d.ts +92 -0
  54. package/esm/evaluator/Evaluator.js +118 -0
  55. package/esm/evaluator/Evaluator.js.map +1 -0
  56. package/esm/evaluator/handlers.d.ts +111 -0
  57. package/esm/evaluator/handlers.js +202 -0
  58. package/esm/evaluator/handlers.js.map +1 -0
  59. package/esm/grammar.d.ts +25 -0
  60. package/esm/grammar.js +175 -0
  61. package/esm/grammar.js.map +1 -0
  62. package/esm/index.d.ts +5 -0
  63. package/esm/index.js +9 -0
  64. package/esm/index.js.map +1 -0
  65. package/esm/parser/Parser.d.ts +112 -0
  66. package/esm/parser/Parser.js +198 -0
  67. package/esm/parser/Parser.js.map +1 -0
  68. package/esm/parser/handlers.d.ts +112 -0
  69. package/esm/parser/handlers.js +280 -0
  70. package/esm/parser/handlers.js.map +1 -0
  71. package/esm/parser/states.d.ts +47 -0
  72. package/esm/parser/states.js +159 -0
  73. package/esm/parser/states.js.map +1 -0
  74. package/esm/types.d.ts +77 -0
  75. package/esm/types.js +6 -0
  76. package/esm/types.js.map +1 -0
  77. package/package.json +67 -0
  78. package/src/Expression.ts +95 -0
  79. package/src/Jexl.ts +232 -0
  80. package/src/Lexer.ts +344 -0
  81. package/src/PromiseSync.ts +86 -0
  82. package/src/evaluator/Evaluator.ts +153 -0
  83. package/src/evaluator/handlers.ts +226 -0
  84. package/src/grammar.ts +200 -0
  85. package/src/index.ts +10 -0
  86. package/src/parser/Parser.ts +235 -0
  87. package/src/parser/handlers.ts +309 -0
  88. package/src/parser/states.ts +177 -0
  89. package/src/types.ts +107 -0
@@ -0,0 +1,309 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+
6
+ import type { AstNode, Token } from '../types.ts'
7
+ import type Parser from './Parser.ts'
8
+
9
+ /**
10
+ * Handles a subexpression that's used to define a transform argument's value.
11
+ * @param {{type: <string>}} ast The subexpression tree
12
+ */
13
+ export function argVal(this: Parser, ast: AstNode | null) {
14
+ if (ast) {
15
+ ;(this._cursor as any).args.push(ast)
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Handles new array literals by adding them as a new node in the AST,
21
+ * initialized with an empty array.
22
+ */
23
+ export function arrayStart(this: Parser) {
24
+ this._placeAtCursor({
25
+ type: 'ArrayLiteral',
26
+ value: []
27
+ } as any)
28
+ }
29
+
30
+ /**
31
+ * Handles a subexpression representing an element of an array literal.
32
+ * @param {{type: <string>}} ast The subexpression tree
33
+ */
34
+ export function arrayVal(this: Parser, ast: AstNode | null) {
35
+ if (ast) {
36
+ ;(this._cursor as any).value.push(ast)
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Handles tokens of type 'binaryOp', indicating an operation that has two
42
+ * inputs: a left side and a right side.
43
+ * @param {{type: <string>}} token A token object
44
+ */
45
+ export function binaryOp(this: Parser, token: Token) {
46
+ if (token.value === '=') {
47
+ if (this._cursor?.type !== 'Identifier') {
48
+ throw new Error('Left side of assignment must be a variable name')
49
+ }
50
+
51
+ const node: any = {
52
+ type: 'AssignmentExpression',
53
+ operator: '=',
54
+ left: this._cursor
55
+ }
56
+ this._cursor = this._cursor._parent
57
+ this._placeAtCursor(node)
58
+ return
59
+ }
60
+
61
+ const precedence = this._grammar.elements[token.value].precedence || 0
62
+ let parent = this._cursor?._parent
63
+ while (
64
+ parent &&
65
+ (parent as any).operator &&
66
+ this._grammar.elements[(parent as any).operator].precedence >= precedence
67
+ ) {
68
+ this._cursor = parent
69
+ parent = parent._parent
70
+ }
71
+ const node: any = {
72
+ type: 'BinaryExpression',
73
+ operator: token.value,
74
+ left: this._cursor
75
+ }
76
+ this._setParent(this._cursor!, node)
77
+ this._cursor = parent
78
+ this._placeAtCursor(node)
79
+ }
80
+
81
+ /**
82
+ * Handles successive nodes in an identifier chain. More specifically, it
83
+ * sets values that determine how the following identifier gets placed in the
84
+ * AST.
85
+ */
86
+ export function dot(this: Parser) {
87
+ const isBinaryExprWithRight = (node: AstNode) =>
88
+ node.type === 'BinaryExpression' && (node as any).right
89
+
90
+ this._nextIdentEncapsulate =
91
+ this._cursor &&
92
+ this._cursor.type !== 'UnaryExpression' &&
93
+ (this._cursor.type !== 'BinaryExpression' ||
94
+ isBinaryExprWithRight(this._cursor))
95
+
96
+ this._nextIdentRelative = !this._cursor || !this._nextIdentEncapsulate
97
+ if (this._nextIdentRelative) {
98
+ this._relative = true
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Handles a subexpression used for filtering an array returned by an
104
+ * identifier chain.
105
+ * @param {{type: <string>}} ast The subexpression tree
106
+ */
107
+ export function filter(this: Parser, ast: AstNode | null) {
108
+ this._placeBeforeCursor({
109
+ type: 'FilterExpression',
110
+ expr: ast,
111
+ relative: this._subParser!.isRelative(),
112
+ subject: this._cursor
113
+ } as any)
114
+ }
115
+
116
+ /**
117
+ * Handles identifier tokens when used to indicate the name of a function to
118
+ * be called.
119
+ * @param {{type: <string>}} token A token object
120
+ */
121
+ export function functionCall(this: Parser) {
122
+ this._placeBeforeCursor({
123
+ type: 'FunctionCall',
124
+ name: (this._cursor as any).value,
125
+ args: [],
126
+ pool: 'functions'
127
+ } as any)
128
+ }
129
+
130
+ /**
131
+ * Handles identifier tokens by adding them as a new node in the AST.
132
+ * @param {{type: <string>}} token A token object
133
+ */
134
+ export function identifier(this: Parser, token: Token) {
135
+ const node: any = {
136
+ type: 'Identifier',
137
+ value: token.value
138
+ }
139
+ if (this._nextIdentEncapsulate) {
140
+ node.from = this._cursor
141
+ this._placeBeforeCursor(node)
142
+ this._nextIdentEncapsulate = false
143
+ } else {
144
+ if (this._nextIdentRelative) {
145
+ node.relative = true
146
+ this._nextIdentRelative = false
147
+ }
148
+ this._placeAtCursor(node)
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Handles literal values, such as strings, booleans, and numerics, by adding
154
+ * them as a new node in the AST.
155
+ * @param {{type: <string>}} token A token object
156
+ */
157
+ export function literal(this: Parser, token: Token) {
158
+ this._placeAtCursor({
159
+ type: 'Literal',
160
+ value: token.value
161
+ } as any)
162
+ }
163
+
164
+ /**
165
+ * Handles template string tokens by parsing their interpolation expressions
166
+ * and creating a TemplateLiteral AST node.
167
+ * @param {{type: <string>}} token A token object
168
+ */
169
+ export function templateString(this: Parser, token: Token) {
170
+ const parts: { type: 'static' | 'expression'; value: string | AstNode }[] = []
171
+
172
+ for (const part of token.value) {
173
+ if (part.type === 'static') {
174
+ parts.push({
175
+ type: 'static',
176
+ value: this._lexer._unescapeTemplateString(part.value)
177
+ })
178
+ } else {
179
+ const subTokens = this._lexer.tokenize(part.value)
180
+ const subParser = new (this.constructor as any)(
181
+ this._grammar,
182
+ this._lexer
183
+ )
184
+ subParser.addTokens(subTokens)
185
+ const subAst = subParser.complete()
186
+
187
+ if (!subAst) {
188
+ throw new Error('Empty interpolation in template string')
189
+ }
190
+
191
+ parts.push({
192
+ type: 'expression',
193
+ value: subAst
194
+ })
195
+ }
196
+ }
197
+
198
+ this._placeAtCursor({
199
+ type: 'TemplateLiteral',
200
+ parts: parts
201
+ } as any)
202
+ }
203
+
204
+ /**
205
+ * Queues a new object literal key to be written once a value is collected.
206
+ * @param {{type: <string>}} token A token object
207
+ */
208
+ export function objKey(this: Parser, token: Token) {
209
+ this._curObjKey = token.value
210
+ }
211
+
212
+ /**
213
+ * Handles new object literals by adding them as a new node in the AST,
214
+ * initialized with an empty object.
215
+ */
216
+ export function objStart(this: Parser) {
217
+ this._placeAtCursor({
218
+ type: 'ObjectLiteral',
219
+ value: {}
220
+ } as any)
221
+ }
222
+
223
+ /**
224
+ * Handles an object value by adding its AST to the queued key on the object
225
+ * literal node currently at the cursor.
226
+ * @param {{type: <string>}} ast The subexpression tree
227
+ */
228
+ export function objVal(this: Parser, ast: AstNode | null) {
229
+ ;(this._cursor as any).value[this._curObjKey!] = ast
230
+ }
231
+
232
+ /**
233
+ * Handles traditional subexpressions, delineated with the groupStart and
234
+ * groupEnd elements.
235
+ * @param {{type: <string>}} ast The subexpression tree
236
+ */
237
+ export function subExpression(this: Parser, ast: AstNode | null) {
238
+ this._placeAtCursor(ast!)
239
+ }
240
+
241
+ /**
242
+ * Handles a completed alternate subexpression of a ternary operator.
243
+ * @param {{type: <string>}} ast The subexpression tree
244
+ */
245
+ export function ternaryEnd(this: Parser, ast: AstNode | null) {
246
+ ;(this._cursor as any).alternate = ast
247
+ }
248
+
249
+ /**
250
+ * Handles a completed consequent subexpression of a ternary operator.
251
+ * @param {{type: <string>}} ast The subexpression tree
252
+ */
253
+ export function ternaryMid(this: Parser, ast: AstNode | null) {
254
+ ;(this._cursor as any).consequent = ast
255
+ }
256
+
257
+ /**
258
+ * Handles the start of a new ternary expression by encapsulating the entire
259
+ * AST in a ConditionalExpression node, and using the existing tree as the
260
+ * test element.
261
+ */
262
+ export function ternaryStart(this: Parser) {
263
+ this._tree = {
264
+ type: 'ConditionalExpression',
265
+ test: this._tree
266
+ } as any
267
+ this._cursor = this._tree
268
+ }
269
+
270
+ /**
271
+ * Handles identifier tokens when used to indicate the name of a transform to
272
+ * be applied.
273
+ * @param {{type: <string>}} token A token object
274
+ */
275
+ export function transform(this: Parser, token: Token) {
276
+ this._placeBeforeCursor({
277
+ type: 'FunctionCall',
278
+ name: token.value,
279
+ args: [this._cursor],
280
+ pool: 'transforms'
281
+ } as any)
282
+ }
283
+
284
+ /**
285
+ * Handles token of type 'unaryOp', indicating that the operation has only
286
+ * one input: a right side.
287
+ * @param {{type: <string>}} token A token object
288
+ */
289
+ export function unaryOp(this: Parser, token: Token) {
290
+ this._placeAtCursor({
291
+ type: 'UnaryExpression',
292
+ operator: token.value
293
+ } as any)
294
+ }
295
+
296
+ /**
297
+ * Handles semicolon separator between expressions
298
+ */
299
+ export function semicolon(this: Parser) {
300
+ if (!this._sequenceExpressions) {
301
+ this._sequenceExpressions = [this._tree!]
302
+ } else {
303
+ this._sequenceExpressions.push(this._tree!)
304
+ }
305
+
306
+ this._tree = null
307
+ this._cursor = null
308
+ this._state = 'expectOperand'
309
+ }
@@ -0,0 +1,177 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+
6
+ import * as h from './handlers.ts'
7
+
8
+ import type { AstNode } from '../types.ts'
9
+ import type Parser from './Parser.ts'
10
+
11
+ interface TokenTypeOptions {
12
+ toState?: string
13
+ handler?: (this: Parser, ...args: any[]) => void
14
+ }
15
+
16
+ interface StateDefinition {
17
+ tokenTypes?: Record<string, TokenTypeOptions>
18
+ subHandler?: (this: Parser, ast: AstNode | null) => void
19
+ endStates?: Record<string, string>
20
+ completable?: boolean
21
+ }
22
+
23
+ type States = Record<string, StateDefinition>
24
+
25
+ /**
26
+ * A mapping of all states in the finite state machine to a set of instructions
27
+ * for handling or transitioning into other states. Each state can be handled
28
+ * in one of two schemes: a tokenType map, or a subHandler.
29
+ *
30
+ * Standard expression elements are handled through the tokenType object. This
31
+ * is an object map of all legal token types to encounter in this state (and
32
+ * any unexpected token types will generate a thrown error) to an options
33
+ * object that defines how they're handled. The available options are:
34
+ *
35
+ * {string} toState: The name of the state to which to transition
36
+ * immediately after handling this token
37
+ * {string} handler: The handler function to call when this token type is
38
+ * encountered in this state. If omitted, the default handler
39
+ * matching the token's "type" property will be called. If the handler
40
+ * function does not exist, no call will be made and no error will be
41
+ * generated. This is useful for tokens whose sole purpose is to
42
+ * transition to other states.
43
+ *
44
+ * States that consume a subexpression should define a subHandler, the
45
+ * function to be called with an expression tree argument when the
46
+ * subexpression is complete. Completeness is determined through the
47
+ * endStates object, which maps tokens on which an expression should end to the
48
+ * state to which to transition once the subHandler function has been called.
49
+ *
50
+ * Additionally, any state in which it is legal to mark the AST as completed
51
+ * should have a 'completable' property set to boolean true. Attempting to
52
+ * call {@link Parser#complete} in any state without this property will result
53
+ * in a thrown Error.
54
+ *
55
+ * @type {{}}
56
+ */
57
+ export const states: States = {
58
+ expectOperand: {
59
+ tokenTypes: {
60
+ literal: { toState: 'expectBinOp' },
61
+ templateString: { toState: 'expectBinOp' },
62
+ identifier: { toState: 'identifier' },
63
+ unaryOp: {},
64
+ openParen: { toState: 'subExpression' },
65
+ openCurl: { toState: 'expectObjKey', handler: h.objStart },
66
+ dot: { toState: 'traverse' },
67
+ openBracket: { toState: 'arrayVal', handler: h.arrayStart }
68
+ }
69
+ },
70
+ expectBinOp: {
71
+ tokenTypes: {
72
+ binaryOp: { toState: 'expectOperand' },
73
+ pipe: { toState: 'expectTransform' },
74
+ dot: { toState: 'traverse' },
75
+ question: { toState: 'ternaryMid', handler: h.ternaryStart },
76
+ semicolon: { handler: h.semicolon }
77
+ },
78
+ completable: true
79
+ },
80
+ expectTransform: {
81
+ tokenTypes: {
82
+ identifier: { toState: 'postTransform', handler: h.transform }
83
+ }
84
+ },
85
+ expectObjKey: {
86
+ tokenTypes: {
87
+ literal: { toState: 'expectKeyValSep', handler: h.objKey },
88
+ identifier: { toState: 'expectKeyValSep', handler: h.objKey },
89
+ closeCurl: { toState: 'expectBinOp' }
90
+ }
91
+ },
92
+ expectKeyValSep: {
93
+ tokenTypes: {
94
+ colon: { toState: 'objVal' }
95
+ }
96
+ },
97
+ postTransform: {
98
+ tokenTypes: {
99
+ openParen: { toState: 'argVal' },
100
+ binaryOp: { toState: 'expectOperand' },
101
+ dot: { toState: 'traverse' },
102
+ openBracket: { toState: 'filter' },
103
+ pipe: { toState: 'expectTransform' },
104
+ question: { toState: 'ternaryMid', handler: h.ternaryStart }
105
+ },
106
+ completable: true
107
+ },
108
+ postArgs: {
109
+ tokenTypes: {
110
+ binaryOp: { toState: 'expectOperand' },
111
+ dot: { toState: 'traverse' },
112
+ openBracket: { toState: 'filter' },
113
+ pipe: { toState: 'expectTransform' },
114
+ question: { toState: 'ternaryMid', handler: h.ternaryStart }
115
+ },
116
+ completable: true
117
+ },
118
+ identifier: {
119
+ tokenTypes: {
120
+ binaryOp: { toState: 'expectOperand' },
121
+ dot: { toState: 'traverse' },
122
+ openBracket: { toState: 'filter' },
123
+ openParen: { toState: 'argVal', handler: h.functionCall },
124
+ pipe: { toState: 'expectTransform' },
125
+ question: { toState: 'ternaryMid', handler: h.ternaryStart }
126
+ },
127
+ completable: true
128
+ },
129
+ traverse: {
130
+ tokenTypes: {
131
+ identifier: { toState: 'identifier' }
132
+ }
133
+ },
134
+ filter: {
135
+ subHandler: h.filter,
136
+ endStates: {
137
+ closeBracket: 'identifier'
138
+ }
139
+ },
140
+ subExpression: {
141
+ subHandler: h.subExpression,
142
+ endStates: {
143
+ closeParen: 'expectBinOp'
144
+ }
145
+ },
146
+ argVal: {
147
+ subHandler: h.argVal,
148
+ endStates: {
149
+ comma: 'argVal',
150
+ closeParen: 'postArgs'
151
+ }
152
+ },
153
+ objVal: {
154
+ subHandler: h.objVal,
155
+ endStates: {
156
+ comma: 'expectObjKey',
157
+ closeCurl: 'expectBinOp'
158
+ }
159
+ },
160
+ arrayVal: {
161
+ subHandler: h.arrayVal,
162
+ endStates: {
163
+ comma: 'arrayVal',
164
+ closeBracket: 'expectBinOp'
165
+ }
166
+ },
167
+ ternaryMid: {
168
+ subHandler: h.ternaryMid,
169
+ endStates: {
170
+ colon: 'ternaryEnd'
171
+ }
172
+ },
173
+ ternaryEnd: {
174
+ subHandler: h.ternaryEnd,
175
+ completable: true
176
+ }
177
+ }
package/src/types.ts ADDED
@@ -0,0 +1,107 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+
6
+ export interface Token {
7
+ type: string
8
+ value: any
9
+ raw: string
10
+ }
11
+
12
+ export interface AstNode {
13
+ type: string
14
+ _parent?: AstNode
15
+ }
16
+
17
+ export interface Literal extends AstNode {
18
+ type: 'Literal'
19
+ value: string | number | boolean
20
+ }
21
+
22
+ export interface Identifier extends AstNode {
23
+ type: 'Identifier'
24
+ value: string
25
+ from?: AstNode
26
+ relative?: boolean
27
+ }
28
+
29
+ export interface BinaryExpression extends AstNode {
30
+ type: 'BinaryExpression'
31
+ operator: string
32
+ left: AstNode
33
+ right?: AstNode
34
+ }
35
+
36
+ export interface UnaryExpression extends AstNode {
37
+ type: 'UnaryExpression'
38
+ operator: string
39
+ right?: AstNode
40
+ }
41
+
42
+ export interface ArrayLiteral extends AstNode {
43
+ type: 'ArrayLiteral'
44
+ value: AstNode[]
45
+ }
46
+
47
+ export interface ObjectLiteral extends AstNode {
48
+ type: 'ObjectLiteral'
49
+ value: Record<string, AstNode>
50
+ }
51
+
52
+ export interface TemplateLiteral extends AstNode {
53
+ type: 'TemplateLiteral'
54
+ parts: {
55
+ type: 'static' | 'expression'
56
+ value: string | AstNode
57
+ }[]
58
+ }
59
+
60
+ export interface FunctionCall extends AstNode {
61
+ type: 'FunctionCall'
62
+ name: string
63
+ args: AstNode[]
64
+ pool: 'functions' | 'transforms'
65
+ }
66
+
67
+ export interface FilterExpression extends AstNode {
68
+ type: 'FilterExpression'
69
+ expr: AstNode
70
+ relative: boolean
71
+ subject: AstNode
72
+ }
73
+
74
+ export interface ConditionalExpression extends AstNode {
75
+ type: 'ConditionalExpression'
76
+ test: AstNode
77
+ consequent?: AstNode
78
+ alternate?: AstNode
79
+ }
80
+
81
+ export interface SequenceExpression extends AstNode {
82
+ type: 'SequenceExpression'
83
+ expressions: AstNode[]
84
+ }
85
+
86
+ export interface AssignmentExpression extends AstNode {
87
+ type: 'AssignmentExpression'
88
+ operator: '='
89
+ left: Identifier
90
+ right: AstNode
91
+ }
92
+
93
+ export type AstNodeUnion =
94
+ | Literal
95
+ | Identifier
96
+ | BinaryExpression
97
+ | UnaryExpression
98
+ | ArrayLiteral
99
+ | ObjectLiteral
100
+ | TemplateLiteral
101
+ | FunctionCall
102
+ | FilterExpression
103
+ | ConditionalExpression
104
+ | SequenceExpression
105
+ | AssignmentExpression
106
+
107
+ export type NodeByType<T extends AstNodeUnion['type']> = Extract<AstNodeUnion, { type: T }>