@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,912 @@
1
+ package main
2
+
3
+ import (
4
+ "io/fs"
5
+ "os"
6
+ "path/filepath"
7
+ "regexp"
8
+ "sort"
9
+ "strconv"
10
+ "strings"
11
+
12
+ shimast "github.com/microsoft/typescript-go/shim/ast"
13
+ )
14
+
15
+ type sourceCallInfo struct {
16
+ name string
17
+ receiver string
18
+ typeArgs []string
19
+ args []string
20
+ pos int
21
+ }
22
+
23
+ func resolvedReceiveTypeCall(info *fileInfo, reg *registry, node *shimast.Node) (callInfo, bool) {
24
+ if info == nil || info.file == nil || reg == nil || reg.checker == nil || node == nil || node.Kind != shimast.KindCallExpression {
25
+ return callInfo{}, false
26
+ }
27
+ call := node.AsCallExpression()
28
+ signature := reg.checker.GetResolvedSignature(node)
29
+ if signature == nil {
30
+ return callInfo{}, false
31
+ }
32
+ declaration := signature.Declaration()
33
+ if declaration == nil {
34
+ return callInfo{}, false
35
+ }
36
+ declarationFile := shimast.GetSourceFileOfNode(declaration)
37
+ if declarationFile == nil {
38
+ return callInfo{}, false
39
+ }
40
+ name := "ReceiveType"
41
+ if declaration.Name() != nil {
42
+ // A resolved signature can be declared by a computed method, whose name
43
+ // node cannot be converted to text by the TypeScript Go AST API.
44
+ name = nodeText(declarationFile, declaration.Name().AsNode())
45
+ }
46
+ fn := functionInfo{
47
+ name: name,
48
+ typeParams: typeParameterNames(declaration),
49
+ params: paramsFromNode(declarationFile, declaration),
50
+ pos: declaration.Pos(),
51
+ }
52
+ receiveTypeText := ""
53
+ if _, text, ok := receiveTypeParameter(fn); ok {
54
+ receiveTypeText = unwrapReceiveTypeHelperType(text)
55
+ } else {
56
+ return callInfo{}, false
57
+ }
58
+ sourceCall := sourceCallInfo{name: name, pos: node.Pos()}
59
+ for _, typeArg := range node.TypeArguments() {
60
+ sourceCall.typeArgs = append(sourceCall.typeArgs, nodeText(info.file, typeArg))
61
+ }
62
+ if call.Arguments != nil {
63
+ for _, arg := range call.Arguments.Nodes {
64
+ sourceCall.args = append(sourceCall.args, nodeText(info.file, arg))
65
+ }
66
+ }
67
+ typeText, metadataArgIndex, ok := receiveTypeForCall(info, reg, fn, sourceCall)
68
+ if !ok {
69
+ return callInfo{}, false
70
+ }
71
+ var typeNode *shimast.Node
72
+ for index, typeParam := range fn.typeParams {
73
+ if receiveTypeText == typeParam && index < len(node.TypeArguments()) {
74
+ typeNode = node.TypeArguments()[index]
75
+ break
76
+ }
77
+ }
78
+ return callInfo{
79
+ name: name,
80
+ nodePos: node.Pos(),
81
+ metadataArgIndex: metadataArgIndex,
82
+ typeText: typeText,
83
+ typeNode: typeNode,
84
+ preferTypia: typeNode != nil,
85
+ pos: node.Pos(),
86
+ }, true
87
+ }
88
+
89
+ func collectReceiveTypeCalls(info *fileInfo, reg *registry) []callInfo {
90
+ text := info.file.Text()
91
+ out := []callInfo{}
92
+ seen := map[int]bool{}
93
+ for name, fns := range receiveTypeFunctionCandidates(info, reg) {
94
+ if len(fns) == 0 {
95
+ continue
96
+ }
97
+ for _, call := range sourceCalls(text, name) {
98
+ if seen[call.pos] {
99
+ continue
100
+ }
101
+ for _, fn := range fns {
102
+ typeText, metadataArgIndex, ok := receiveTypeForCall(info, reg, fn, call)
103
+ if !ok {
104
+ continue
105
+ }
106
+ out = append(out, callInfo{name: name, nodePos: -1, metadataArgIndex: metadataArgIndex, typeText: typeText, pos: call.pos})
107
+ seen[call.pos] = true
108
+ break
109
+ }
110
+ }
111
+ }
112
+ for name, fns := range receiveTypeMethodCandidates(reg) {
113
+ if len(fns) == 0 {
114
+ continue
115
+ }
116
+ for _, call := range sourceMethodCalls(text, name) {
117
+ if seen[call.pos] {
118
+ continue
119
+ }
120
+ typeText, metadataArgIndex, ok := receiveTypeMethodForCall(info, reg, fns, call)
121
+ if ok {
122
+ out = append(out, callInfo{name: name, nodePos: -1, metadataArgIndex: metadataArgIndex, typeText: typeText, pos: call.pos})
123
+ seen[call.pos] = true
124
+ }
125
+ }
126
+ }
127
+ return out
128
+ }
129
+
130
+ func receiveTypeFunctionCandidates(info *fileInfo, reg *registry) map[string][]functionInfo {
131
+ out := map[string][]functionInfo{}
132
+ for name, fns := range info.functions {
133
+ if receive := receiveTypeFunctionInfos(fns); len(receive) > 0 {
134
+ out[name] = append(out[name], receive...)
135
+ }
136
+ }
137
+ for localName, ref := range info.imports {
138
+ target := reg.byPath[ref.source]
139
+ if target != nil {
140
+ fns := target.functions[ref.exportName]
141
+ if receive := receiveTypeFunctionInfos(fns); len(receive) > 0 {
142
+ out[localName] = append(out[localName], receive...)
143
+ }
144
+ continue
145
+ }
146
+ if receive := externalReceiveTypeFunctionInfos(info.file.FileName(), ref.spec, ref.exportName, reg); len(receive) > 0 {
147
+ out[localName] = append(out[localName], receive...)
148
+ }
149
+ }
150
+ return out
151
+ }
152
+
153
+ func receiveTypeFunctionInfos(fns []functionInfo) []functionInfo {
154
+ out := []functionInfo{}
155
+ for _, fn := range fns {
156
+ if _, _, ok := receiveTypeParameter(fn); ok {
157
+ out = append(out, fn)
158
+ }
159
+ }
160
+ return out
161
+ }
162
+
163
+ func externalReceiveTypeFunctionInfos(fromFile string, spec string, exportName string, reg *registry) []functionInfo {
164
+ functions := externalFunctionInfos(fromFile, spec, reg)
165
+ if len(functions) == 0 {
166
+ return nil
167
+ }
168
+ return receiveTypeFunctionInfos(functions[exportName])
169
+ }
170
+
171
+ func externalFunctionInfos(fromFile string, spec string, reg *registry) map[string][]functionInfo {
172
+ root := externalPackageRoot(fromFile, spec, reg)
173
+ if root == "" {
174
+ return nil
175
+ }
176
+ if cached, ok := reg.external[root]; ok {
177
+ return cached
178
+ }
179
+ functions := map[string][]functionInfo{}
180
+ _ = filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
181
+ if err != nil {
182
+ return nil
183
+ }
184
+ name := entry.Name()
185
+ if entry.IsDir() {
186
+ switch name {
187
+ case ".git", ".yarn", "node_modules", "coverage":
188
+ return filepath.SkipDir
189
+ }
190
+ return nil
191
+ }
192
+ slash := filepath.ToSlash(path)
193
+ if !(strings.HasSuffix(slash, ".ts") || strings.HasSuffix(slash, ".d.ts")) || strings.HasSuffix(slash, ".js") {
194
+ return nil
195
+ }
196
+ textBytes, err := os.ReadFile(path)
197
+ if err != nil {
198
+ return nil
199
+ }
200
+ for _, fn := range exportedFunctionsFromText(string(textBytes)) {
201
+ functions[fn.name] = append(functions[fn.name], fn)
202
+ }
203
+ return nil
204
+ })
205
+ reg.external[root] = functions
206
+ return functions
207
+ }
208
+
209
+ func externalPackageRoot(fromFile string, spec string, reg *registry) string {
210
+ if strings.HasPrefix(spec, ".") || strings.HasPrefix(spec, "/") {
211
+ return ""
212
+ }
213
+ pkg := packageNameFromSpec(spec)
214
+ if pkg == "" {
215
+ return ""
216
+ }
217
+ if reg != nil {
218
+ if root := reg.externalPackageRoots[pkg]; root != "" {
219
+ if stat, err := os.Stat(root); err == nil && stat.IsDir() {
220
+ return filepath.Clean(root)
221
+ }
222
+ }
223
+ }
224
+ dir := filepath.Dir(fromFile)
225
+ for {
226
+ candidate := filepath.Join(dir, "node_modules", filepath.FromSlash(pkg))
227
+ if stat, err := os.Stat(candidate); err == nil && stat.IsDir() {
228
+ if real, err := filepath.EvalSymlinks(candidate); err == nil {
229
+ return filepath.Clean(real)
230
+ }
231
+ return filepath.Clean(candidate)
232
+ }
233
+ parent := filepath.Dir(dir)
234
+ if parent == dir {
235
+ return ""
236
+ }
237
+ dir = parent
238
+ }
239
+ }
240
+
241
+ func packageNameFromSpec(spec string) string {
242
+ parts := strings.Split(filepath.ToSlash(spec), "/")
243
+ if len(parts) == 0 || parts[0] == "" {
244
+ return ""
245
+ }
246
+ if strings.HasPrefix(parts[0], "@") {
247
+ if len(parts) < 2 || parts[1] == "" {
248
+ return ""
249
+ }
250
+ return parts[0] + "/" + parts[1]
251
+ }
252
+ return parts[0]
253
+ }
254
+
255
+ func exportedFunctionsFromText(text string) []functionInfo {
256
+ out := []functionInfo{}
257
+ search := 0
258
+ for search < len(text) {
259
+ idx := strings.Index(text[search:], "function")
260
+ if idx < 0 {
261
+ break
262
+ }
263
+ start := search + idx
264
+ after := start + len("function")
265
+ if start > 0 && isIdent(text[start-1]) || after < len(text) && isIdent(text[after]) {
266
+ search = after
267
+ continue
268
+ }
269
+ if !isExportedFunctionPosition(text, start) {
270
+ search = after
271
+ continue
272
+ }
273
+ pos := skipSpace(text, after)
274
+ nameStart, ok := scanIdentifierRight(text, pos)
275
+ if !ok {
276
+ search = after
277
+ continue
278
+ }
279
+ name := text[pos:nameStart]
280
+ pos = skipSpace(text, nameStart)
281
+ typeParams := []string{}
282
+ if pos < len(text) && text[pos] == '<' {
283
+ end := findBalanced(text, pos, '<', '>')
284
+ if end < 0 {
285
+ search = pos + 1
286
+ continue
287
+ }
288
+ typeParams = typeParameterNamesFromText(text[pos+1 : end])
289
+ pos = skipSpace(text, end+1)
290
+ }
291
+ if pos >= len(text) || text[pos] != '(' {
292
+ search = nameStart
293
+ continue
294
+ }
295
+ close := findBalanced(text, pos, '(', ')')
296
+ if close < 0 {
297
+ search = pos + 1
298
+ continue
299
+ }
300
+ out = append(out, functionInfo{name: name, typeParams: typeParams, params: paramsFromText(text[pos+1 : close]), pos: start})
301
+ search = close + 1
302
+ }
303
+ return out
304
+ }
305
+
306
+ func isExportedFunctionPosition(text string, start int) bool {
307
+ lineStart := strings.LastIndexAny(text[:start], "\r\n")
308
+ if lineStart < 0 {
309
+ lineStart = 0
310
+ } else {
311
+ lineStart++
312
+ }
313
+ prefix := strings.TrimSpace(text[lineStart:start])
314
+ return regexp.MustCompile(`(?:^|\b)export(?:\s+declare)?\s*$`).MatchString(prefix)
315
+ }
316
+
317
+ func scanIdentifierRight(text string, pos int) (int, bool) {
318
+ if pos >= len(text) || !isIdent(text[pos]) || text[pos] >= '0' && text[pos] <= '9' {
319
+ return 0, false
320
+ }
321
+ for pos < len(text) && isIdent(text[pos]) {
322
+ pos++
323
+ }
324
+ return pos, true
325
+ }
326
+
327
+ func typeParameterNamesFromText(raw string) []string {
328
+ params := []string{}
329
+ for _, part := range splitTop(raw, ",") {
330
+ part = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(part), "const "))
331
+ if part == "" {
332
+ continue
333
+ }
334
+ name := part
335
+ for _, marker := range []string{" extends ", " = ", " "} {
336
+ if idx := strings.Index(name, marker); idx >= 0 {
337
+ name = strings.TrimSpace(name[:idx])
338
+ }
339
+ }
340
+ if isIdentifierName(name) {
341
+ params = append(params, name)
342
+ }
343
+ }
344
+ return params
345
+ }
346
+
347
+ func paramsFromText(raw string) []paramInfo {
348
+ params := []paramInfo{}
349
+ for _, part := range splitTop(raw, ",") {
350
+ part = strings.TrimSpace(part)
351
+ if part == "" {
352
+ continue
353
+ }
354
+ colon := topLevelColon(part)
355
+ if colon < 0 {
356
+ continue
357
+ }
358
+ name := strings.TrimSpace(strings.TrimPrefix(part[:colon], "..."))
359
+ optional := strings.HasSuffix(name, "?")
360
+ name = strings.TrimSuffix(name, "?")
361
+ typeText := strings.TrimSpace(part[colon+1:])
362
+ hasDefault := false
363
+ if eq := topLevelEquals(typeText); eq >= 0 {
364
+ typeText = strings.TrimSpace(typeText[:eq])
365
+ hasDefault = true
366
+ }
367
+ params = append(params, paramInfo{name: name, typeText: typeText, optional: optional || hasDefault, hasDefault: hasDefault})
368
+ }
369
+ return params
370
+ }
371
+
372
+ func receiveTypeMethodCandidates(reg *registry) map[string][]functionInfo {
373
+ out := map[string][]functionInfo{}
374
+ for _, info := range reg.files {
375
+ for _, class := range info.classes {
376
+ methods := append(append([]methodInfo(nil), class.methods...), class.staticMethods...)
377
+ for _, method := range methods {
378
+ fn := functionInfo{name: method.name, owner: class.name, typeParams: method.typeParams, params: method.params, pos: class.pos}
379
+ if _, _, ok := receiveTypeParameter(fn); ok {
380
+ out[method.name] = append(out[method.name], fn)
381
+ }
382
+ }
383
+ }
384
+ }
385
+ return out
386
+ }
387
+
388
+ func receiveTypeParameter(fn functionInfo) (int, string, bool) {
389
+ if len(fn.params) == 0 {
390
+ return 0, "", false
391
+ }
392
+ index := len(fn.params) - 1
393
+ typeText, ok := receiveTypeArgument(fn.params[index].typeText)
394
+ return index, typeText, ok
395
+ }
396
+
397
+ func receiveTypeArgument(raw string) (string, bool) {
398
+ raw = strings.TrimSpace(trimParens(raw))
399
+ name, args, ok := generic(raw)
400
+ if !ok || name != "ReceiveType" && !strings.HasSuffix(name, ".ReceiveType") || len(args) == 0 {
401
+ return "", false
402
+ }
403
+ return firstArg(args), true
404
+ }
405
+
406
+ func receiveTypeForCall(info *fileInfo, reg *registry, fn functionInfo, call sourceCallInfo) (string, int, bool) {
407
+ paramIndex, typeText, ok := receiveTypeParameter(fn)
408
+ if !ok || len(call.args) >= paramIndex+1 {
409
+ return "", 0, false
410
+ }
411
+ substitutions := map[string]string{}
412
+ for i, name := range fn.typeParams {
413
+ if i < len(call.typeArgs) {
414
+ substitutions[name] = call.typeArgs[i]
415
+ }
416
+ }
417
+ for i := 0; i < paramIndex && i < len(call.args); i++ {
418
+ for _, typeParam := range fn.typeParams {
419
+ if substitutions[typeParam] != "" || !typeTextContainsTypeParameter(fn.params[i].typeText, typeParam) {
420
+ continue
421
+ }
422
+ if inferred, ok := inferTypeParameterFromArgument(fn.params[i].typeText, typeParam, call.args[i]); ok {
423
+ substitutions[typeParam] = inferred
424
+ }
425
+ }
426
+ }
427
+ for _, typeParam := range fn.typeParams {
428
+ if replacement := substitutions[typeParam]; replacement != "" {
429
+ typeText = replaceTypeParameter(typeText, typeParam, replacement)
430
+ }
431
+ }
432
+ typeText = unwrapReceiveTypeHelperType(typeText)
433
+ if hasUnresolvedTypeParameters(typeText, fn.typeParams) {
434
+ availableArgs := min(len(call.args), paramIndex)
435
+ if inferred, ok := uniqueTypedFunctionArgumentParameter(info, call.args[:availableArgs], call.pos); ok {
436
+ return inferred, paramIndex, true
437
+ }
438
+ return "", 0, false
439
+ }
440
+ if !receiveTypeMetadataResolvable(info, reg, typeText) {
441
+ availableArgs := min(len(call.args), paramIndex)
442
+ if inferred, ok := uniqueTypedFunctionArgumentParameter(info, call.args[:availableArgs], call.pos); ok {
443
+ return inferred, paramIndex, true
444
+ }
445
+ }
446
+ return typeText, paramIndex, true
447
+ }
448
+
449
+ func receiveTypeMethodForCall(info *fileInfo, reg *registry, fns []functionInfo, call sourceCallInfo) (string, int, bool) {
450
+ if owner, ok := receiverClassName(info, reg, call); ok {
451
+ fns = methodCandidatesForOwner(fns, owner)
452
+ if len(fns) == 0 {
453
+ return "", 0, false
454
+ }
455
+ }
456
+ type match struct {
457
+ typeText string
458
+ metadataArgIndex int
459
+ }
460
+ matches := []match{}
461
+ for _, fn := range fns {
462
+ typeText, metadataArgIndex, ok := receiveTypeForCall(info, reg, fn, call)
463
+ if ok {
464
+ matches = append(matches, match{typeText: typeText, metadataArgIndex: metadataArgIndex})
465
+ }
466
+ }
467
+ if len(matches) == 1 {
468
+ return matches[0].typeText, matches[0].metadataArgIndex, true
469
+ }
470
+ if len(matches) == 0 {
471
+ return "", 0, false
472
+ }
473
+ first := matches[0]
474
+ for _, next := range matches[1:] {
475
+ if next.typeText != first.typeText || next.metadataArgIndex != first.metadataArgIndex {
476
+ return "", 0, false
477
+ }
478
+ }
479
+ return first.typeText, first.metadataArgIndex, true
480
+ }
481
+
482
+ func methodCandidatesForOwner(fns []functionInfo, owner string) []functionInfo {
483
+ out := []functionInfo{}
484
+ for _, fn := range fns {
485
+ if fn.owner == owner {
486
+ out = append(out, fn)
487
+ }
488
+ }
489
+ return out
490
+ }
491
+
492
+ func typeTextContainsTypeParameter(raw string, typeParam string) bool {
493
+ return regexp.MustCompile(`\b` + regexp.QuoteMeta(typeParam) + `\b`).MatchString(raw)
494
+ }
495
+
496
+ func inferTypeParameterFromArgument(paramType string, typeParam string, arg string) (string, bool) {
497
+ if strings.TrimSpace(trimParens(paramType)) == typeParam {
498
+ return argumentTypeText(arg)
499
+ }
500
+ if !typeTextContainsTypeParameter(paramType, typeParam) {
501
+ return "", false
502
+ }
503
+ return firstFunctionParameterType(arg)
504
+ }
505
+
506
+ func argumentTypeText(raw string) (string, bool) {
507
+ raw = strings.TrimSpace(raw)
508
+ if raw == "true" || raw == "false" || raw == "null" || raw == "undefined" {
509
+ return raw, true
510
+ }
511
+ if strings.HasPrefix(raw, "'") || strings.HasPrefix(raw, "\"") || strings.HasPrefix(raw, "`") {
512
+ return raw, true
513
+ }
514
+ if _, err := strconv.ParseFloat(raw, 64); err == nil {
515
+ return raw, true
516
+ }
517
+ return firstFunctionParameterType(raw)
518
+ }
519
+
520
+ func unwrapReceiveTypeHelperType(raw string) string {
521
+ for {
522
+ raw = strings.TrimSpace(trimParens(raw))
523
+ name, args, ok := generic(raw)
524
+ if !ok || len(args) == 0 {
525
+ return raw
526
+ }
527
+ if name != "NoInfer" {
528
+ return raw
529
+ }
530
+ raw = firstArg(args)
531
+ }
532
+ }
533
+
534
+ func receiveTypeMetadataResolvable(info *fileInfo, reg *registry, raw string) bool {
535
+ raw = unwrapReceiveTypeHelperType(raw)
536
+ name, args, ok := generic(raw)
537
+ if !ok {
538
+ return !isUnsupportedTypeSyntax(raw)
539
+ }
540
+ if alias, owner, _, ok := resolveAliasRef(info, reg, name); ok && len(alias.params) > 0 {
541
+ body := alias.body
542
+ for i := range alias.params {
543
+ body = replaceTypeParameter(body, aliasParamName(alias, i), aliasArg(alias, args, i))
544
+ }
545
+ if shouldUseTypiaTypeCtx(owner, reg, body, map[string]bool{}) {
546
+ return true
547
+ }
548
+ return !isUnsupportedTypeSyntax(body)
549
+ }
550
+ switch name {
551
+ case "Array", "ReadonlyArray", "Promise", "NoInfer", "NonNullable", "ApiResponse",
552
+ "HttpBody", "HttpQueries", "HttpQuery", "HttpPath", "HttpHeader",
553
+ "ApiName", "ApiType", "MinLength", "MaxLength", "Minimum", "GreaterThan", "Maximum", "LessThan", "Pattern",
554
+ "Validate", "DatabaseField", "MySQL", "Reference", "Index", "Indexed", "Unique", "PrimaryKey",
555
+ "AutoIncrement", "TypeAnnotation", "Record", "EntityFields", "EntityOptionals",
556
+ "NewEntityFields", "Pick", "Omit", "Partial", "Required", "Extract":
557
+ return true
558
+ default:
559
+ if ref, ok := info.imports[name]; ok && isExternalImportRef(ref) {
560
+ return true
561
+ }
562
+ return false
563
+ }
564
+ }
565
+
566
+ func uniqueTypedFunctionArgumentParameter(info *fileInfo, args []string, pos int) (string, bool) {
567
+ found := ""
568
+ for _, arg := range args {
569
+ typeText, ok := functionArgumentParameterType(info, arg, pos)
570
+ if !ok {
571
+ continue
572
+ }
573
+ if found != "" {
574
+ return "", false
575
+ }
576
+ found = typeText
577
+ }
578
+ return found, found != ""
579
+ }
580
+
581
+ func functionArgumentParameterType(info *fileInfo, raw string, pos int) (string, bool) {
582
+ if typeText, ok := firstFunctionParameterType(raw); ok {
583
+ return typeText, true
584
+ }
585
+ raw = strings.TrimSpace(raw)
586
+ if !isIdentifierName(raw) {
587
+ return "", false
588
+ }
589
+ for _, fn := range localFunctionsBefore(info.functions[raw], pos) {
590
+ if len(fn.params) > 0 {
591
+ return fn.params[0].typeText, fn.params[0].typeText != ""
592
+ }
593
+ }
594
+ return localFunctionVariableParameterType(info.file.Text(), raw, pos)
595
+ }
596
+
597
+ func localFunctionsBefore(fns []functionInfo, pos int) []functionInfo {
598
+ out := []functionInfo{}
599
+ for _, fn := range fns {
600
+ if fn.pos <= pos {
601
+ out = append(out, fn)
602
+ }
603
+ }
604
+ sort.SliceStable(out, func(i, j int) bool { return out[i].pos > out[j].pos })
605
+ return out
606
+ }
607
+
608
+ func localFunctionVariableParameterType(text string, name string, pos int) (string, bool) {
609
+ if pos > len(text) {
610
+ pos = len(text)
611
+ }
612
+ prefix := text[:pos]
613
+ ident := regexp.QuoteMeta(name)
614
+ for _, pattern := range []string{
615
+ `(?:^|[^A-Za-z0-9_$])(?:const|let|var)\s+` + ident + `\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>`,
616
+ `(?:^|[^A-Za-z0-9_$])(?:const|let|var)\s+` + ident + `\s*:\s*\(([^)]*)\)\s*=>`,
617
+ } {
618
+ if params, ok := lastCapture(prefix, pattern); ok {
619
+ return firstParameterType(params)
620
+ }
621
+ }
622
+ return "", false
623
+ }
624
+
625
+ func sourceCalls(text string, name string) []sourceCallInfo {
626
+ out := []sourceCallInfo{}
627
+ search := 0
628
+ for search < len(text) {
629
+ idx := strings.Index(text[search:], name)
630
+ if idx < 0 {
631
+ break
632
+ }
633
+ start := search + idx
634
+ after := start + len(name)
635
+ if start > 0 && isIdent(text[start-1]) {
636
+ search = after
637
+ continue
638
+ }
639
+ if after < len(text) && isIdent(text[after]) {
640
+ search = after
641
+ continue
642
+ }
643
+ if !isCodePosition(text, start) || isFunctionDeclarationName(text, start) {
644
+ search = after
645
+ continue
646
+ }
647
+ typeArgs := []string{}
648
+ pos := skipSpace(text, after)
649
+ if pos < len(text) && text[pos] == '<' {
650
+ typeEnd := findBalanced(text, pos, '<', '>')
651
+ if typeEnd < 0 {
652
+ search = pos + 1
653
+ continue
654
+ }
655
+ typeArgs = nonEmptyParts(splitTop(text[pos+1:typeEnd], ","))
656
+ pos = skipSpace(text, typeEnd+1)
657
+ }
658
+ if pos >= len(text) || text[pos] != '(' {
659
+ search = after
660
+ continue
661
+ }
662
+ close := findBalanced(text, pos, '(', ')')
663
+ if close < 0 {
664
+ search = pos + 1
665
+ continue
666
+ }
667
+ if isDeclarationCallShape(text, close) {
668
+ search = close + 1
669
+ continue
670
+ }
671
+ out = append(out, sourceCallInfo{name: name, typeArgs: typeArgs, args: nonEmptyParts(splitTop(text[pos+1:close], ",")), pos: start})
672
+ search = close + 1
673
+ }
674
+ return out
675
+ }
676
+
677
+ func sourceMethodCalls(text string, name string) []sourceCallInfo {
678
+ out := []sourceCallInfo{}
679
+ for _, call := range sourceCalls(text, name) {
680
+ before := call.pos - 1
681
+ for before >= 0 && (text[before] == ' ' || text[before] == '\t' || text[before] == '\r' || text[before] == '\n') {
682
+ before--
683
+ }
684
+ if before >= 0 && text[before] == '.' {
685
+ call.receiver = methodReceiverExpression(text, before)
686
+ out = append(out, call)
687
+ }
688
+ }
689
+ return out
690
+ }
691
+
692
+ func methodReceiverExpression(text string, dot int) string {
693
+ end := dot
694
+ pos := dot - 1
695
+ for pos >= 0 && (text[pos] == ' ' || text[pos] == '\t' || text[pos] == '\r' || text[pos] == '\n') {
696
+ pos--
697
+ }
698
+ if pos >= 0 && text[pos] == '?' {
699
+ pos--
700
+ for pos >= 0 && (text[pos] == ' ' || text[pos] == '\t' || text[pos] == '\r' || text[pos] == '\n') {
701
+ pos--
702
+ }
703
+ }
704
+ start, ok := scanIdentifierLeft(text, pos)
705
+ if !ok {
706
+ return ""
707
+ }
708
+ for {
709
+ prev := start - 1
710
+ for prev >= 0 && (text[prev] == ' ' || text[prev] == '\t' || text[prev] == '\r' || text[prev] == '\n') {
711
+ prev--
712
+ }
713
+ if prev < 0 || text[prev] != '.' {
714
+ break
715
+ }
716
+ leftEnd := prev - 1
717
+ for leftEnd >= 0 && (text[leftEnd] == ' ' || text[leftEnd] == '\t' || text[leftEnd] == '\r' || text[leftEnd] == '\n') {
718
+ leftEnd--
719
+ }
720
+ if leftEnd >= 0 && text[leftEnd] == '?' {
721
+ leftEnd--
722
+ for leftEnd >= 0 && (text[leftEnd] == ' ' || text[leftEnd] == '\t' || text[leftEnd] == '\r' || text[leftEnd] == '\n') {
723
+ leftEnd--
724
+ }
725
+ }
726
+ leftStart, ok := scanIdentifierLeft(text, leftEnd)
727
+ if !ok {
728
+ return ""
729
+ }
730
+ start = leftStart
731
+ }
732
+ return strings.TrimSpace(text[start:end])
733
+ }
734
+
735
+ func scanIdentifierLeft(text string, pos int) (int, bool) {
736
+ if pos < 0 || pos >= len(text) || !isIdent(text[pos]) {
737
+ return 0, false
738
+ }
739
+ end := pos
740
+ for pos >= 0 && isIdent(text[pos]) {
741
+ pos--
742
+ }
743
+ start := pos + 1
744
+ if start <= end && text[start] >= '0' && text[start] <= '9' {
745
+ return 0, false
746
+ }
747
+ return start, true
748
+ }
749
+
750
+ func receiverClassName(info *fileInfo, reg *registry, call sourceCallInfo) (string, bool) {
751
+ receiver := strings.TrimSpace(call.receiver)
752
+ if receiver == "" {
753
+ return "", false
754
+ }
755
+ if receiver == "this" {
756
+ if class := containingClass(info, call.pos); class != nil {
757
+ return class.name, true
758
+ }
759
+ return "", false
760
+ }
761
+ if strings.HasPrefix(receiver, "this.") {
762
+ class := containingClass(info, call.pos)
763
+ if class == nil {
764
+ return "", false
765
+ }
766
+ member := strings.TrimSpace(strings.TrimPrefix(receiver, "this."))
767
+ if strings.Contains(member, ".") {
768
+ return "", false
769
+ }
770
+ if typeText, ok := classMemberType(class, member); ok {
771
+ return classNameFromType(info, reg, typeText, call.pos)
772
+ }
773
+ return "", false
774
+ }
775
+ if !isIdentifierName(receiver) {
776
+ return "", false
777
+ }
778
+ if typeText, ok := localReceiverType(info.file.Text(), receiver, call.pos); ok {
779
+ return classNameFromType(info, reg, typeText, call.pos)
780
+ }
781
+ return "", false
782
+ }
783
+
784
+ func containingClass(info *fileInfo, pos int) *classInfo {
785
+ var found *classInfo
786
+ for _, class := range info.classes {
787
+ if class.pos <= pos && pos <= class.end && (found == nil || class.pos > found.pos) {
788
+ found = class
789
+ }
790
+ }
791
+ return found
792
+ }
793
+
794
+ func classMemberType(class *classInfo, name string) (string, bool) {
795
+ for _, prop := range class.properties {
796
+ if prop.name == name {
797
+ return prop.typeText, true
798
+ }
799
+ }
800
+ for _, param := range class.ctor {
801
+ if param.name == name {
802
+ return param.typeText, true
803
+ }
804
+ }
805
+ return "", false
806
+ }
807
+
808
+ func localReceiverType(text string, name string, pos int) (string, bool) {
809
+ if pos > len(text) {
810
+ pos = len(text)
811
+ }
812
+ prefix := text[:pos]
813
+ ident := regexp.QuoteMeta(name)
814
+ if typeText, ok := lastCapture(prefix, `(?:^|[^A-Za-z0-9_$])(?:const|let|var)\s+`+ident+`\s*=\s*new\s+([A-Za-z_$][A-Za-z0-9_$]*)\b`); ok {
815
+ return typeText, true
816
+ }
817
+ if typeText, ok := lastCapture(prefix, `(?:^|[^A-Za-z0-9_$])(?:const|let|var)\s+`+ident+`\s*:\s*([^=;\n]+)`); ok {
818
+ return typeText, true
819
+ }
820
+ if typeText, ok := lastCapture(prefix, `(?:^|[^A-Za-z0-9_$])`+ident+`\s*:\s*([A-Za-z_$][A-Za-z0-9_$]*(?:\s*<[^=;,\)\n]+>)?)`); ok {
821
+ return typeText, true
822
+ }
823
+ return "", false
824
+ }
825
+
826
+ func lastCapture(text string, pattern string) (string, bool) {
827
+ re := regexp.MustCompile(pattern)
828
+ matches := re.FindAllStringSubmatch(text, -1)
829
+ if len(matches) == 0 || len(matches[len(matches)-1]) < 2 {
830
+ return "", false
831
+ }
832
+ value := strings.TrimSpace(matches[len(matches)-1][1])
833
+ return value, value != ""
834
+ }
835
+
836
+ func classNameFromType(info *fileInfo, reg *registry, typeText string, pos int) (string, bool) {
837
+ name := interfaceRefName(typeText)
838
+ if class, _, ok := resolveClassRefAt(info, reg, name, pos); ok {
839
+ return class.name, true
840
+ }
841
+ if _, ok := reg.classes[name]; ok {
842
+ return name, true
843
+ }
844
+ return "", false
845
+ }
846
+
847
+ func isDeclarationCallShape(text string, close int) bool {
848
+ next := skipSpace(text, close+1)
849
+ if next >= len(text) {
850
+ return false
851
+ }
852
+ return text[next] == ':' || text[next] == '{'
853
+ }
854
+
855
+ func isFunctionDeclarationName(text string, start int) bool {
856
+ lineStart := strings.LastIndexAny(text[:start], "\r\n")
857
+ if lineStart < 0 {
858
+ lineStart = 0
859
+ } else {
860
+ lineStart++
861
+ }
862
+ prefix := strings.TrimSpace(text[lineStart:start])
863
+ return regexp.MustCompile(`(?:^|\b)(?:export\s+)?(?:async\s+)?function\s*$`).MatchString(prefix)
864
+ }
865
+
866
+ func firstFunctionParameterType(raw string) (string, bool) {
867
+ raw = strings.TrimSpace(raw)
868
+ raw = strings.TrimPrefix(raw, "async ")
869
+ if strings.HasPrefix(raw, "function") {
870
+ open := strings.IndexByte(raw, '(')
871
+ if open < 0 {
872
+ return "", false
873
+ }
874
+ close := findBalanced(raw, open, '(', ')')
875
+ if close < 0 {
876
+ return "", false
877
+ }
878
+ return firstParameterType(raw[open+1 : close])
879
+ }
880
+ if strings.HasPrefix(raw, "(") {
881
+ close := findBalanced(raw, 0, '(', ')')
882
+ if close < 0 {
883
+ return "", false
884
+ }
885
+ after := skipSpace(raw, close+1)
886
+ if after >= len(raw) || !strings.HasPrefix(raw[after:], "=>") {
887
+ return "", false
888
+ }
889
+ return firstParameterType(raw[1:close])
890
+ }
891
+ arrow := strings.Index(raw, "=>")
892
+ if arrow < 0 {
893
+ return "", false
894
+ }
895
+ return firstParameterType(raw[:arrow])
896
+ }
897
+
898
+ func firstParameterType(params string) (string, bool) {
899
+ first := firstArg(splitTop(params, ","))
900
+ if strings.TrimSpace(first) == "" {
901
+ return "", false
902
+ }
903
+ colon := topLevelColon(first)
904
+ if colon < 0 {
905
+ return "", false
906
+ }
907
+ typeText := strings.TrimSpace(first[colon+1:])
908
+ if eq := topLevelEquals(typeText); eq >= 0 {
909
+ typeText = strings.TrimSpace(typeText[:eq])
910
+ }
911
+ return typeText, typeText != ""
912
+ }