@di-framework/tsc 4.3.3 → 5.0.0
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 +28 -8
- package/package.json +1 -1
- package/plugin/main.go +1028 -61
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[`ttsc`](https://ttsc.dev) transform plugin that injects **runtime parameter checks** from your TypeScript types.
|
|
4
4
|
|
|
5
|
-
Source stays plain TypeScript — no `assert()`, schemas, or decorators. On emit, function bodies get `typeof
|
|
5
|
+
Source stays plain TypeScript — no `assert()`, schemas, or decorators. On emit, function bodies get `typeof`, equality, and shape guards synthesized from parameter types.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Function declarations and expressions, arrows (including concise and async arrows), methods, and constructors are supported.
|
|
8
8
|
|
|
9
9
|
`di-framework init` wires this by default. Use the steps below for an existing app.
|
|
10
10
|
|
|
@@ -69,18 +69,38 @@ function greet(user) {
|
|
|
69
69
|
## How it works
|
|
70
70
|
|
|
71
71
|
1. `ttsc` loads the program (parse + typecheck).
|
|
72
|
-
2. This plugin walks
|
|
72
|
+
2. This plugin walks functions, arrows, methods, and constructors.
|
|
73
73
|
3. For each required parameter, it reads the type (syntax keywords, else checker).
|
|
74
74
|
4. It synthesizes `if` / `throw` AST nodes with typescript-go `NodeFactory` and prepends them to the body.
|
|
75
75
|
5. `EmitAllRaw` prints JavaScript from the mutated AST.
|
|
76
76
|
|
|
77
|
-
##
|
|
77
|
+
## Supported boundaries and types
|
|
78
|
+
|
|
79
|
+
The transformer handles function declarations/expressions, methods, constructors, and block- or expression-bodied arrows at any nesting level. Required, optional, default, rest, and simple destructured parameters are supported. Runtime predicates cover primitives, nullish and literal types, enums, accessible local class instances, sound bounded unions, plain structural objects, non-callable object-only intersections, erased primitive brands, template-literal strings, string index signatures, `Record<string, V>` and literal-key records, arrays/readonly arrays, and fixed tuples with optional tails.
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
## Limitations
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
83
|
+
Unsupported paths are skipped as a whole rather than emitting a predicate that can reject valid input. Current intentional gaps are:
|
|
84
|
+
|
|
85
|
+
- defaults and rest elements nested inside destructuring patterns
|
|
86
|
+
- variadic tuples, imported class bindings, callable/constructable intersections, typia-style tags, and custom nominal predicates
|
|
87
|
+
- number-only and symbol index signatures, and advanced mapped/conditional types
|
|
88
|
+
- unions with unsupported members or more than 12 members
|
|
89
|
+
- `void`, `never`, and `unique symbol`
|
|
90
|
+
|
|
91
|
+
Arrays and `ReadonlyArray<T>` are checked with `Array.isArray` and a full element scan.
|
|
92
|
+
Fixed tuples enforce their allowed length and validate each position; optional tails are presence-gated.
|
|
93
|
+
Object-only intersections use the checker's flattened apparent properties, validating each required property once. Required properties are checked for presence before their value, including properties whose type admits `undefined`; optional object properties remain unchecked.
|
|
94
|
+
String index signatures and `Record<string, V>` scan every own enumerable string-keyed value with `Object.keys`. Literal-key records instead check each required key, including names that require bracket access.
|
|
95
|
+
Primitive intersections used as brands validate only their string, number, boolean, bigint, or literal runtime representation. Recognized marker objects contain only computed unique-symbol properties, or conventional marker keys such as `__brand`, `_brand`, or `_tag` whose values are literals, unique symbols, or `never`. Brand marker properties are compile-time-only and are never read at runtime; class, callable, constructable, indexed, and ordinary structural intersections are not erased as brands. Enforcing application-specific brand semantics requires a future custom predicate facility.
|
|
96
|
+
Template-literal types always require a string. A template with one string-like placeholder also enforces its nonempty fixed prefix and suffix with `startsWith` and `endsWith`; multiple placeholders and non-string placeholders intentionally fall back to the string check.
|
|
97
|
+
Structural traversal keeps a per-path visited set and a maximum depth of eight, so recursive types cannot hang compilation.
|
|
98
|
+
Optional and defaulted parameters are validated only when their runtime value is not `undefined`; `null` is still checked against the declared type.
|
|
99
|
+
Rest parameters use the same full array and element validation as ordinary arrays.
|
|
100
|
+
Simple object, array, and nested destructured bindings are validated using the types of the bound identifiers.
|
|
101
|
+
|
|
102
|
+
Numeric, string, and const enums are checked against their checker-known member values. Enums with a computed member are skipped as a whole because the computed runtime value cannot be reproduced soundly without depending on an emitted enum object.
|
|
103
|
+
Class-typed parameters use `instanceof` when the checker can name an accessible local or local-namespace runtime constructor; interfaces remain structural. Subclasses pass the check, and generic type arguments are erased at runtime. As with any `instanceof` test, equivalent instances created in another JavaScript realm do not pass. ES-imported class bindings are conservatively skipped, including ordinary value imports, because TypeScript can elide an import used only as a type and the transformer does not synthesize or retain imports.
|
|
84
104
|
|
|
85
105
|
## Monorepo
|
|
86
106
|
|
package/package.json
CHANGED
package/plugin/main.go
CHANGED
|
@@ -9,7 +9,9 @@ import (
|
|
|
9
9
|
"fmt"
|
|
10
10
|
"os"
|
|
11
11
|
"path/filepath"
|
|
12
|
+
"strconv"
|
|
12
13
|
"strings"
|
|
14
|
+
"unicode"
|
|
13
15
|
|
|
14
16
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
15
17
|
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
@@ -183,8 +185,31 @@ func injectFile(factory *shimast.NodeFactory, file *shimast.SourceFile, checker
|
|
|
183
185
|
if node == nil {
|
|
184
186
|
return
|
|
185
187
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
switch node.Kind {
|
|
189
|
+
case shimast.KindFunctionDeclaration:
|
|
190
|
+
fn := node.AsFunctionDeclaration()
|
|
191
|
+
injectCallable(factory, checker, node, fn.Parameters, fn.Body)
|
|
192
|
+
case shimast.KindMethodDeclaration:
|
|
193
|
+
method := node.AsMethodDeclaration()
|
|
194
|
+
injectCallable(factory, checker, node, method.Parameters, method.Body)
|
|
195
|
+
case shimast.KindConstructor:
|
|
196
|
+
ctor := node.AsConstructorDeclaration()
|
|
197
|
+
injectCallable(factory, checker, node, ctor.Parameters, ctor.Body)
|
|
198
|
+
case shimast.KindFunctionExpression:
|
|
199
|
+
fn := node.AsFunctionExpression()
|
|
200
|
+
injectCallable(factory, checker, node, fn.Parameters, fn.Body)
|
|
201
|
+
case shimast.KindArrowFunction:
|
|
202
|
+
arrow := node.AsArrowFunction()
|
|
203
|
+
if arrow.Body != nil && arrow.Body.Kind == shimast.KindBlock {
|
|
204
|
+
injectCallable(factory, checker, node, arrow.Parameters, arrow.Body)
|
|
205
|
+
} else if arrow.Body != nil {
|
|
206
|
+
checks := checksForParams(factory, checker, node, arrow.Parameters)
|
|
207
|
+
if len(checks) > 0 {
|
|
208
|
+
stmts := append(checks, factory.NewReturnStatement(arrow.Body))
|
|
209
|
+
arrow.Body = factory.NewBlock(factory.NewNodeList(stmts), true)
|
|
210
|
+
shimast.SetParentInChildren(node)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
188
213
|
}
|
|
189
214
|
node.ForEachChild(func(child *shimast.Node) bool {
|
|
190
215
|
walk(child)
|
|
@@ -198,47 +223,116 @@ func injectFile(factory *shimast.NodeFactory, file *shimast.SourceFile, checker
|
|
|
198
223
|
}
|
|
199
224
|
}
|
|
200
225
|
|
|
201
|
-
func
|
|
202
|
-
|
|
203
|
-
|
|
226
|
+
func injectCallable(
|
|
227
|
+
factory *shimast.NodeFactory,
|
|
228
|
+
checker *shimchecker.Checker,
|
|
229
|
+
enclosing *shimast.Node,
|
|
230
|
+
params *shimast.NodeList,
|
|
231
|
+
body *shimast.Node,
|
|
232
|
+
) {
|
|
233
|
+
if body == nil || params == nil {
|
|
204
234
|
return
|
|
205
235
|
}
|
|
206
|
-
block :=
|
|
236
|
+
block := body.AsBlock()
|
|
207
237
|
if block == nil || block.Statements == nil {
|
|
208
238
|
return
|
|
209
239
|
}
|
|
210
240
|
|
|
241
|
+
checks := checksForParams(factory, checker, enclosing, params)
|
|
242
|
+
if len(checks) == 0 {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Structural prepend — printer walks Statements.Nodes directly.
|
|
247
|
+
out := make([]*shimast.Node, 0, len(checks)+len(block.Statements.Nodes))
|
|
248
|
+
out = append(out, checks...)
|
|
249
|
+
out = append(out, block.Statements.Nodes...)
|
|
250
|
+
block.Statements.Nodes = out
|
|
251
|
+
// Emit walks parents; factory nodes start detached.
|
|
252
|
+
shimast.SetParentInChildren(body)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func checksForParams(
|
|
256
|
+
factory *shimast.NodeFactory,
|
|
257
|
+
checker *shimchecker.Checker,
|
|
258
|
+
enclosing *shimast.Node,
|
|
259
|
+
params *shimast.NodeList,
|
|
260
|
+
) []*shimast.Node {
|
|
261
|
+
if params == nil {
|
|
262
|
+
return nil
|
|
263
|
+
}
|
|
211
264
|
var checks []*shimast.Node
|
|
212
|
-
for _, param := range
|
|
265
|
+
for _, param := range params.Nodes {
|
|
213
266
|
if param == nil || param.Kind != shimast.KindParameter {
|
|
214
267
|
continue
|
|
215
268
|
}
|
|
216
269
|
p := param.AsParameterDeclaration()
|
|
217
|
-
if p == nil || p.Name() == nil
|
|
270
|
+
if p == nil || p.Name() == nil {
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
if p.Name().Kind == shimast.KindObjectBindingPattern || p.Name().Kind == shimast.KindArrayBindingPattern {
|
|
274
|
+
checks = append(checks, checksForBindingName(factory, checker, enclosing, p.Name())...)
|
|
218
275
|
continue
|
|
219
276
|
}
|
|
220
|
-
if p.
|
|
277
|
+
if p.Name().Kind != shimast.KindIdentifier {
|
|
221
278
|
continue
|
|
222
279
|
}
|
|
223
280
|
name := p.Name().Text()
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
281
|
+
paramChecks := checksForParam(factory, checker, enclosing, param, name)
|
|
282
|
+
if len(paramChecks) == 0 {
|
|
283
|
+
continue
|
|
284
|
+
}
|
|
285
|
+
if p.QuestionToken != nil || p.Initializer != nil {
|
|
286
|
+
present := binary(factory, pathExpr(factory, name), shimast.KindExclamationEqualsEqualsToken, factory.NewIdentifier("undefined"))
|
|
287
|
+
checks = append(checks, factory.NewIfStatement(present, factory.NewBlock(factory.NewNodeList(paramChecks), true), nil))
|
|
288
|
+
continue
|
|
289
|
+
}
|
|
290
|
+
checks = append(checks, paramChecks...)
|
|
228
291
|
}
|
|
292
|
+
return checks
|
|
293
|
+
}
|
|
229
294
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
295
|
+
func checksForBindingName(
|
|
296
|
+
factory *shimast.NodeFactory,
|
|
297
|
+
checker *shimchecker.Checker,
|
|
298
|
+
enclosing *shimast.Node,
|
|
299
|
+
name *shimast.Node,
|
|
300
|
+
) []*shimast.Node {
|
|
301
|
+
if name == nil || checker == nil {
|
|
302
|
+
return nil
|
|
303
|
+
}
|
|
304
|
+
if name.Kind == shimast.KindIdentifier {
|
|
305
|
+
t := checker.GetTypeAtLocation(name)
|
|
306
|
+
if t == nil {
|
|
307
|
+
return nil
|
|
308
|
+
}
|
|
309
|
+
return stmtsFromCheckerType(factory, checker, enclosing, name.Text(), t)
|
|
310
|
+
}
|
|
311
|
+
if name.Kind != shimast.KindObjectBindingPattern && name.Kind != shimast.KindArrayBindingPattern {
|
|
312
|
+
return nil
|
|
313
|
+
}
|
|
314
|
+
pattern := name.AsBindingPattern()
|
|
315
|
+
if pattern == nil || pattern.Elements == nil {
|
|
316
|
+
return nil
|
|
317
|
+
}
|
|
318
|
+
var out []*shimast.Node
|
|
319
|
+
for _, raw := range pattern.Elements.Nodes {
|
|
320
|
+
if raw == nil || raw.Kind != shimast.KindBindingElement {
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
element := raw.AsBindingElement()
|
|
324
|
+
if element == nil || element.Name() == nil || element.DotDotDotToken != nil || element.Initializer != nil {
|
|
325
|
+
continue
|
|
326
|
+
}
|
|
327
|
+
out = append(out, checksForBindingName(factory, checker, enclosing, element.Name())...)
|
|
328
|
+
}
|
|
329
|
+
return out
|
|
237
330
|
}
|
|
238
331
|
|
|
239
332
|
func checksForParam(
|
|
240
333
|
factory *shimast.NodeFactory,
|
|
241
334
|
checker *shimchecker.Checker,
|
|
335
|
+
enclosing *shimast.Node,
|
|
242
336
|
param *shimast.Node,
|
|
243
337
|
path string,
|
|
244
338
|
) []*shimast.Node {
|
|
@@ -255,7 +349,7 @@ func checksForParam(
|
|
|
255
349
|
if t == nil {
|
|
256
350
|
return nil
|
|
257
351
|
}
|
|
258
|
-
return stmtsFromCheckerType(factory, checker, path, t)
|
|
352
|
+
return stmtsFromCheckerType(factory, checker, enclosing, path, t)
|
|
259
353
|
}
|
|
260
354
|
|
|
261
355
|
func stmtsFromTypeNode(
|
|
@@ -283,27 +377,143 @@ func stmtsFromTypeNode(
|
|
|
283
377
|
func stmtsFromCheckerType(
|
|
284
378
|
factory *shimast.NodeFactory,
|
|
285
379
|
checker *shimchecker.Checker,
|
|
380
|
+
enclosing *shimast.Node,
|
|
286
381
|
path string,
|
|
287
382
|
t *shimchecker.Type,
|
|
288
383
|
) []*shimast.Node {
|
|
289
|
-
return stmtsFromCheckerTypeSeen(factory, checker, path, t,
|
|
384
|
+
return stmtsFromCheckerTypeSeen(factory, checker, enclosing, path, t, newPredicateState(path), 0)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
type predicateState struct {
|
|
388
|
+
active map[*shimchecker.Type]bool
|
|
389
|
+
identifiers map[string]bool
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func newPredicateState(path string) *predicateState {
|
|
393
|
+
rootEnd := strings.IndexAny(path, ".[")
|
|
394
|
+
if rootEnd < 0 {
|
|
395
|
+
rootEnd = len(path)
|
|
396
|
+
}
|
|
397
|
+
return &predicateState{
|
|
398
|
+
active: make(map[*shimchecker.Type]bool),
|
|
399
|
+
identifiers: map[string]bool{path[:rootEnd]: true},
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
func (state *predicateState) clone() *predicateState {
|
|
404
|
+
clone := &predicateState{
|
|
405
|
+
active: make(map[*shimchecker.Type]bool, len(state.active)),
|
|
406
|
+
identifiers: make(map[string]bool, len(state.identifiers)),
|
|
407
|
+
}
|
|
408
|
+
for t, active := range state.active {
|
|
409
|
+
clone.active[t] = active
|
|
410
|
+
}
|
|
411
|
+
for name, used := range state.identifiers {
|
|
412
|
+
clone.identifiers[name] = used
|
|
413
|
+
}
|
|
414
|
+
return clone
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
func (state *predicateState) freshIdentifier(base string) string {
|
|
418
|
+
if !state.identifiers[base] {
|
|
419
|
+
state.identifiers[base] = true
|
|
420
|
+
return base
|
|
421
|
+
}
|
|
422
|
+
for suffix := 1; ; suffix++ {
|
|
423
|
+
candidate := fmt.Sprintf("%s_%d", base, suffix)
|
|
424
|
+
if !state.identifiers[candidate] {
|
|
425
|
+
state.identifiers[candidate] = true
|
|
426
|
+
return candidate
|
|
427
|
+
}
|
|
428
|
+
}
|
|
290
429
|
}
|
|
291
430
|
|
|
292
431
|
func stmtsFromCheckerTypeSeen(
|
|
293
432
|
factory *shimast.NodeFactory,
|
|
294
433
|
checker *shimchecker.Checker,
|
|
434
|
+
enclosing *shimast.Node,
|
|
295
435
|
path string,
|
|
296
436
|
t *shimchecker.Type,
|
|
297
|
-
|
|
437
|
+
state *predicateState,
|
|
298
438
|
depth int,
|
|
299
439
|
) []*shimast.Node {
|
|
300
440
|
// Platform and application interfaces can be recursive. Runtime guards are
|
|
301
441
|
// deliberately bounded so a cyclic type graph cannot make compilation hang.
|
|
302
|
-
if depth > 8 ||
|
|
442
|
+
if depth > 8 || state.active[t] {
|
|
303
443
|
return nil
|
|
304
444
|
}
|
|
305
445
|
flags := t.Flags()
|
|
306
446
|
switch {
|
|
447
|
+
case flags&shimchecker.TypeFlagsEnumLike != 0:
|
|
448
|
+
invalid, ok := enumInvalidPredicate(factory, path, t)
|
|
449
|
+
if !ok {
|
|
450
|
+
return nil
|
|
451
|
+
}
|
|
452
|
+
return []*shimast.Node{throwIf(factory, invalid, "Expected "+path+" to be a valid enum value")}
|
|
453
|
+
case classTypeSymbol(t) != nil:
|
|
454
|
+
invalid, className, ok := classInvalidPredicate(factory, checker, enclosing, path, t)
|
|
455
|
+
if !ok {
|
|
456
|
+
return nil
|
|
457
|
+
}
|
|
458
|
+
return []*shimast.Node{throwIf(factory, invalid, "Expected "+path+" to be an instance of "+className)}
|
|
459
|
+
case flags&shimchecker.TypeFlagsUnion != 0:
|
|
460
|
+
members := t.Types()
|
|
461
|
+
if len(members) == 0 || len(members) > 12 {
|
|
462
|
+
return nil
|
|
463
|
+
}
|
|
464
|
+
var invalid *shimast.Expression
|
|
465
|
+
for _, member := range members {
|
|
466
|
+
memberInvalid, ok := invalidPredicate(factory, checker, enclosing, path, member, state, depth+1)
|
|
467
|
+
if !ok {
|
|
468
|
+
return nil
|
|
469
|
+
}
|
|
470
|
+
if invalid == nil {
|
|
471
|
+
invalid = memberInvalid
|
|
472
|
+
} else {
|
|
473
|
+
invalid = binary(factory, invalid, shimast.KindAmpersandAmpersandToken, memberInvalid)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return []*shimast.Node{throwIf(factory, invalid, "Expected "+path+" to match its union type")}
|
|
477
|
+
case flags&shimchecker.TypeFlagsIntersection != 0:
|
|
478
|
+
if base, ok := brandedBaseType(checker, t); ok {
|
|
479
|
+
return stmtsFromCheckerTypeSeen(factory, checker, enclosing, path, base, state, depth+1)
|
|
480
|
+
}
|
|
481
|
+
if !isStructuralIntersection(checker, t) {
|
|
482
|
+
return nil
|
|
483
|
+
}
|
|
484
|
+
return structuralStatements(factory, checker, enclosing, path, t, state, depth)
|
|
485
|
+
case flags&shimchecker.TypeFlagsNull != 0:
|
|
486
|
+
return []*shimast.Node{equalityCheck(factory, path, factory.NewToken(shimast.KindNullKeyword), "null")}
|
|
487
|
+
case flags&shimchecker.TypeFlagsUndefined != 0:
|
|
488
|
+
return []*shimast.Node{equalityCheck(factory, path, factory.NewIdentifier("undefined"), "undefined")}
|
|
489
|
+
case flags&shimchecker.TypeFlagsStringLiteral != 0:
|
|
490
|
+
value, ok := t.AsLiteralType().Value().(string)
|
|
491
|
+
if !ok {
|
|
492
|
+
return nil
|
|
493
|
+
}
|
|
494
|
+
return []*shimast.Node{equalityCheck(factory, path, factory.NewStringLiteral(value, shimast.TokenFlagsNone), fmt.Sprintf("%q", value))}
|
|
495
|
+
case flags&shimchecker.TypeFlagsNumberLiteral != 0:
|
|
496
|
+
value := fmt.Sprint(t.AsLiteralType().Value())
|
|
497
|
+
return []*shimast.Node{equalityCheck(factory, path, factory.NewNumericLiteral(value, shimast.TokenFlagsNone), value)}
|
|
498
|
+
case flags&shimchecker.TypeFlagsBooleanLiteral != 0:
|
|
499
|
+
value, ok := t.AsLiteralType().Value().(bool)
|
|
500
|
+
if !ok {
|
|
501
|
+
return nil
|
|
502
|
+
}
|
|
503
|
+
kind, label := shimast.KindFalseKeyword, "false"
|
|
504
|
+
if value {
|
|
505
|
+
kind, label = shimast.KindTrueKeyword, "true"
|
|
506
|
+
}
|
|
507
|
+
return []*shimast.Node{equalityCheck(factory, path, factory.NewToken(kind), label)}
|
|
508
|
+
case flags&shimchecker.TypeFlagsBigIntLiteral != 0:
|
|
509
|
+
value := fmt.Sprint(t.AsLiteralType().Value()) + "n"
|
|
510
|
+
return []*shimast.Node{equalityCheck(factory, path, factory.NewBigIntLiteral(value, shimast.TokenFlagsNone), value)}
|
|
511
|
+
case flags&shimchecker.TypeFlagsTemplateLiteral != 0:
|
|
512
|
+
invalid, ok := templateLiteralInvalidPredicate(factory, path, t)
|
|
513
|
+
if !ok {
|
|
514
|
+
return nil
|
|
515
|
+
}
|
|
516
|
+
return []*shimast.Node{throwIf(factory, invalid, "Expected "+path+" to match its template literal type")}
|
|
307
517
|
case flags&shimchecker.TypeFlagsString != 0:
|
|
308
518
|
return []*shimast.Node{typeofCheck(factory, path, "string")}
|
|
309
519
|
case flags&shimchecker.TypeFlagsNumber != 0:
|
|
@@ -312,56 +522,813 @@ func stmtsFromCheckerTypeSeen(
|
|
|
312
522
|
return []*shimast.Node{typeofCheck(factory, path, "boolean")}
|
|
313
523
|
case flags&shimchecker.TypeFlagsBigInt != 0:
|
|
314
524
|
return []*shimast.Node{typeofCheck(factory, path, "bigint")}
|
|
315
|
-
case flags&shimchecker.TypeFlagsObject != 0:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
props := shimchecker.Checker_getApparentProperties(checker, t)
|
|
319
|
-
if len(props) == 0 {
|
|
525
|
+
case flags&shimchecker.TypeFlagsObject != 0 && checker.IsArrayLikeType(t) && !shimchecker.IsTupleType(t):
|
|
526
|
+
invalid, ok := arrayInvalidPredicate(factory, checker, enclosing, path, t, state, depth)
|
|
527
|
+
if !ok {
|
|
320
528
|
return nil
|
|
321
529
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
530
|
+
return []*shimast.Node{throwIf(factory, invalid, "Expected "+path+" to be an array with valid elements")}
|
|
531
|
+
case flags&shimchecker.TypeFlagsObject != 0 && shimchecker.IsTupleType(t):
|
|
532
|
+
invalid, ok := tupleInvalidPredicate(factory, checker, enclosing, path, t, state, depth)
|
|
533
|
+
if !ok {
|
|
534
|
+
return nil
|
|
535
|
+
}
|
|
536
|
+
return []*shimast.Node{throwIf(factory, invalid, "Expected "+path+" to be a valid tuple")}
|
|
537
|
+
case flags&shimchecker.TypeFlagsObject != 0:
|
|
538
|
+
return structuralStatements(factory, checker, enclosing, path, t, state, depth)
|
|
539
|
+
default:
|
|
540
|
+
return nil
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// invalidPredicate returns a composable expression that is true when path does
|
|
545
|
+
// not satisfy t. The bool is false when a sound runtime predicate is not
|
|
546
|
+
// available; callers then skip the entire union rather than partially checking it.
|
|
547
|
+
func invalidPredicate(
|
|
548
|
+
factory *shimast.NodeFactory,
|
|
549
|
+
checker *shimchecker.Checker,
|
|
550
|
+
enclosing *shimast.Node,
|
|
551
|
+
path string,
|
|
552
|
+
t *shimchecker.Type,
|
|
553
|
+
state *predicateState,
|
|
554
|
+
depth int,
|
|
555
|
+
) (*shimast.Expression, bool) {
|
|
556
|
+
if t == nil || depth > 8 || state.active[t] {
|
|
557
|
+
return nil, false
|
|
558
|
+
}
|
|
559
|
+
flags := t.Flags()
|
|
560
|
+
switch {
|
|
561
|
+
case flags&shimchecker.TypeFlagsEnumLike != 0:
|
|
562
|
+
return enumInvalidPredicate(factory, path, t)
|
|
563
|
+
case classTypeSymbol(t) != nil:
|
|
564
|
+
invalid, _, ok := classInvalidPredicate(factory, checker, enclosing, path, t)
|
|
565
|
+
return invalid, ok
|
|
566
|
+
case flags&shimchecker.TypeFlagsUnion != 0:
|
|
567
|
+
members := t.Types()
|
|
568
|
+
if len(members) == 0 || len(members) > 12 {
|
|
569
|
+
return nil, false
|
|
570
|
+
}
|
|
571
|
+
var out *shimast.Expression
|
|
572
|
+
for _, member := range members {
|
|
573
|
+
pred, ok := invalidPredicate(factory, checker, enclosing, path, member, state, depth+1)
|
|
574
|
+
if !ok {
|
|
575
|
+
return nil, false
|
|
326
576
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
577
|
+
if out == nil {
|
|
578
|
+
out = pred
|
|
579
|
+
} else {
|
|
580
|
+
out = binary(factory, out, shimast.KindAmpersandAmpersandToken, pred)
|
|
330
581
|
}
|
|
331
|
-
|
|
332
|
-
|
|
582
|
+
}
|
|
583
|
+
return out, true
|
|
584
|
+
case flags&shimchecker.TypeFlagsIntersection != 0:
|
|
585
|
+
if base, ok := brandedBaseType(checker, t); ok {
|
|
586
|
+
return invalidPredicate(factory, checker, enclosing, path, base, state, depth+1)
|
|
587
|
+
}
|
|
588
|
+
if !isStructuralIntersection(checker, t) {
|
|
589
|
+
return nil, false
|
|
590
|
+
}
|
|
591
|
+
return structuralInvalidPredicate(factory, checker, enclosing, path, t, state, depth)
|
|
592
|
+
case flags&shimchecker.TypeFlagsNull != 0:
|
|
593
|
+
return binary(factory, pathExpr(factory, path), shimast.KindExclamationEqualsEqualsToken, factory.NewToken(shimast.KindNullKeyword)), true
|
|
594
|
+
case flags&shimchecker.TypeFlagsUndefined != 0:
|
|
595
|
+
return binary(factory, pathExpr(factory, path), shimast.KindExclamationEqualsEqualsToken, factory.NewIdentifier("undefined")), true
|
|
596
|
+
case flags&shimchecker.TypeFlagsStringLiteral != 0:
|
|
597
|
+
value, ok := t.AsLiteralType().Value().(string)
|
|
598
|
+
if !ok {
|
|
599
|
+
return nil, false
|
|
600
|
+
}
|
|
601
|
+
return binary(factory, pathExpr(factory, path), shimast.KindExclamationEqualsEqualsToken, factory.NewStringLiteral(value, shimast.TokenFlagsNone)), true
|
|
602
|
+
case flags&shimchecker.TypeFlagsNumberLiteral != 0:
|
|
603
|
+
return binary(factory, pathExpr(factory, path), shimast.KindExclamationEqualsEqualsToken, factory.NewNumericLiteral(fmt.Sprint(t.AsLiteralType().Value()), shimast.TokenFlagsNone)), true
|
|
604
|
+
case flags&shimchecker.TypeFlagsBooleanLiteral != 0:
|
|
605
|
+
value, ok := t.AsLiteralType().Value().(bool)
|
|
606
|
+
if !ok {
|
|
607
|
+
return nil, false
|
|
608
|
+
}
|
|
609
|
+
kind := shimast.KindFalseKeyword
|
|
610
|
+
if value {
|
|
611
|
+
kind = shimast.KindTrueKeyword
|
|
612
|
+
}
|
|
613
|
+
return binary(factory, pathExpr(factory, path), shimast.KindExclamationEqualsEqualsToken, factory.NewToken(kind)), true
|
|
614
|
+
case flags&shimchecker.TypeFlagsBigIntLiteral != 0:
|
|
615
|
+
value := fmt.Sprint(t.AsLiteralType().Value()) + "n"
|
|
616
|
+
return binary(factory, pathExpr(factory, path), shimast.KindExclamationEqualsEqualsToken, factory.NewBigIntLiteral(value, shimast.TokenFlagsNone)), true
|
|
617
|
+
case flags&shimchecker.TypeFlagsTemplateLiteral != 0:
|
|
618
|
+
return templateLiteralInvalidPredicate(factory, path, t)
|
|
619
|
+
case flags&shimchecker.TypeFlagsString != 0:
|
|
620
|
+
return binary(factory, factory.NewTypeOfExpression(pathExpr(factory, path)), shimast.KindExclamationEqualsEqualsToken, factory.NewStringLiteral("string", shimast.TokenFlagsNone)), true
|
|
621
|
+
case flags&shimchecker.TypeFlagsNumber != 0:
|
|
622
|
+
return binary(factory, factory.NewTypeOfExpression(pathExpr(factory, path)), shimast.KindExclamationEqualsEqualsToken, factory.NewStringLiteral("number", shimast.TokenFlagsNone)), true
|
|
623
|
+
case flags&shimchecker.TypeFlagsBoolean != 0:
|
|
624
|
+
return binary(factory, factory.NewTypeOfExpression(pathExpr(factory, path)), shimast.KindExclamationEqualsEqualsToken, factory.NewStringLiteral("boolean", shimast.TokenFlagsNone)), true
|
|
625
|
+
case flags&shimchecker.TypeFlagsBigInt != 0:
|
|
626
|
+
return binary(factory, factory.NewTypeOfExpression(pathExpr(factory, path)), shimast.KindExclamationEqualsEqualsToken, factory.NewStringLiteral("bigint", shimast.TokenFlagsNone)), true
|
|
627
|
+
case flags&shimchecker.TypeFlagsObject != 0 && checker.IsArrayLikeType(t) && !shimchecker.IsTupleType(t):
|
|
628
|
+
return arrayInvalidPredicate(factory, checker, enclosing, path, t, state, depth)
|
|
629
|
+
case flags&shimchecker.TypeFlagsObject != 0 && shimchecker.IsTupleType(t):
|
|
630
|
+
return tupleInvalidPredicate(factory, checker, enclosing, path, t, state, depth)
|
|
631
|
+
case flags&shimchecker.TypeFlagsObject != 0:
|
|
632
|
+
return structuralInvalidPredicate(factory, checker, enclosing, path, t, state, depth)
|
|
633
|
+
default:
|
|
634
|
+
return nil, false
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// brandedBaseType recognizes erased nominal types such as
|
|
639
|
+
// string & { readonly __brand: unique symbol }. Exactly one runtime-checkable
|
|
640
|
+
// primitive base is required; every other constituent must be a non-empty
|
|
641
|
+
// plain object marker. Marker properties are intentionally never inspected at
|
|
642
|
+
// runtime because TypeScript brands do not exist in emitted JavaScript.
|
|
643
|
+
func brandedBaseType(checker *shimchecker.Checker, t *shimchecker.Type) (*shimchecker.Type, bool) {
|
|
644
|
+
var base *shimchecker.Type
|
|
645
|
+
markerCount := 0
|
|
646
|
+
for _, member := range t.Types() {
|
|
647
|
+
if member == nil {
|
|
648
|
+
return nil, false
|
|
649
|
+
}
|
|
650
|
+
if isBrandBaseType(member) {
|
|
651
|
+
if base != nil {
|
|
652
|
+
return nil, false
|
|
333
653
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
654
|
+
base = member
|
|
655
|
+
continue
|
|
656
|
+
}
|
|
657
|
+
if !isBrandMarkerType(checker, member) {
|
|
658
|
+
return nil, false
|
|
659
|
+
}
|
|
660
|
+
markerCount++
|
|
661
|
+
}
|
|
662
|
+
return base, base != nil && markerCount > 0
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
func isBrandBaseType(t *shimchecker.Type) bool {
|
|
666
|
+
flags := t.Flags()
|
|
667
|
+
return flags&(shimchecker.TypeFlagsString|shimchecker.TypeFlagsNumber|shimchecker.TypeFlagsBoolean|shimchecker.TypeFlagsBigInt|shimchecker.TypeFlagsStringLiteral|shimchecker.TypeFlagsNumberLiteral|shimchecker.TypeFlagsBooleanLiteral|shimchecker.TypeFlagsBigIntLiteral|shimchecker.TypeFlagsTemplateLiteral) != 0
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
func isBrandMarkerType(checker *shimchecker.Checker, t *shimchecker.Type) bool {
|
|
671
|
+
if t.Flags()&shimchecker.TypeFlagsObject == 0 ||
|
|
672
|
+
isClassObjectType(t) ||
|
|
673
|
+
checker.IsArrayLikeType(t) ||
|
|
674
|
+
shimchecker.IsTupleType(t) ||
|
|
675
|
+
len(shimchecker.Checker_getSignaturesOfType(checker, t, shimchecker.SignatureKindCall)) > 0 ||
|
|
676
|
+
len(shimchecker.Checker_getSignaturesOfType(checker, t, shimchecker.SignatureKindConstruct)) > 0 ||
|
|
677
|
+
len(shimchecker.Checker_getIndexInfosOfType(checker, t)) > 0 {
|
|
678
|
+
return false
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
properties := shimchecker.Checker_getApparentProperties(checker, t)
|
|
682
|
+
if len(properties) == 0 {
|
|
683
|
+
return false
|
|
684
|
+
}
|
|
685
|
+
for _, property := range properties {
|
|
686
|
+
if property == nil {
|
|
687
|
+
return false
|
|
688
|
+
}
|
|
689
|
+
if isUniqueSymbolMarkerProperty(property.Name) {
|
|
690
|
+
continue
|
|
691
|
+
}
|
|
692
|
+
if !conventionalBrandMarkerNames[property.Name] {
|
|
693
|
+
return false
|
|
694
|
+
}
|
|
695
|
+
propertyType := shimchecker.Checker_getTypeOfPropertyOfType(checker, t, property.Name)
|
|
696
|
+
if !isBrandMarkerValueType(propertyType) {
|
|
697
|
+
return false
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return true
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
var conventionalBrandMarkerNames = map[string]bool{
|
|
704
|
+
"__brand": true,
|
|
705
|
+
"_brand": true,
|
|
706
|
+
"_tag": true,
|
|
707
|
+
"__tag": true,
|
|
708
|
+
"__type": true,
|
|
709
|
+
"_type": true,
|
|
710
|
+
"brand": true,
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
func isClassObjectType(t *shimchecker.Type) bool {
|
|
714
|
+
if t.ObjectFlags()&shimchecker.ObjectFlagsClass != 0 ||
|
|
715
|
+
t.Symbol() != nil && t.Symbol().Flags&shimast.SymbolFlagsClass != 0 {
|
|
716
|
+
return true
|
|
717
|
+
}
|
|
718
|
+
if t.ObjectFlags()&shimchecker.ObjectFlagsReference == 0 {
|
|
719
|
+
return false
|
|
720
|
+
}
|
|
721
|
+
target := t.Target()
|
|
722
|
+
return target != nil && (target.ObjectFlags()&shimchecker.ObjectFlagsClass != 0 ||
|
|
723
|
+
target.Symbol() != nil && target.Symbol().Flags&shimast.SymbolFlagsClass != 0)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Unique symbol property names are encoded by typescript-go as
|
|
727
|
+
// <internal-prefix>@<symbol-name>@<numeric-id>. Well-known symbols omit the
|
|
728
|
+
// numeric identity suffix and are therefore not treated as nominal markers.
|
|
729
|
+
func isUniqueSymbolMarkerProperty(name string) bool {
|
|
730
|
+
if len(name) < 4 || name[0] != 0xfe || name[1] != '@' {
|
|
731
|
+
return false
|
|
732
|
+
}
|
|
733
|
+
lastAt := strings.LastIndexByte(name, '@')
|
|
734
|
+
if lastAt <= 1 || lastAt == len(name)-1 {
|
|
735
|
+
return false
|
|
736
|
+
}
|
|
737
|
+
_, err := strconv.ParseUint(name[lastAt+1:], 10, 64)
|
|
738
|
+
return err == nil
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
func isBrandMarkerValueType(t *shimchecker.Type) bool {
|
|
742
|
+
if t == nil {
|
|
743
|
+
return false
|
|
744
|
+
}
|
|
745
|
+
flags := t.Flags()
|
|
746
|
+
if flags&(shimchecker.TypeFlagsStringLiteral|shimchecker.TypeFlagsNumberLiteral|shimchecker.TypeFlagsBooleanLiteral|shimchecker.TypeFlagsBigIntLiteral|shimchecker.TypeFlagsUniqueESSymbol|shimchecker.TypeFlagsNever) != 0 {
|
|
747
|
+
return true
|
|
748
|
+
}
|
|
749
|
+
if flags&shimchecker.TypeFlagsUnion == 0 || len(t.Types()) == 0 {
|
|
750
|
+
return false
|
|
751
|
+
}
|
|
752
|
+
for _, member := range t.Types() {
|
|
753
|
+
if !isBrandMarkerValueType(member) {
|
|
754
|
+
return false
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return true
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// templateLiteralInvalidPredicate always checks the string representation. A
|
|
761
|
+
// template with exactly one string-like placeholder can also enforce its
|
|
762
|
+
// fixed prefix and suffix cheaply. Complex templates deliberately fall back
|
|
763
|
+
// to typeof rather than approximating their language with an unsound regex.
|
|
764
|
+
func templateLiteralInvalidPredicate(
|
|
765
|
+
factory *shimast.NodeFactory,
|
|
766
|
+
path string,
|
|
767
|
+
t *shimchecker.Type,
|
|
768
|
+
) (*shimast.Expression, bool) {
|
|
769
|
+
template := t.AsTemplateLiteralType()
|
|
770
|
+
if template == nil {
|
|
771
|
+
return nil, false
|
|
772
|
+
}
|
|
773
|
+
notString := binary(factory, factory.NewTypeOfExpression(pathExpr(factory, path)), shimast.KindExclamationEqualsEqualsToken, factory.NewStringLiteral("string", shimast.TokenFlagsNone))
|
|
774
|
+
texts, placeholders := template.Texts(), template.Types()
|
|
775
|
+
if len(texts) != len(placeholders)+1 {
|
|
776
|
+
return nil, false
|
|
777
|
+
}
|
|
778
|
+
if len(placeholders) != 1 || !isStringLikeType(placeholders[0]) {
|
|
779
|
+
return notString, true
|
|
780
|
+
}
|
|
781
|
+
out := notString
|
|
782
|
+
if texts[0] != "" {
|
|
783
|
+
out = binary(factory, out, shimast.KindBarBarToken, stringMethodInvalidPredicate(factory, path, "startsWith", texts[0]))
|
|
784
|
+
}
|
|
785
|
+
if texts[1] != "" {
|
|
786
|
+
out = binary(factory, out, shimast.KindBarBarToken, stringMethodInvalidPredicate(factory, path, "endsWith", texts[1]))
|
|
787
|
+
}
|
|
788
|
+
return out, true
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
func isStringLikeType(t *shimchecker.Type) bool {
|
|
792
|
+
if t == nil {
|
|
793
|
+
return false
|
|
794
|
+
}
|
|
795
|
+
return t.Flags()&(shimchecker.TypeFlagsString|shimchecker.TypeFlagsStringLiteral|shimchecker.TypeFlagsTemplateLiteral|shimchecker.TypeFlagsStringMapping) != 0
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
func stringMethodInvalidPredicate(factory *shimast.NodeFactory, path, method, value string) *shimast.Expression {
|
|
799
|
+
call := factory.NewCallExpression(
|
|
800
|
+
factory.NewPropertyAccessExpression(pathExpr(factory, path), nil, factory.NewIdentifier(method), 0),
|
|
801
|
+
nil,
|
|
802
|
+
nil,
|
|
803
|
+
factory.NewNodeList([]*shimast.Node{factory.NewStringLiteral(value, shimast.TokenFlagsNone)}),
|
|
804
|
+
0,
|
|
805
|
+
)
|
|
806
|
+
return factory.NewPrefixUnaryExpression(shimast.KindExclamationToken, call)
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// isStructuralIntersection keeps primitive/object intersections (including
|
|
810
|
+
// brands) out of this generic structural path. Arrays and tuples intersected
|
|
811
|
+
// with objects also need semantics beyond a flattened property walk.
|
|
812
|
+
func isStructuralIntersection(checker *shimchecker.Checker, t *shimchecker.Type) bool {
|
|
813
|
+
members := t.Types()
|
|
814
|
+
if len(members) == 0 ||
|
|
815
|
+
len(shimchecker.Checker_getSignaturesOfType(checker, t, shimchecker.SignatureKindCall)) > 0 ||
|
|
816
|
+
len(shimchecker.Checker_getSignaturesOfType(checker, t, shimchecker.SignatureKindConstruct)) > 0 {
|
|
817
|
+
return false
|
|
818
|
+
}
|
|
819
|
+
for _, member := range members {
|
|
820
|
+
if member == nil ||
|
|
821
|
+
member.Flags()&shimchecker.TypeFlagsObject == 0 ||
|
|
822
|
+
checker.IsArrayLikeType(member) ||
|
|
823
|
+
shimchecker.IsTupleType(member) ||
|
|
824
|
+
len(shimchecker.Checker_getSignaturesOfType(checker, member, shimchecker.SignatureKindCall)) > 0 ||
|
|
825
|
+
len(shimchecker.Checker_getSignaturesOfType(checker, member, shimchecker.SignatureKindConstruct)) > 0 {
|
|
826
|
+
return false
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return true
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// structuralStatements emits readable, per-path checks after first proving
|
|
833
|
+
// that the complete structural predicate is representable. That preflight
|
|
834
|
+
// prevents an unsupported property or index value from producing a partial
|
|
835
|
+
// guard that could reject valid inputs.
|
|
836
|
+
func structuralStatements(
|
|
837
|
+
factory *shimast.NodeFactory,
|
|
838
|
+
checker *shimchecker.Checker,
|
|
839
|
+
enclosing *shimast.Node,
|
|
840
|
+
path string,
|
|
841
|
+
t *shimchecker.Type,
|
|
842
|
+
state *predicateState,
|
|
843
|
+
depth int,
|
|
844
|
+
) []*shimast.Node {
|
|
845
|
+
if _, ok := structuralInvalidPredicate(factory, checker, enclosing, path, t, state.clone(), depth); !ok {
|
|
846
|
+
return nil
|
|
847
|
+
}
|
|
848
|
+
state.active[t] = true
|
|
849
|
+
defer delete(state.active, t)
|
|
850
|
+
|
|
851
|
+
out := []*shimast.Node{objectCheck(factory, path)}
|
|
852
|
+
propertyNames := make(map[string]bool)
|
|
853
|
+
for _, sym := range shimchecker.Checker_getApparentProperties(checker, t) {
|
|
854
|
+
if !isRuntimeProperty(sym, propertyNames) || sym.Flags&shimast.SymbolFlagsOptional != 0 {
|
|
855
|
+
continue
|
|
856
|
+
}
|
|
857
|
+
propName := sym.Name
|
|
858
|
+
propType := shimchecker.Checker_getTypeOfPropertyOfType(checker, t, propName)
|
|
859
|
+
out = append(out, requiredPropertyCheck(factory, path, propName))
|
|
860
|
+
out = append(out, stmtsFromCheckerTypeSeen(
|
|
861
|
+
factory,
|
|
862
|
+
checker,
|
|
863
|
+
enclosing,
|
|
864
|
+
propertyPath(path, propName),
|
|
865
|
+
propType,
|
|
866
|
+
state,
|
|
867
|
+
depth+1,
|
|
868
|
+
)...)
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
indexType, hasStringIndex, _ := stringIndexValueType(checker, t)
|
|
872
|
+
if hasStringIndex {
|
|
873
|
+
invalid, _ := stringIndexInvalidPredicate(factory, checker, enclosing, path, indexType, state, depth)
|
|
874
|
+
out = append(out, throwIf(factory, invalid, "Expected "+path+" to have valid string-indexed values"))
|
|
875
|
+
}
|
|
876
|
+
return out
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// structuralInvalidPredicate composes object, required-property, and string
|
|
880
|
+
// index checks so structural types remain usable inside unions and arrays.
|
|
881
|
+
func structuralInvalidPredicate(
|
|
882
|
+
factory *shimast.NodeFactory,
|
|
883
|
+
checker *shimchecker.Checker,
|
|
884
|
+
enclosing *shimast.Node,
|
|
885
|
+
path string,
|
|
886
|
+
t *shimchecker.Type,
|
|
887
|
+
state *predicateState,
|
|
888
|
+
depth int,
|
|
889
|
+
) (*shimast.Expression, bool) {
|
|
890
|
+
if t == nil || depth > 8 || state.active[t] {
|
|
891
|
+
return nil, false
|
|
892
|
+
}
|
|
893
|
+
state.active[t] = true
|
|
894
|
+
defer delete(state.active, t)
|
|
895
|
+
|
|
896
|
+
notObject := binary(factory, factory.NewTypeOfExpression(pathExpr(factory, path)), shimast.KindExclamationEqualsEqualsToken, factory.NewStringLiteral("object", shimast.TokenFlagsNone))
|
|
897
|
+
out := binary(factory, notObject, shimast.KindBarBarToken, binary(factory, pathExpr(factory, path), shimast.KindEqualsEqualsEqualsToken, factory.NewToken(shimast.KindNullKeyword)))
|
|
898
|
+
hasShape := false
|
|
899
|
+
propertyNames := make(map[string]bool)
|
|
900
|
+
for _, sym := range shimchecker.Checker_getApparentProperties(checker, t) {
|
|
901
|
+
if !isRuntimeProperty(sym, propertyNames) {
|
|
902
|
+
continue
|
|
903
|
+
}
|
|
904
|
+
hasShape = true
|
|
905
|
+
if sym.Flags&shimast.SymbolFlagsOptional != 0 {
|
|
906
|
+
continue
|
|
907
|
+
}
|
|
908
|
+
propType := shimchecker.Checker_getTypeOfPropertyOfType(checker, t, sym.Name)
|
|
909
|
+
pred, ok := invalidPredicate(factory, checker, enclosing, propertyPath(path, sym.Name), propType, state, depth+1)
|
|
910
|
+
if !ok {
|
|
911
|
+
return nil, false
|
|
912
|
+
}
|
|
913
|
+
missing := missingPropertyPredicate(factory, path, sym.Name)
|
|
914
|
+
out = binary(factory, out, shimast.KindBarBarToken, binary(factory, missing, shimast.KindBarBarToken, pred))
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
indexType, hasStringIndex, ok := stringIndexValueType(checker, t)
|
|
918
|
+
if !ok {
|
|
919
|
+
return nil, false
|
|
920
|
+
}
|
|
921
|
+
if hasStringIndex {
|
|
922
|
+
hasShape = true
|
|
923
|
+
invalid, valid := stringIndexInvalidPredicate(factory, checker, enclosing, path, indexType, state, depth)
|
|
924
|
+
if !valid {
|
|
925
|
+
return nil, false
|
|
926
|
+
}
|
|
927
|
+
out = binary(factory, out, shimast.KindBarBarToken, invalid)
|
|
928
|
+
}
|
|
929
|
+
if !hasShape {
|
|
930
|
+
return nil, false
|
|
931
|
+
}
|
|
932
|
+
return out, true
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// enumInvalidPredicate inlines the checker-known enum member values. This is
|
|
936
|
+
// deliberately independent of the emitted enum object so it also works for
|
|
937
|
+
// const enums. A computed member has a nil checker value; in that case the
|
|
938
|
+
// complete enum guard is skipped instead of rejecting a valid runtime value.
|
|
939
|
+
func enumInvalidPredicate(
|
|
940
|
+
factory *shimast.NodeFactory,
|
|
941
|
+
path string,
|
|
942
|
+
t *shimchecker.Type,
|
|
943
|
+
) (*shimast.Expression, bool) {
|
|
944
|
+
if t == nil || t.Flags()&shimchecker.TypeFlagsEnumLike == 0 {
|
|
945
|
+
return nil, false
|
|
946
|
+
}
|
|
947
|
+
members := []*shimchecker.Type{t}
|
|
948
|
+
if t.Flags()&shimchecker.TypeFlagsUnion != 0 {
|
|
949
|
+
members = t.Types()
|
|
950
|
+
}
|
|
951
|
+
if len(members) == 0 {
|
|
952
|
+
return nil, false
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
seen := make(map[string]bool, len(members))
|
|
956
|
+
var invalid *shimast.Expression
|
|
957
|
+
for _, member := range members {
|
|
958
|
+
if member == nil || member.Flags()&shimchecker.TypeFlagsEnumLike == 0 {
|
|
959
|
+
return nil, false
|
|
960
|
+
}
|
|
961
|
+
value := member.AsLiteralType().Value()
|
|
962
|
+
if value == nil {
|
|
963
|
+
return nil, false
|
|
964
|
+
}
|
|
965
|
+
var expected *shimast.Expression
|
|
966
|
+
var key string
|
|
967
|
+
switch {
|
|
968
|
+
case member.Flags()&shimchecker.TypeFlagsStringLiteral != 0:
|
|
969
|
+
text, ok := value.(string)
|
|
970
|
+
if !ok {
|
|
971
|
+
return nil, false
|
|
337
972
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
973
|
+
expected = factory.NewStringLiteral(text, shimast.TokenFlagsNone)
|
|
974
|
+
key = "string:" + text
|
|
975
|
+
case member.Flags()&shimchecker.TypeFlagsNumberLiteral != 0:
|
|
976
|
+
text := fmt.Sprint(value)
|
|
977
|
+
expected = factory.NewNumericLiteral(text, shimast.TokenFlagsNone)
|
|
978
|
+
key = "number:" + text
|
|
979
|
+
default:
|
|
980
|
+
return nil, false
|
|
981
|
+
}
|
|
982
|
+
if seen[key] {
|
|
983
|
+
continue
|
|
984
|
+
}
|
|
985
|
+
seen[key] = true
|
|
986
|
+
memberInvalid := binary(factory, pathExpr(factory, path), shimast.KindExclamationEqualsEqualsToken, expected)
|
|
987
|
+
if invalid == nil {
|
|
988
|
+
invalid = memberInvalid
|
|
989
|
+
} else {
|
|
990
|
+
invalid = binary(factory, invalid, shimast.KindAmpersandAmpersandToken, memberInvalid)
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return invalid, invalid != nil
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
func classTypeSymbol(t *shimchecker.Type) *shimast.Symbol {
|
|
997
|
+
if t == nil || t.Flags()&shimchecker.TypeFlagsObject == 0 {
|
|
349
998
|
return nil
|
|
350
999
|
}
|
|
1000
|
+
candidates := []*shimast.Symbol{t.Symbol(), shimchecker.Type_getTypeNameSymbol(t)}
|
|
1001
|
+
if t.ObjectFlags()&shimchecker.ObjectFlagsReference != 0 {
|
|
1002
|
+
target := t.Target()
|
|
1003
|
+
if target != nil {
|
|
1004
|
+
candidates = append(candidates, target.Symbol(), shimchecker.Type_getTypeNameSymbol(target))
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
for _, symbol := range candidates {
|
|
1008
|
+
if symbol != nil && symbol.Flags&shimast.SymbolFlagsClass != 0 {
|
|
1009
|
+
return symbol
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return nil
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
func classInvalidPredicate(
|
|
1016
|
+
factory *shimast.NodeFactory,
|
|
1017
|
+
checker *shimchecker.Checker,
|
|
1018
|
+
enclosing *shimast.Node,
|
|
1019
|
+
path string,
|
|
1020
|
+
t *shimchecker.Type,
|
|
1021
|
+
) (*shimast.Expression, string, bool) {
|
|
1022
|
+
symbol := classTypeSymbol(t)
|
|
1023
|
+
if symbol == nil || !shimchecker.Checker_isSymbolAccessibleAsValue(checker, symbol, enclosing) {
|
|
1024
|
+
return nil, "", false
|
|
1025
|
+
}
|
|
1026
|
+
className := shimchecker.Checker_symbolToValueString(checker, symbol, enclosing)
|
|
1027
|
+
constructor, ok := qualifiedValueExpr(factory, className)
|
|
1028
|
+
if !ok || !hasRuntimeValueBinding(factory, checker, enclosing, className) {
|
|
1029
|
+
return nil, "", false
|
|
1030
|
+
}
|
|
1031
|
+
isInstance := binary(factory, pathExpr(factory, path), shimast.KindInstanceOfKeyword, constructor)
|
|
1032
|
+
return factory.NewPrefixUnaryExpression(shimast.KindExclamationToken, isInstance), className, true
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
func hasRuntimeValueBinding(
|
|
1036
|
+
factory *shimast.NodeFactory,
|
|
1037
|
+
checker *shimchecker.Checker,
|
|
1038
|
+
enclosing *shimast.Node,
|
|
1039
|
+
name string,
|
|
1040
|
+
) bool {
|
|
1041
|
+
root, _, _ := strings.Cut(name, ".")
|
|
1042
|
+
binding := shimchecker.Checker_resolveEntityName(
|
|
1043
|
+
checker,
|
|
1044
|
+
factory.NewIdentifier(root),
|
|
1045
|
+
shimast.SymbolFlagsValue,
|
|
1046
|
+
true,
|
|
1047
|
+
true,
|
|
1048
|
+
enclosing,
|
|
1049
|
+
)
|
|
1050
|
+
if binding == nil {
|
|
1051
|
+
return false
|
|
1052
|
+
}
|
|
1053
|
+
for _, declaration := range binding.Declarations {
|
|
1054
|
+
if declaration == nil {
|
|
1055
|
+
continue
|
|
1056
|
+
}
|
|
1057
|
+
for current := declaration; current != nil; current = current.Parent {
|
|
1058
|
+
// Emit decides whether an ES import survives from the original
|
|
1059
|
+
// semantic references. A detached predicate added here cannot
|
|
1060
|
+
// reliably retain an alias that was used only as a type, so reject
|
|
1061
|
+
// every imported root rather than risk a dangling constructor name.
|
|
1062
|
+
if current.Kind == shimast.KindImportClause {
|
|
1063
|
+
return false
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return true
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// qualifiedValueExpr accepts the identifier/property-access form produced by
|
|
1071
|
+
// the checker for ordinary class values. If naming the constructor would need
|
|
1072
|
+
// a synthetic import or another expression form, the class guard is skipped.
|
|
1073
|
+
func qualifiedValueExpr(factory *shimast.NodeFactory, name string) (*shimast.Expression, bool) {
|
|
1074
|
+
parts := strings.Split(name, ".")
|
|
1075
|
+
if len(parts) == 0 || !isIdentifierSegment(parts[0]) {
|
|
1076
|
+
return nil, false
|
|
1077
|
+
}
|
|
1078
|
+
var out *shimast.Expression = factory.NewIdentifier(parts[0])
|
|
1079
|
+
for _, part := range parts[1:] {
|
|
1080
|
+
if !isIdentifierSegment(part) {
|
|
1081
|
+
return nil, false
|
|
1082
|
+
}
|
|
1083
|
+
out = factory.NewPropertyAccessExpression(out, nil, factory.NewIdentifier(part), 0)
|
|
1084
|
+
}
|
|
1085
|
+
return out, true
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
func isRuntimeProperty(sym *shimast.Symbol, seen map[string]bool) bool {
|
|
1089
|
+
if sym == nil || strings.HasPrefix(sym.Name, "\xFE") || seen[sym.Name] {
|
|
1090
|
+
return false
|
|
1091
|
+
}
|
|
1092
|
+
seen[sym.Name] = true
|
|
1093
|
+
return true
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// stringIndexValueType accepts exactly one string index. Number and symbol
|
|
1097
|
+
// indexes are skipped as a whole because Object.keys cannot enforce their
|
|
1098
|
+
// distinct value semantics soundly.
|
|
1099
|
+
func stringIndexValueType(checker *shimchecker.Checker, t *shimchecker.Type) (*shimchecker.Type, bool, bool) {
|
|
1100
|
+
var valueType *shimchecker.Type
|
|
1101
|
+
for _, info := range shimchecker.Checker_getIndexInfosOfType(checker, t) {
|
|
1102
|
+
if info == nil || info.KeyType() == nil || info.KeyType().Flags()&shimchecker.TypeFlagsString == 0 || valueType != nil {
|
|
1103
|
+
return nil, false, false
|
|
1104
|
+
}
|
|
1105
|
+
valueType = info.ValueType()
|
|
1106
|
+
if valueType == nil {
|
|
1107
|
+
return nil, false, false
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return valueType, valueType != nil, true
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
func stringIndexInvalidPredicate(
|
|
1114
|
+
factory *shimast.NodeFactory,
|
|
1115
|
+
checker *shimchecker.Checker,
|
|
1116
|
+
enclosing *shimast.Node,
|
|
1117
|
+
path string,
|
|
1118
|
+
valueType *shimchecker.Type,
|
|
1119
|
+
state *predicateState,
|
|
1120
|
+
depth int,
|
|
1121
|
+
) (*shimast.Expression, bool) {
|
|
1122
|
+
keyName := state.freshIdentifier("__di_key")
|
|
1123
|
+
valueInvalid, ok := invalidPredicate(factory, checker, enclosing, path+"["+keyName+"]", valueType, state, depth+1)
|
|
1124
|
+
if !ok {
|
|
1125
|
+
return nil, false
|
|
1126
|
+
}
|
|
1127
|
+
keys := factory.NewCallExpression(
|
|
1128
|
+
factory.NewPropertyAccessExpression(factory.NewIdentifier("Object"), nil, factory.NewIdentifier("keys"), 0),
|
|
1129
|
+
nil, nil, factory.NewNodeList([]*shimast.Node{pathExpr(factory, path)}), 0,
|
|
1130
|
+
)
|
|
1131
|
+
param := factory.NewParameterDeclaration(nil, nil, factory.NewIdentifier(keyName), nil, nil, nil)
|
|
1132
|
+
arrow := factory.NewArrowFunction(nil, nil, factory.NewNodeList([]*shimast.Node{param}), nil, nil, factory.NewToken(shimast.KindEqualsGreaterThanToken), valueInvalid)
|
|
1133
|
+
return factory.NewCallExpression(
|
|
1134
|
+
factory.NewPropertyAccessExpression(keys, nil, factory.NewIdentifier("some"), 0),
|
|
1135
|
+
nil, nil, factory.NewNodeList([]*shimast.Node{arrow}), 0,
|
|
1136
|
+
), true
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
func isIdentifierSegment(value string) bool {
|
|
1140
|
+
if value == "" {
|
|
1141
|
+
return false
|
|
1142
|
+
}
|
|
1143
|
+
for i, r := range value {
|
|
1144
|
+
if i == 0 {
|
|
1145
|
+
if r != '_' && r != '$' && !unicode.IsLetter(r) {
|
|
1146
|
+
return false
|
|
1147
|
+
}
|
|
1148
|
+
continue
|
|
1149
|
+
}
|
|
1150
|
+
if r != '_' && r != '$' && !unicode.IsLetter(r) && !unicode.IsDigit(r) && !unicode.IsMark(r) {
|
|
1151
|
+
return false
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return true
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
func tupleInvalidPredicate(
|
|
1158
|
+
factory *shimast.NodeFactory,
|
|
1159
|
+
checker *shimchecker.Checker,
|
|
1160
|
+
enclosing *shimast.Node,
|
|
1161
|
+
path string,
|
|
1162
|
+
t *shimchecker.Type,
|
|
1163
|
+
state *predicateState,
|
|
1164
|
+
depth int,
|
|
1165
|
+
) (*shimast.Expression, bool) {
|
|
1166
|
+
elements := checker.GetTypeArguments(t)
|
|
1167
|
+
flags := t.TargetTupleType().ElementFlags()
|
|
1168
|
+
if len(elements) != len(flags) {
|
|
1169
|
+
return nil, false
|
|
1170
|
+
}
|
|
1171
|
+
minLength := 0
|
|
1172
|
+
for _, flag := range flags {
|
|
1173
|
+
if flag&shimchecker.ElementFlagsRest != 0 {
|
|
1174
|
+
return nil, false
|
|
1175
|
+
}
|
|
1176
|
+
if flag&shimchecker.ElementFlagsRequired != 0 {
|
|
1177
|
+
minLength++
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
isArray := factory.NewCallExpression(
|
|
1181
|
+
factory.NewPropertyAccessExpression(factory.NewIdentifier("Array"), nil, factory.NewIdentifier("isArray"), 0),
|
|
1182
|
+
nil, nil, factory.NewNodeList([]*shimast.Node{pathExpr(factory, path)}), 0,
|
|
1183
|
+
)
|
|
1184
|
+
out := factory.NewPrefixUnaryExpression(shimast.KindExclamationToken, isArray)
|
|
1185
|
+
lengthPath := path + ".length"
|
|
1186
|
+
if minLength == len(elements) {
|
|
1187
|
+
badLength := binary(factory, pathExpr(factory, lengthPath), shimast.KindExclamationEqualsEqualsToken, factory.NewNumericLiteral(fmt.Sprint(len(elements)), shimast.TokenFlagsNone))
|
|
1188
|
+
out = binary(factory, out, shimast.KindBarBarToken, badLength)
|
|
1189
|
+
} else {
|
|
1190
|
+
tooShort := binary(factory, pathExpr(factory, lengthPath), shimast.KindLessThanToken, factory.NewNumericLiteral(fmt.Sprint(minLength), shimast.TokenFlagsNone))
|
|
1191
|
+
tooLong := binary(factory, pathExpr(factory, lengthPath), shimast.KindGreaterThanToken, factory.NewNumericLiteral(fmt.Sprint(len(elements)), shimast.TokenFlagsNone))
|
|
1192
|
+
out = binary(factory, out, shimast.KindBarBarToken, binary(factory, tooShort, shimast.KindBarBarToken, tooLong))
|
|
1193
|
+
}
|
|
1194
|
+
for i, element := range elements {
|
|
1195
|
+
pred, ok := invalidPredicate(factory, checker, enclosing, fmt.Sprintf("%s[%d]", path, i), element, state, depth+1)
|
|
1196
|
+
if !ok {
|
|
1197
|
+
return nil, false
|
|
1198
|
+
}
|
|
1199
|
+
if flags[i]&shimchecker.ElementFlagsOptional != 0 {
|
|
1200
|
+
present := binary(factory, pathExpr(factory, lengthPath), shimast.KindGreaterThanToken, factory.NewNumericLiteral(fmt.Sprint(i), shimast.TokenFlagsNone))
|
|
1201
|
+
pred = binary(factory, present, shimast.KindAmpersandAmpersandToken, pred)
|
|
1202
|
+
}
|
|
1203
|
+
out = binary(factory, out, shimast.KindBarBarToken, pred)
|
|
1204
|
+
}
|
|
1205
|
+
return out, true
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
func arrayInvalidPredicate(
|
|
1209
|
+
factory *shimast.NodeFactory,
|
|
1210
|
+
checker *shimchecker.Checker,
|
|
1211
|
+
enclosing *shimast.Node,
|
|
1212
|
+
path string,
|
|
1213
|
+
t *shimchecker.Type,
|
|
1214
|
+
state *predicateState,
|
|
1215
|
+
depth int,
|
|
1216
|
+
) (*shimast.Expression, bool) {
|
|
1217
|
+
typeArgs := checker.GetTypeArguments(t)
|
|
1218
|
+
if len(typeArgs) != 1 {
|
|
1219
|
+
return nil, false
|
|
1220
|
+
}
|
|
1221
|
+
itemName := state.freshIdentifier("__di_item")
|
|
1222
|
+
itemInvalid, ok := invalidPredicate(factory, checker, enclosing, itemName, typeArgs[0], state, depth+1)
|
|
1223
|
+
if !ok {
|
|
1224
|
+
return nil, false
|
|
1225
|
+
}
|
|
1226
|
+
isArray := factory.NewCallExpression(
|
|
1227
|
+
factory.NewPropertyAccessExpression(factory.NewIdentifier("Array"), nil, factory.NewIdentifier("isArray"), 0),
|
|
1228
|
+
nil, nil, factory.NewNodeList([]*shimast.Node{pathExpr(factory, path)}), 0,
|
|
1229
|
+
)
|
|
1230
|
+
notArray := factory.NewPrefixUnaryExpression(shimast.KindExclamationToken, isArray)
|
|
1231
|
+
param := factory.NewParameterDeclaration(nil, nil, factory.NewIdentifier(itemName), nil, nil, nil)
|
|
1232
|
+
arrow := factory.NewArrowFunction(nil, nil, factory.NewNodeList([]*shimast.Node{param}), nil, nil, factory.NewToken(shimast.KindEqualsGreaterThanToken), itemInvalid)
|
|
1233
|
+
hasInvalidItem := factory.NewCallExpression(
|
|
1234
|
+
factory.NewPropertyAccessExpression(pathExpr(factory, path), nil, factory.NewIdentifier("some"), 0),
|
|
1235
|
+
nil, nil, factory.NewNodeList([]*shimast.Node{arrow}), 0,
|
|
1236
|
+
)
|
|
1237
|
+
return binary(factory, notArray, shimast.KindBarBarToken, hasInvalidItem), true
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
func equalityCheck(factory *shimast.NodeFactory, path string, expected *shimast.Expression, label string) *shimast.Node {
|
|
1241
|
+
cond := binary(factory, pathExpr(factory, path), shimast.KindExclamationEqualsEqualsToken, expected)
|
|
1242
|
+
return throwIf(factory, cond, "Expected "+path+" to equal "+label)
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
func requiredPropertyCheck(factory *shimast.NodeFactory, path, property string) *shimast.Node {
|
|
1246
|
+
return throwIf(factory, missingPropertyPredicate(factory, path, property), "Expected "+path+" to have required property "+strconv.Quote(property))
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
func missingPropertyPredicate(factory *shimast.NodeFactory, path, property string) *shimast.Expression {
|
|
1250
|
+
present := binary(
|
|
1251
|
+
factory,
|
|
1252
|
+
factory.NewStringLiteral(property, shimast.TokenFlagsNone),
|
|
1253
|
+
shimast.KindInKeyword,
|
|
1254
|
+
pathExpr(factory, path),
|
|
1255
|
+
)
|
|
1256
|
+
return factory.NewPrefixUnaryExpression(shimast.KindExclamationToken, present)
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
func propertyPath(path, property string) string {
|
|
1260
|
+
if isSimpleIdentifier(property) {
|
|
1261
|
+
return path + "." + property
|
|
1262
|
+
}
|
|
1263
|
+
return path + "[" + strconv.Quote(property) + "]"
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
func isSimpleIdentifier(value string) bool {
|
|
1267
|
+
if value == "" {
|
|
1268
|
+
return false
|
|
1269
|
+
}
|
|
1270
|
+
for i, r := range value {
|
|
1271
|
+
if !(r == '_' || r == '$' || r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || i > 0 && r >= '0' && r <= '9') {
|
|
1272
|
+
return false
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return true
|
|
351
1276
|
}
|
|
352
1277
|
|
|
353
1278
|
// pathExpr builds a fresh expression tree for a dotted path (e.g. "user.id").
|
|
354
1279
|
// Each call allocates new nodes — AST nodes are not shareable across parents.
|
|
355
1280
|
func pathExpr(factory *shimast.NodeFactory, path string) *shimast.Expression {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1281
|
+
end := strings.IndexAny(path, ".[")
|
|
1282
|
+
if end < 0 {
|
|
1283
|
+
return factory.NewIdentifier(path)
|
|
1284
|
+
}
|
|
1285
|
+
var expr *shimast.Expression = factory.NewIdentifier(path[:end])
|
|
1286
|
+
for end < len(path) {
|
|
1287
|
+
if path[end] == '.' {
|
|
1288
|
+
start := end + 1
|
|
1289
|
+
end = start
|
|
1290
|
+
for end < len(path) && path[end] != '.' && path[end] != '[' {
|
|
1291
|
+
end++
|
|
1292
|
+
}
|
|
1293
|
+
expr = factory.NewPropertyAccessExpression(expr, nil, factory.NewIdentifier(path[start:end]), 0)
|
|
1294
|
+
continue
|
|
1295
|
+
}
|
|
1296
|
+
start := end + 1
|
|
1297
|
+
if start < len(path) && path[start] == '"' {
|
|
1298
|
+
quotedEnd := start + 1
|
|
1299
|
+
escaped := false
|
|
1300
|
+
for quotedEnd < len(path) {
|
|
1301
|
+
if path[quotedEnd] == '"' && !escaped {
|
|
1302
|
+
break
|
|
1303
|
+
}
|
|
1304
|
+
if path[quotedEnd] == '\\' {
|
|
1305
|
+
escaped = !escaped
|
|
1306
|
+
} else {
|
|
1307
|
+
escaped = false
|
|
1308
|
+
}
|
|
1309
|
+
quotedEnd++
|
|
1310
|
+
}
|
|
1311
|
+
if quotedEnd >= len(path) || quotedEnd+1 >= len(path) || path[quotedEnd+1] != ']' {
|
|
1312
|
+
return expr
|
|
1313
|
+
}
|
|
1314
|
+
property, err := strconv.Unquote(path[start : quotedEnd+1])
|
|
1315
|
+
if err != nil {
|
|
1316
|
+
return expr
|
|
1317
|
+
}
|
|
1318
|
+
expr = factory.NewElementAccessExpression(expr, nil, factory.NewStringLiteral(property, shimast.TokenFlagsNone), 0)
|
|
1319
|
+
end = quotedEnd + 2
|
|
1320
|
+
continue
|
|
1321
|
+
}
|
|
1322
|
+
close := strings.IndexByte(path[start:], ']')
|
|
1323
|
+
if close < 0 {
|
|
1324
|
+
return expr
|
|
1325
|
+
}
|
|
1326
|
+
close += start
|
|
1327
|
+
index := path[start:close]
|
|
1328
|
+
// An identifier-shaped synthesized index prints as the numeric token while
|
|
1329
|
+
// avoiding the emitter asking source-text questions of a detached literal.
|
|
1330
|
+
expr = factory.NewElementAccessExpression(expr, nil, factory.NewIdentifier(index), 0)
|
|
1331
|
+
end = close + 1
|
|
365
1332
|
}
|
|
366
1333
|
return expr
|
|
367
1334
|
}
|