@zyno-io/ts-reflection 26.803.2224

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 (61) hide show
  1. package/README.md +13 -0
  2. package/dist/index.cjs +1 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +966 -0
  6. package/dist/reflection/annotations.d.ts +15 -0
  7. package/dist/reflection/annotations.d.ts.map +1 -0
  8. package/dist/reflection/compact-metadata.d.ts +18 -0
  9. package/dist/reflection/compact-metadata.d.ts.map +1 -0
  10. package/dist/reflection/conversion.d.ts +15 -0
  11. package/dist/reflection/conversion.d.ts.map +1 -0
  12. package/dist/reflection/deserializer.d.ts +15 -0
  13. package/dist/reflection/deserializer.d.ts.map +1 -0
  14. package/dist/reflection/errors.d.ts +8 -0
  15. package/dist/reflection/errors.d.ts.map +1 -0
  16. package/dist/reflection/index.d.ts +10 -0
  17. package/dist/reflection/index.d.ts.map +1 -0
  18. package/dist/reflection/metadata-store.d.ts +20 -0
  19. package/dist/reflection/metadata-store.d.ts.map +1 -0
  20. package/dist/reflection/model.d.ts +273 -0
  21. package/dist/reflection/model.d.ts.map +1 -0
  22. package/dist/reflection/primitive-conversion.d.ts +2 -0
  23. package/dist/reflection/primitive-conversion.d.ts.map +1 -0
  24. package/dist/reflection/reflection-class.d.ts +60 -0
  25. package/dist/reflection/reflection-class.d.ts.map +1 -0
  26. package/dist/reflection/type-utils.d.ts +34 -0
  27. package/dist/reflection/type-utils.d.ts.map +1 -0
  28. package/dist/type-compiler/download-prebuilt.cjs +114 -0
  29. package/dist/type-compiler/go/ast_expression.go +187 -0
  30. package/dist/type-compiler/go/ast_metadata.go +388 -0
  31. package/dist/type-compiler/go/collect.go +963 -0
  32. package/dist/type-compiler/go/compact_metadata.go +553 -0
  33. package/dist/type-compiler/go/emission_plan.go +340 -0
  34. package/dist/type-compiler/go/emit_ast.go +557 -0
  35. package/dist/type-compiler/go/emit_ast_test.go +558 -0
  36. package/dist/type-compiler/go/go.mod +10 -0
  37. package/dist/type-compiler/go/plugin.go +359 -0
  38. package/dist/type-compiler/go/plugin_test.go +1206 -0
  39. package/dist/type-compiler/go/precompute.go +86 -0
  40. package/dist/type-compiler/go/receive_type.go +912 -0
  41. package/dist/type-compiler/go/resolve.go +265 -0
  42. package/dist/type-compiler/go/source_scan.go +51 -0
  43. package/dist/type-compiler/go/text_parse.go +734 -0
  44. package/dist/type-compiler/go/type_expr.go +1291 -0
  45. package/dist/type-compiler/go/typia_expr.go +2316 -0
  46. package/dist/type-compiler/index.cjs +43 -0
  47. package/dist/type-compiler/pnp.cjs +474 -0
  48. package/dist/type-compiler/prebuilt.cjs +324 -0
  49. package/dist/type-metadata-runtime.cjs +1 -0
  50. package/dist/type-metadata-runtime.d.ts +2 -0
  51. package/dist/type-metadata-runtime.d.ts.map +1 -0
  52. package/dist/type-metadata-runtime.js +107 -0
  53. package/dist/types/index.d.ts +4 -0
  54. package/dist/types/index.d.ts.map +1 -0
  55. package/dist/types/primitives.d.ts +33 -0
  56. package/dist/types/primitives.d.ts.map +1 -0
  57. package/dist/types/runtime.d.ts +2 -0
  58. package/dist/types/runtime.d.ts.map +1 -0
  59. package/dist/types/type-annotations.d.ts +28 -0
  60. package/dist/types/type-annotations.d.ts.map +1 -0
  61. package/package.json +47 -0
@@ -0,0 +1,963 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "regexp"
8
+ "sort"
9
+ "strconv"
10
+ "strings"
11
+
12
+ shimast "github.com/microsoft/typescript-go/shim/ast"
13
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
14
+
15
+ "github.com/samchon/ttsc/packages/ttsc/driver"
16
+ )
17
+
18
+ func collectRegistry(prog *driver.Program, cwd string, emitTypeAliases bool, emitUndecoratedMethods bool) *registry {
19
+ reg := &registry{
20
+ files: map[string]*fileInfo{},
21
+ byPath: map[string]*fileInfo{},
22
+ checker: prog.Checker,
23
+ typiaCache: map[typiaCacheKey]string{},
24
+ typiaFailures: map[*shimchecker.Type]bool{},
25
+ classes: map[string]*classInfo{},
26
+ external: map[string]map[string][]functionInfo{},
27
+ externalPackageRoots: loadPnpExternalPackageRoots(cwd),
28
+ }
29
+ for _, file := range prog.TSProgram.SourceFiles() {
30
+ if shouldSkipFile(file.FileName(), cwd) {
31
+ continue
32
+ }
33
+ info := &fileInfo{
34
+ file: file,
35
+ moduleKey: moduleKey(file.FileName()),
36
+ precompute: shouldPrecomputeFile(file.FileName()),
37
+ decoratedMethodsOnly: !emitUndecoratedMethods,
38
+ aliases: map[string]aliasInfo{},
39
+ interfaces: map[string][]interfaceInfo{},
40
+ enums: map[string]enumInfo{},
41
+ classes: []*classInfo{},
42
+ functions: map[string][]functionInfo{},
43
+ imports: map[string]importRef{},
44
+ reexports: map[string]importRef{},
45
+ }
46
+ reg.files[file.FileName()] = info
47
+ reg.byPath[info.moduleKey] = info
48
+ collectTextDeclarations(info)
49
+ }
50
+ for _, info := range reg.files {
51
+ collectImports(info, reg)
52
+ collectReexports(info, reg)
53
+ }
54
+ for _, info := range reg.files {
55
+ collectAstDeclarations(info, reg)
56
+ }
57
+ for _, info := range reg.files {
58
+ collectGenericCalls(info, reg)
59
+ }
60
+ precomputeMetadataExpressions(reg, emitTypeAliases)
61
+ return reg
62
+ }
63
+
64
+ // Yarn PnP keeps JavaScript packages inside ZipFS, which Node can read but the
65
+ // native compiler cannot. The descriptor copies just imported declaration files
66
+ // to this map before starting the host. Other package managers leave the map
67
+ // empty and continue through the normal node_modules lookup.
68
+ func loadPnpExternalPackageRoots(cwd string) map[string]string {
69
+ if cwd == "" {
70
+ return map[string]string{}
71
+ }
72
+ contents, err := os.ReadFile(filepath.Join(cwd, ".yarn", "tsf-pnp", "external-package-roots.json"))
73
+ if err != nil {
74
+ return map[string]string{}
75
+ }
76
+ roots := map[string]string{}
77
+ if err := json.Unmarshal(contents, &roots); err != nil {
78
+ return map[string]string{}
79
+ }
80
+ for packageName, root := range roots {
81
+ if stat, err := os.Stat(root); err != nil || !stat.IsDir() {
82
+ delete(roots, packageName)
83
+ }
84
+ }
85
+ return roots
86
+ }
87
+
88
+ func shouldSkipFile(fileName string, cwd string) bool {
89
+ slash := filepath.ToSlash(fileName)
90
+ return isTypeScriptLibDeclaration(slash) ||
91
+ isAmbientTypesPackageDeclaration(slash) ||
92
+ isProjectBuildOutputFile(fileName, cwd) ||
93
+ strings.HasSuffix(slash, "/src/reflection.ts")
94
+ }
95
+
96
+ func shouldPrecomputeFile(fileName string) bool {
97
+ slash := filepath.ToSlash(fileName)
98
+ return !strings.HasSuffix(slash, ".d.ts") && !strings.Contains(slash, "/node_modules/")
99
+ }
100
+
101
+ func isTypeScriptLibDeclaration(slash string) bool {
102
+ base := filepath.Base(slash)
103
+ return strings.HasPrefix(base, "lib.") && strings.HasSuffix(base, ".d.ts")
104
+ }
105
+
106
+ func isAmbientTypesPackageDeclaration(slash string) bool {
107
+ return strings.Contains(slash, "/node_modules/@types/") && strings.HasSuffix(slash, ".d.ts")
108
+ }
109
+
110
+ func isProjectBuildOutputFile(fileName string, cwd string) bool {
111
+ if cwd == "" {
112
+ return false
113
+ }
114
+ rel, err := filepath.Rel(cwd, fileName)
115
+ if err != nil || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
116
+ return false
117
+ }
118
+ rel = filepath.ToSlash(rel)
119
+ first, _, _ := strings.Cut(rel, "/")
120
+ return first == "dist" || strings.HasPrefix(first, "dist-")
121
+ }
122
+
123
+ func moduleKey(fileName string) string {
124
+ slash := filepath.ToSlash(fileName)
125
+ lower := strings.ToLower(slash)
126
+ for _, suffix := range []string{".d.mts", ".d.cts", ".d.ts"} {
127
+ if strings.HasSuffix(lower, suffix) {
128
+ return filepath.Clean(slash[:len(slash)-len(suffix)])
129
+ }
130
+ }
131
+ ext := strings.ToLower(filepath.Ext(slash))
132
+ if ext == ".mts" || ext == ".cts" {
133
+ return filepath.Clean(slash)
134
+ }
135
+ return filepath.Clean(strings.TrimSuffix(slash, filepath.Ext(slash)))
136
+ }
137
+
138
+ func moduleSpecifier(fromFile string, toFile string) string {
139
+ return moduleSpecifierForOutput(fromFile, toFile, false)
140
+ }
141
+
142
+ func moduleSpecifierForOutput(fromFile string, toFile string, esm bool) string {
143
+ fromDir := filepath.Dir(fromFile)
144
+ target := strings.TrimSuffix(toFile, filepath.Ext(toFile)) + outputImportExtension(toFile, esm)
145
+ spec, err := filepath.Rel(fromDir, target)
146
+ if err != nil {
147
+ spec = target
148
+ }
149
+ spec = filepath.ToSlash(spec)
150
+ if !strings.HasPrefix(spec, ".") {
151
+ spec = "./" + spec
152
+ }
153
+ return spec
154
+ }
155
+
156
+ func outputImportExtension(sourceFile string, esm bool) string {
157
+ switch strings.ToLower(filepath.Ext(sourceFile)) {
158
+ case ".mts":
159
+ return ".mjs"
160
+ case ".cts":
161
+ return ".cjs"
162
+ case ".ts", ".tsx":
163
+ if esm {
164
+ return ".js"
165
+ }
166
+ return ""
167
+ default:
168
+ return ""
169
+ }
170
+ }
171
+
172
+ func collectTextDeclarations(info *fileInfo) {
173
+ text := info.file.Text()
174
+ re := regexp.MustCompile(`(?m)^\s*(export\s+)?(?:declare\s+)?interface\s+([A-Za-z_$][\w$]*)\b`)
175
+ search := 0
176
+ for {
177
+ loc := re.FindStringSubmatchIndex(text[search:])
178
+ if loc == nil {
179
+ break
180
+ }
181
+ start := search + loc[0]
182
+ name := text[search+loc[4] : search+loc[5]]
183
+ exported := loc[2] >= 0
184
+ afterName := search + loc[1]
185
+ openRel := strings.IndexByte(text[afterName:], '{')
186
+ if openRel < 0 {
187
+ break
188
+ }
189
+ open := afterName + openRel
190
+ close := findBalanced(text, open, '{', '}')
191
+ if close < 0 {
192
+ search = open + 1
193
+ continue
194
+ }
195
+ body := strings.TrimSpace(text[open+1 : close])
196
+ header := text[afterName:open]
197
+ params, defaults := interfaceTypeParametersFromHeader(header)
198
+ info.interfaces[name] = append(info.interfaces[name], interfaceInfo{
199
+ body: body, params: params, defaults: defaults, extends: interfaceExtendsFromHeader(header), exported: exported, pos: start, source: "text",
200
+ })
201
+ search = close + 1
202
+ }
203
+
204
+ enumRe := regexp.MustCompile(`(?m)^\s*(?:export\s+)?(?:declare\s+)?enum\s+([A-Za-z_$][\w$]*)\b`)
205
+ search = 0
206
+ for {
207
+ loc := enumRe.FindStringSubmatchIndex(text[search:])
208
+ if loc == nil {
209
+ break
210
+ }
211
+ start := search + loc[0]
212
+ name := text[search+loc[2] : search+loc[3]]
213
+ afterName := search + loc[1]
214
+ openRel := strings.IndexByte(text[afterName:], '{')
215
+ if openRel < 0 {
216
+ break
217
+ }
218
+ open := afterName + openRel
219
+ close := findBalanced(text, open, '{', '}')
220
+ if close < 0 {
221
+ search = open + 1
222
+ continue
223
+ }
224
+ body := strings.TrimSpace(text[open+1 : close])
225
+ info.enums[name] = enumInfo{name: name, values: enumValuesFromBody(body), pos: start}
226
+ search = close + 1
227
+ }
228
+ }
229
+
230
+ func collectAstDeclarations(info *fileInfo, reg *registry) {
231
+ var walk func(*shimast.Node)
232
+ walk = func(node *shimast.Node) {
233
+ if node == nil {
234
+ return
235
+ }
236
+ if node.Kind == shimast.KindClassDeclaration {
237
+ if class := classFromNode(info, node); class != nil {
238
+ info.classes = append(info.classes, class)
239
+ reg.classes[class.name] = class
240
+ }
241
+ } else if node.Kind == shimast.KindFunctionDeclaration {
242
+ if fn := functionFromNode(info.file, node); fn.name != "" {
243
+ info.functions[fn.name] = append(info.functions[fn.name], fn)
244
+ }
245
+ } else if node.Kind == shimast.KindTypeAliasDeclaration {
246
+ if alias := aliasFromNode(info.file, node); alias.body != "" && node.Name() != nil {
247
+ info.aliases[node.Name().Text()] = alias
248
+ }
249
+ } else if node.Kind == shimast.KindInterfaceDeclaration {
250
+ if node.Name() != nil {
251
+ name := node.Name().Text()
252
+ decl := interfaceFromNode(info.file, node)
253
+ if index := textInterfaceDeclarationNearIndex(info.interfaces[name], node.Pos()); index >= 0 {
254
+ info.interfaces[name][index] = decl
255
+ } else {
256
+ info.interfaces[name] = append(info.interfaces[name], decl)
257
+ }
258
+ }
259
+ }
260
+ node.ForEachChild(func(child *shimast.Node) bool {
261
+ walk(child)
262
+ return false
263
+ })
264
+ }
265
+ walk(info.file.AsNode())
266
+ }
267
+
268
+ func hasTextInterfaceDeclarationNear(decls []interfaceInfo, pos int) bool {
269
+ return textInterfaceDeclarationNearIndex(decls, pos) >= 0
270
+ }
271
+
272
+ func textInterfaceDeclarationNearIndex(decls []interfaceInfo, pos int) int {
273
+ for i, decl := range decls {
274
+ if decl.source == "text" && intAbs(decl.pos-pos) <= 64 {
275
+ return i
276
+ }
277
+ }
278
+ return -1
279
+ }
280
+
281
+ func intAbs(value int) int {
282
+ if value < 0 {
283
+ return -value
284
+ }
285
+ return value
286
+ }
287
+
288
+ func aliasFromNode(file *shimast.SourceFile, node *shimast.Node) aliasInfo {
289
+ params := []string{}
290
+ defaults := []string{}
291
+ for _, param := range node.TypeParameters() {
292
+ if param.Name() != nil {
293
+ params = append(params, param.Name().Text())
294
+ defaultText := ""
295
+ if defaultType := param.AsTypeParameterDeclaration().DefaultType; defaultType != nil {
296
+ defaultText = nodeText(file, defaultType)
297
+ }
298
+ defaults = append(defaults, defaultText)
299
+ }
300
+ }
301
+ return aliasInfo{
302
+ body: nodeText(file, node.Type()),
303
+ params: params,
304
+ defaults: defaults,
305
+ typeNode: node.Type(),
306
+ exported: node.ModifierFlags()&shimast.ModifierFlagsExport != 0,
307
+ pos: node.Pos(),
308
+ }
309
+ }
310
+
311
+ func interfaceFromNode(file *shimast.SourceFile, node *shimast.Node) interfaceInfo {
312
+ params := []string{}
313
+ defaults := []string{}
314
+ for _, param := range node.TypeParameters() {
315
+ if param.Name() == nil {
316
+ continue
317
+ }
318
+ params = append(params, param.Name().Text())
319
+ defaultText := ""
320
+ if defaultType := param.AsTypeParameterDeclaration().DefaultType; defaultType != nil {
321
+ defaultText = nodeText(file, defaultType)
322
+ }
323
+ defaults = append(defaults, defaultText)
324
+ }
325
+ return interfaceInfo{
326
+ body: interfaceBodyFromNode(file, node),
327
+ params: params,
328
+ defaults: defaults,
329
+ extends: interfaceExtendsFromNode(file, node),
330
+ properties: interfacePropertiesFromNode(file, node),
331
+ exported: node.ModifierFlags()&shimast.ModifierFlagsExport != 0,
332
+ pos: node.Pos(),
333
+ source: "ast",
334
+ }
335
+ }
336
+
337
+ func interfacePropertiesFromNode(file *shimast.SourceFile, node *shimast.Node) []utilityProperty {
338
+ props := []utilityProperty{}
339
+ for _, member := range node.Members() {
340
+ if member.Kind != shimast.KindPropertySignature {
341
+ continue
342
+ }
343
+ name := memberName(file, member)
344
+ if name == "" || member.Type() == nil {
345
+ continue
346
+ }
347
+ props = append(props, utilityProperty{
348
+ name: name,
349
+ typeText: nodeText(file, member.Type()),
350
+ typeNode: member.Type(),
351
+ optional: member.QuestionToken() != nil,
352
+ })
353
+ }
354
+ return props
355
+ }
356
+
357
+ func interfaceBodyFromNode(file *shimast.SourceFile, node *shimast.Node) string {
358
+ if name := node.Name(); name != nil {
359
+ text := file.Text()
360
+ start := name.End()
361
+ end := node.End()
362
+ if start >= 0 && start < len(text) && end > start && end <= len(text) {
363
+ openRel := strings.IndexByte(text[start:end], '{')
364
+ if openRel >= 0 {
365
+ open := start + openRel
366
+ close := findBalanced(text, open, '{', '}')
367
+ if close >= 0 && close <= end {
368
+ return strings.TrimSpace(text[open+1 : close])
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ members := []string{}
375
+ for _, member := range node.Members() {
376
+ members = append(members, nodeText(file, member))
377
+ }
378
+ return strings.Join(members, ";")
379
+ }
380
+
381
+ func interfaceExtendsFromNode(file *shimast.SourceFile, node *shimast.Node) []string {
382
+ name := node.Name()
383
+ if name == nil {
384
+ return nil
385
+ }
386
+ text := file.Text()
387
+ start := name.End()
388
+ end := node.End()
389
+ if start < 0 || start >= len(text) || end <= start || end > len(text) {
390
+ return nil
391
+ }
392
+ openRel := strings.IndexByte(text[start:end], '{')
393
+ if openRel < 0 {
394
+ return nil
395
+ }
396
+ return interfaceExtendsFromHeader(text[start : start+openRel])
397
+ }
398
+
399
+ func interfaceExtendsFromHeader(header string) []string {
400
+ header = strings.TrimSpace(stripTypeComments(header))
401
+ if strings.HasPrefix(header, "<") {
402
+ if end := findBalanced(header, 0, '<', '>'); end >= 0 {
403
+ header = strings.TrimSpace(header[end+1:])
404
+ }
405
+ }
406
+ idx := topLevelKeywordIndex(header, "extends")
407
+ if idx < 0 {
408
+ return nil
409
+ }
410
+ refs := []string{}
411
+ for _, part := range splitTop(header[idx+len("extends"):], ",") {
412
+ part = strings.TrimSpace(part)
413
+ if part != "" {
414
+ refs = append(refs, part)
415
+ }
416
+ }
417
+ return refs
418
+ }
419
+
420
+ func interfaceTypeParametersFromHeader(header string) ([]string, []string) {
421
+ header = strings.TrimSpace(stripTypeComments(header))
422
+ if !strings.HasPrefix(header, "<") {
423
+ return nil, nil
424
+ }
425
+ end := findBalanced(header, 0, '<', '>')
426
+ if end < 0 {
427
+ return nil, nil
428
+ }
429
+ params := []string{}
430
+ defaults := []string{}
431
+ for _, part := range splitTop(header[1:end], ",") {
432
+ part = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(part), "const "))
433
+ if part == "" {
434
+ continue
435
+ }
436
+ defaultText := ""
437
+ if index := topLevelEquals(part); index >= 0 {
438
+ defaultText = strings.TrimSpace(part[index+1:])
439
+ part = strings.TrimSpace(part[:index])
440
+ }
441
+ if index := topLevelKeywordIndex(part, "extends"); index >= 0 {
442
+ part = strings.TrimSpace(part[:index])
443
+ }
444
+ if !isIdentifierName(part) {
445
+ continue
446
+ }
447
+ params = append(params, part)
448
+ defaults = append(defaults, defaultText)
449
+ }
450
+ return params, defaults
451
+ }
452
+
453
+ func topLevelKeywordIndex(input string, keyword string) int {
454
+ depthAngle, depthBrace, depthParen, depthBracket, quote := 0, 0, 0, 0, rune(0)
455
+ for i, r := range input {
456
+ if quote != 0 {
457
+ if r == quote {
458
+ quote = 0
459
+ }
460
+ continue
461
+ }
462
+ switch r {
463
+ case '\'', '"', '`':
464
+ quote = r
465
+ case '<':
466
+ depthAngle++
467
+ case '>':
468
+ if depthAngle > 0 {
469
+ depthAngle--
470
+ }
471
+ case '{':
472
+ depthBrace++
473
+ case '}':
474
+ if depthBrace > 0 {
475
+ depthBrace--
476
+ }
477
+ case '(':
478
+ depthParen++
479
+ case ')':
480
+ if depthParen > 0 {
481
+ depthParen--
482
+ }
483
+ case '[':
484
+ depthBracket++
485
+ case ']':
486
+ if depthBracket > 0 {
487
+ depthBracket--
488
+ }
489
+ }
490
+ if depthAngle == 0 && depthBrace == 0 && depthParen == 0 && depthBracket == 0 && strings.HasPrefix(input[i:], keyword) {
491
+ beforeOK := i == 0 || !isIdent(input[i-1])
492
+ after := i + len(keyword)
493
+ afterOK := after >= len(input) || !isIdent(input[after])
494
+ if beforeOK && afterOK {
495
+ return i
496
+ }
497
+ }
498
+ }
499
+ return -1
500
+ }
501
+
502
+ func topLevelColon(input string) int {
503
+ return topLevelByteIndex(input, ':')
504
+ }
505
+
506
+ func topLevelEquals(input string) int {
507
+ return topLevelByteIndex(input, '=')
508
+ }
509
+
510
+ func topLevelByteIndex(input string, target byte) int {
511
+ depthAngle, depthBrace, depthParen, depthBracket, quote := 0, 0, 0, 0, rune(0)
512
+ for i, r := range input {
513
+ if quote != 0 {
514
+ if r == quote {
515
+ quote = 0
516
+ }
517
+ continue
518
+ }
519
+ switch r {
520
+ case '\'', '"', '`':
521
+ quote = r
522
+ case '<':
523
+ depthAngle++
524
+ case '>':
525
+ if depthAngle > 0 {
526
+ depthAngle--
527
+ }
528
+ case '{':
529
+ depthBrace++
530
+ case '}':
531
+ if depthBrace > 0 {
532
+ depthBrace--
533
+ }
534
+ case '(':
535
+ depthParen++
536
+ case ')':
537
+ if depthParen > 0 {
538
+ depthParen--
539
+ }
540
+ case '[':
541
+ depthBracket++
542
+ case ']':
543
+ if depthBracket > 0 {
544
+ depthBracket--
545
+ }
546
+ }
547
+ if depthAngle == 0 && depthBrace == 0 && depthParen == 0 && depthBracket == 0 && input[i] == target {
548
+ return i
549
+ }
550
+ }
551
+ return -1
552
+ }
553
+
554
+ func interfaceFullBody(info *fileInfo, reg *registry, decl interfaceInfo, seen map[string]bool) string {
555
+ if seen == nil {
556
+ seen = map[string]bool{}
557
+ }
558
+ key := info.moduleKey + "\x00" + strconv.Itoa(decl.pos)
559
+ if seen[key] {
560
+ return decl.body
561
+ }
562
+ seen[key] = true
563
+ parts := []string{}
564
+ for _, base := range decl.extends {
565
+ baseDecl, owner, _, ok := resolveInterfaceDeclRefAt(info, reg, base, decl.pos)
566
+ if !ok {
567
+ continue
568
+ }
569
+ baseDecl = instantiateInterfaceReference(baseDecl, base)
570
+ if body := strings.TrimSpace(interfaceFullBody(owner, reg, baseDecl, seen)); body != "" {
571
+ parts = append(parts, body)
572
+ }
573
+ }
574
+ if body := strings.TrimSpace(decl.body); body != "" {
575
+ parts = append(parts, body)
576
+ }
577
+ return strings.Join(parts, ";\n")
578
+ }
579
+
580
+ func interfaceFullProperties(info *fileInfo, reg *registry, decl interfaceInfo, seen map[string]bool) []utilityProperty {
581
+ if seen == nil {
582
+ seen = map[string]bool{}
583
+ }
584
+ key := info.moduleKey + "\x00" + strconv.Itoa(decl.pos)
585
+ if seen[key] {
586
+ return interfaceOwnProperties(info, decl)
587
+ }
588
+ seen[key] = true
589
+ props := []utilityProperty{}
590
+ for _, base := range decl.extends {
591
+ baseDecl, owner, _, ok := resolveInterfaceDeclRefAt(info, reg, base, decl.pos)
592
+ if ok {
593
+ baseDecl = instantiateInterfaceReference(baseDecl, base)
594
+ props = append(props, interfaceFullProperties(owner, reg, baseDecl, seen)...)
595
+ continue
596
+ }
597
+ if baseProps, _, ok := utilitySourceProperties(info, reg, base, &typeContext{seen: map[string]bool{}, pos: decl.pos}); ok {
598
+ props = append(props, baseProps...)
599
+ }
600
+ }
601
+ props = append(props, interfaceOwnProperties(info, decl)...)
602
+ return props
603
+ }
604
+
605
+ func interfaceOwnProperties(info *fileInfo, decl interfaceInfo) []utilityProperty {
606
+ if len(decl.properties) != 0 {
607
+ return withUtilityPropertyOwner(decl.properties, info)
608
+ }
609
+ return withUtilityPropertyOwner(propertiesFromBody(decl.body), info)
610
+ }
611
+
612
+ func instantiateInterfaceReference(decl interfaceInfo, reference string) interfaceInfo {
613
+ _, args, _ := generic(strings.TrimSpace(reference))
614
+ return instantiateInterfaceDecl(decl, args)
615
+ }
616
+
617
+ func instantiateInterfaceDecl(decl interfaceInfo, args []string) interfaceInfo {
618
+ if len(decl.params) == 0 {
619
+ return decl
620
+ }
621
+ replacements := map[string]string{}
622
+ parameterPatterns := []string{}
623
+ for index, param := range decl.params {
624
+ value := "unknown"
625
+ if index < len(args) && strings.TrimSpace(args[index]) != "" {
626
+ value = strings.TrimSpace(args[index])
627
+ } else if index < len(decl.defaults) && strings.TrimSpace(decl.defaults[index]) != "" {
628
+ value = strings.TrimSpace(decl.defaults[index])
629
+ }
630
+ replacements[param] = value
631
+ parameterPatterns = append(parameterPatterns, regexp.QuoteMeta(param))
632
+ }
633
+ parameterPattern := regexp.MustCompile(`\b(` + strings.Join(parameterPatterns, "|") + `)\b`)
634
+ replace := func(value string) string {
635
+ return parameterPattern.ReplaceAllStringFunc(value, func(param string) string { return replacements[param] })
636
+ }
637
+ decl.body = replace(decl.body)
638
+ decl.extends = append([]string(nil), decl.extends...)
639
+ for index := range decl.extends {
640
+ decl.extends[index] = replace(decl.extends[index])
641
+ }
642
+ decl.properties = append([]utilityProperty(nil), decl.properties...)
643
+ for index := range decl.properties {
644
+ decl.properties[index].typeText = replace(decl.properties[index].typeText)
645
+ // The AST node still contains the uninstantiated type parameter.
646
+ decl.properties[index].typeNode = nil
647
+ }
648
+ decl.params = nil
649
+ decl.defaults = nil
650
+ return decl
651
+ }
652
+
653
+ func interfaceImplementsExpr(info *fileInfo, reg *registry, decl interfaceInfo, ctx *typeContext) string {
654
+ items := []string{}
655
+ for _, base := range decl.extends {
656
+ if _, _, _, ok := resolveInterfaceDeclRefAt(info, reg, base, decl.pos); ok {
657
+ continue
658
+ }
659
+ items = append(items, typeExprCtx(info, reg, base, ctx))
660
+ }
661
+ return strings.Join(items, ", ")
662
+ }
663
+
664
+ func interfaceRefName(raw string) string {
665
+ raw = strings.TrimSpace(trimParens(raw))
666
+ if name, _, ok := generic(raw); ok {
667
+ raw = name
668
+ }
669
+ return strings.TrimSpace(raw)
670
+ }
671
+
672
+ func collectImports(info *fileInfo, reg *registry) {
673
+ text := info.file.Text()
674
+ re := regexp.MustCompile(`(?m)import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]`)
675
+ for _, match := range re.FindAllStringSubmatch(text, -1) {
676
+ target := resolveImport(info.file.FileName(), match[2], reg)
677
+ source := ""
678
+ if target != nil {
679
+ source = target.moduleKey
680
+ }
681
+ for _, item := range strings.Split(match[1], ",") {
682
+ item = strings.TrimSpace(item)
683
+ if item == "" {
684
+ continue
685
+ }
686
+ item = strings.TrimSpace(strings.TrimPrefix(item, "type "))
687
+ parts := regexp.MustCompile(`\s+as\s+`).Split(item, 2)
688
+ exportName := strings.TrimSpace(parts[0])
689
+ localName := exportName
690
+ if len(parts) == 2 {
691
+ localName = strings.TrimSpace(parts[1])
692
+ }
693
+ info.imports[localName] = importRef{source: source, exportName: exportName, spec: match[2]}
694
+ }
695
+ }
696
+ }
697
+
698
+ func collectReexports(info *fileInfo, reg *registry) {
699
+ text := info.file.Text()
700
+ starRe := regexp.MustCompile(`(?m)export\s+\*\s+from\s+['"]([^'"]+)['"]`)
701
+ for _, match := range starRe.FindAllStringSubmatch(text, -1) {
702
+ target := resolveImport(info.file.FileName(), match[1], reg)
703
+ if target == nil {
704
+ continue
705
+ }
706
+ info.exportStar = append(info.exportStar, importRef{source: target.moduleKey, exportName: "*", spec: match[1]})
707
+ }
708
+
709
+ namedRe := regexp.MustCompile(`(?m)export\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]`)
710
+ for _, match := range namedRe.FindAllStringSubmatch(text, -1) {
711
+ target := resolveImport(info.file.FileName(), match[2], reg)
712
+ if target == nil {
713
+ continue
714
+ }
715
+ for _, item := range strings.Split(match[1], ",") {
716
+ item = strings.TrimSpace(item)
717
+ if item == "" {
718
+ continue
719
+ }
720
+ item = strings.TrimSpace(strings.TrimPrefix(item, "type "))
721
+ parts := regexp.MustCompile(`\s+as\s+`).Split(item, 2)
722
+ exportName := strings.TrimSpace(parts[0])
723
+ localName := exportName
724
+ if len(parts) == 2 {
725
+ localName = strings.TrimSpace(parts[1])
726
+ }
727
+ info.reexports[localName] = importRef{source: target.moduleKey, exportName: exportName, spec: match[2]}
728
+ }
729
+ }
730
+
731
+ localNamedRe := regexp.MustCompile(`(?m)export\s+(?:type\s+)?\{([^}]+)\}\s*(?:;|$)`)
732
+ for _, match := range localNamedRe.FindAllStringSubmatch(text, -1) {
733
+ for _, item := range strings.Split(match[1], ",") {
734
+ localName, exportName, ok := parseExportItem(item)
735
+ if !ok {
736
+ continue
737
+ }
738
+ if ref, ok := info.imports[localName]; ok {
739
+ info.reexports[exportName] = ref
740
+ continue
741
+ }
742
+ if exportName != localName {
743
+ info.reexports[exportName] = importRef{source: info.moduleKey, exportName: localName}
744
+ }
745
+ }
746
+ }
747
+ }
748
+
749
+ func parseExportItem(item string) (string, string, bool) {
750
+ item = strings.TrimSpace(item)
751
+ if item == "" {
752
+ return "", "", false
753
+ }
754
+ item = strings.TrimSpace(strings.TrimPrefix(item, "type "))
755
+ parts := regexp.MustCompile(`\s+as\s+`).Split(item, 2)
756
+ localName := strings.TrimSpace(parts[0])
757
+ exportName := localName
758
+ if len(parts) == 2 {
759
+ exportName = strings.TrimSpace(parts[1])
760
+ }
761
+ if localName == "" || exportName == "" {
762
+ return "", "", false
763
+ }
764
+ return localName, exportName, true
765
+ }
766
+
767
+ func collectGenericCalls(info *fileInfo, reg *registry) {
768
+ calls := []callInfo{}
769
+ seen := map[int]bool{}
770
+ var walk func(*shimast.Node)
771
+ walk = func(node *shimast.Node) {
772
+ if node == nil {
773
+ return
774
+ }
775
+ if node.Kind == shimast.KindCallExpression {
776
+ call := node.AsCallExpression()
777
+ if received, ok := resolvedReceiveTypeCall(info, reg, node); ok {
778
+ calls = append(calls, received)
779
+ seen[node.Pos()] = true
780
+ }
781
+ typeArgs := node.TypeArguments()
782
+ if len(typeArgs) > 0 && !seen[node.Pos()] {
783
+ name, metadataArgIndex, recognized := metadataCallDetails(reg, call)
784
+ if recognized {
785
+ argumentCount := 0
786
+ if call.Arguments != nil {
787
+ argumentCount = len(call.Arguments.Nodes)
788
+ }
789
+ if argumentCount <= metadataArgIndex {
790
+ calls = append(calls, callInfo{
791
+ name: name,
792
+ nodePos: node.Pos(),
793
+ metadataArgIndex: metadataArgIndex,
794
+ typeText: nodeText(info.file, typeArgs[0]),
795
+ typeNode: typeArgs[0],
796
+ preferTypia: true,
797
+ pos: node.Pos(),
798
+ })
799
+ }
800
+ }
801
+ }
802
+ }
803
+ node.ForEachChild(func(child *shimast.Node) bool {
804
+ walk(child)
805
+ return false
806
+ })
807
+ }
808
+ walk(info.file.AsNode())
809
+ fallbackCalls := collectReceiveTypeCalls(info, reg)
810
+ matchCallNodePositions(info.file, fallbackCalls)
811
+ for _, call := range fallbackCalls {
812
+ if call.nodePos >= 0 && seen[call.nodePos] {
813
+ continue
814
+ }
815
+ calls = append(calls, call)
816
+ if call.nodePos >= 0 {
817
+ seen[call.nodePos] = true
818
+ }
819
+ }
820
+ sort.Slice(calls, func(i, j int) bool { return calls[i].pos < calls[j].pos })
821
+ matchCallNodePositions(info.file, calls)
822
+ info.calls = calls
823
+ }
824
+
825
+ func isMetadataCallName(name string) bool {
826
+ switch name {
827
+ case "deserialize", "validate", "validatedDeserialize", "typeOf":
828
+ return true
829
+ default:
830
+ return false
831
+ }
832
+ }
833
+
834
+ func metadataCallDetails(reg *registry, call *shimast.CallExpression) (string, int, bool) {
835
+ if reg == nil || reg.checker == nil || call == nil {
836
+ return "", 0, false
837
+ }
838
+ signature := reg.checker.GetResolvedSignature(call.AsNode())
839
+ if signature == nil {
840
+ return "", 0, false
841
+ }
842
+ declaration := signature.Declaration()
843
+ if declaration == nil {
844
+ return "", 0, false
845
+ }
846
+ source := shimast.GetSourceFileOfNode(declaration)
847
+ if source == nil || !isReflectionMetadataDeclarationFile(source.FileName()) {
848
+ return "", 0, false
849
+ }
850
+ name := ""
851
+ if declaration.Name() != nil {
852
+ name = declaration.Name().Text()
853
+ }
854
+ if name == "" {
855
+ if typ := reg.checker.GetTypeAtLocation(declaration); typ != nil && typ.Symbol() != nil {
856
+ name = typ.Symbol().Name
857
+ }
858
+ }
859
+ switch name {
860
+ case "typeOf":
861
+ return name, 0, true
862
+ case "deserialize", "validate":
863
+ return name, 1, true
864
+ case "assert", "is":
865
+ return name, 2, true
866
+ case "cast", "validatedDeserialize":
867
+ return name, 4, true
868
+ default:
869
+ return "", 0, false
870
+ }
871
+ }
872
+
873
+ func isReflectionMetadataDeclarationFile(fileName string) bool {
874
+ slash := filepath.ToSlash(fileName)
875
+ isReflectionPackage := strings.Contains(slash, "/ts-reflection/") || strings.Contains(slash, "/packages/reflection/")
876
+ isLegacyFoundationPackage := strings.Contains(slash, "/ts-server-foundation/")
877
+ if !isReflectionPackage && !isLegacyFoundationPackage {
878
+ return false
879
+ }
880
+ for _, suffix := range []string{
881
+ "/src/reflection/conversion.ts",
882
+ "/src/reflection/conversion.d.ts",
883
+ "/dist/reflection/conversion.d.ts",
884
+ "/dist/src/reflection/conversion.d.ts",
885
+ "/src/reflection/reflection-class.ts",
886
+ "/src/reflection/reflection-class.d.ts",
887
+ "/dist/reflection/reflection-class.d.ts",
888
+ "/dist/src/reflection/reflection-class.d.ts",
889
+ } {
890
+ if strings.HasSuffix(slash, suffix) {
891
+ return true
892
+ }
893
+ }
894
+ return false
895
+ }
896
+
897
+ func matchCallNodePositions(file *shimast.SourceFile, calls []callInfo) {
898
+ if file == nil || len(calls) == 0 {
899
+ return
900
+ }
901
+ type candidate struct {
902
+ pos int
903
+ end int
904
+ start int
905
+ }
906
+ candidates := []candidate{}
907
+ var walk func(*shimast.Node)
908
+ walk = func(node *shimast.Node) {
909
+ if node == nil {
910
+ return
911
+ }
912
+ if node.Kind == shimast.KindCallExpression {
913
+ call := node.AsCallExpression()
914
+ start := node.Pos()
915
+ end := node.End()
916
+ if call != nil && call.Expression != nil {
917
+ start = call.Expression.Pos()
918
+ end = call.Expression.End()
919
+ }
920
+ candidates = append(candidates, candidate{pos: node.Pos(), start: start, end: end})
921
+ }
922
+ node.ForEachChild(func(child *shimast.Node) bool {
923
+ walk(child)
924
+ return false
925
+ })
926
+ }
927
+ walk(file.AsNode())
928
+ for index := range calls {
929
+ if calls[index].nodePos >= 0 {
930
+ continue
931
+ }
932
+ bestSpan := 0
933
+ for _, item := range candidates {
934
+ if item.start <= calls[index].pos && calls[index].pos < item.end {
935
+ span := item.end - item.start
936
+ if bestSpan == 0 || span < bestSpan {
937
+ calls[index].nodePos = item.pos
938
+ bestSpan = span
939
+ }
940
+ }
941
+ }
942
+ }
943
+ }
944
+
945
+ func isFoundationCompatibilityImport(ref importRef) bool {
946
+ if ref.spec == foundationPackageSpec || ref.spec == reflectionPackageSpec {
947
+ return true
948
+ }
949
+ source := filepath.ToSlash(ref.source)
950
+ if strings.Contains(source, "/packages/reflection/src/") {
951
+ return strings.HasSuffix(source, "/src/index") ||
952
+ strings.HasSuffix(source, "/src/types/index") ||
953
+ strings.HasSuffix(source, "/src/reflection/index") ||
954
+ strings.HasSuffix(source, "/src/reflection/conversion")
955
+ }
956
+ if !strings.Contains(source, "/ts-server-foundation/src/") {
957
+ return false
958
+ }
959
+ return strings.HasSuffix(source, "/src/index") ||
960
+ strings.HasSuffix(source, "/src/types/index") ||
961
+ strings.HasSuffix(source, "/src/reflection/index") ||
962
+ strings.HasSuffix(source, "/src/reflection/conversion")
963
+ }