@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,114 @@
1
+ 'use strict';
2
+ /* oxlint-disable typescript/no-require-imports -- this helper is spawned by the CommonJS plugin descriptor. */
3
+
4
+ const crypto = require('node:crypto');
5
+ const fs = require('node:fs');
6
+ const http = require('node:http');
7
+ const https = require('node:https');
8
+ const os = require('node:os');
9
+ const path = require('node:path');
10
+
11
+ const MAX_BINARY_BYTES = 128 * 1024 * 1024;
12
+ const MAX_MANIFEST_BYTES = 64 * 1024;
13
+ const MAX_REDIRECTS = 5;
14
+
15
+ async function main() {
16
+ const input = JSON.parse(fs.readFileSync(0, 'utf8'));
17
+ const manifest = JSON.parse((await download(input.manifestUrl, MAX_MANIFEST_BYTES, input)).toString('utf8'));
18
+ validateManifest(manifest, input.expected);
19
+
20
+ const binary = await download(input.binaryUrl, MAX_BINARY_BYTES, input);
21
+ const digest = crypto.createHash('sha256').update(binary).digest('hex');
22
+ if (digest !== manifest.binarySha256) throw new Error(`binary checksum mismatch: expected ${manifest.binarySha256}, received ${digest}`);
23
+ if (binary.length !== manifest.binarySize) throw new Error(`binary size mismatch: expected ${manifest.binarySize}, received ${binary.length}`);
24
+
25
+ const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'tsf-type-compiler-'));
26
+ const temporaryBinary = path.join(temporaryDirectory, path.basename(input.destination));
27
+ try {
28
+ fs.writeFileSync(temporaryBinary, binary, { mode: 0o755 });
29
+ fs.mkdirSync(path.dirname(input.destination), { recursive: true });
30
+ if (fs.existsSync(input.destination)) return;
31
+ try {
32
+ fs.renameSync(temporaryBinary, input.destination);
33
+ } catch (error) {
34
+ if (!fs.existsSync(input.destination)) throw error;
35
+ }
36
+ if (process.platform !== 'win32') fs.chmodSync(input.destination, 0o755);
37
+ } finally {
38
+ fs.rmSync(temporaryDirectory, { force: true, recursive: true });
39
+ }
40
+ }
41
+
42
+ function validateManifest(manifest, expected) {
43
+ for (const key of [
44
+ 'schemaVersion',
45
+ 'packageVersion',
46
+ 'platform',
47
+ 'arch',
48
+ 'pluginSourceSha256',
49
+ 'ttscVersion',
50
+ 'typescriptVersion',
51
+ 'binaryAsset'
52
+ ]) {
53
+ if (manifest[key] !== expected[key]) throw new Error(`manifest ${key} mismatch: expected ${expected[key]}, received ${manifest[key]}`);
54
+ }
55
+ if (manifest.cgoEnabled !== false) throw new Error('prebuilt compiler must be produced with CGO_ENABLED=0');
56
+ if (!/^[a-f0-9]{64}$/.test(manifest.binarySha256)) throw new Error('manifest has an invalid binary checksum');
57
+ if (!Number.isSafeInteger(manifest.binarySize) || manifest.binarySize <= 0 || manifest.binarySize > MAX_BINARY_BYTES) {
58
+ throw new Error('manifest has an invalid binary size');
59
+ }
60
+ }
61
+
62
+ function download(location, maximumBytes, options, redirects = 0) {
63
+ const url = new URL(location);
64
+ if (url.protocol !== 'https:' && !(options.allowHttp === true && url.protocol === 'http:')) {
65
+ throw new Error(`refusing prebuilt compiler URL protocol ${url.protocol}`);
66
+ }
67
+ const client = url.protocol === 'https:' ? https : http;
68
+ return new Promise((resolve, reject) => {
69
+ const request = client.get(
70
+ url,
71
+ {
72
+ headers: {
73
+ Accept: 'application/octet-stream, application/json',
74
+ 'User-Agent': 'ts-server-foundation-type-compiler'
75
+ }
76
+ },
77
+ response => {
78
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
79
+ response.resume();
80
+ if (redirects >= MAX_REDIRECTS) return reject(new Error('too many prebuilt compiler redirects'));
81
+ return resolve(download(new URL(response.headers.location, url).href, maximumBytes, options, redirects + 1));
82
+ }
83
+ if (response.statusCode !== 200) {
84
+ response.resume();
85
+ return reject(new Error(`prebuilt compiler request returned HTTP ${response.statusCode}`));
86
+ }
87
+ const declaredLength = Number(response.headers['content-length']);
88
+ if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
89
+ response.destroy();
90
+ return reject(new Error(`prebuilt compiler response exceeds ${maximumBytes} bytes`));
91
+ }
92
+ const chunks = [];
93
+ let size = 0;
94
+ response.on('data', chunk => {
95
+ size += chunk.length;
96
+ if (size > maximumBytes) {
97
+ response.destroy(new Error(`prebuilt compiler response exceeds ${maximumBytes} bytes`));
98
+ return;
99
+ }
100
+ chunks.push(chunk);
101
+ });
102
+ response.on('end', () => resolve(Buffer.concat(chunks)));
103
+ response.on('error', reject);
104
+ }
105
+ );
106
+ request.setTimeout(options.requestTimeoutMs, () => request.destroy(new Error('prebuilt compiler request timed out')));
107
+ request.on('error', reject);
108
+ });
109
+ }
110
+
111
+ main().catch(error => {
112
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
113
+ process.exitCode = 1;
114
+ });
@@ -0,0 +1,187 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+
9
+ shimast "github.com/microsoft/typescript-go/shim/ast"
10
+ shimcore "github.com/microsoft/typescript-go/shim/core"
11
+ shimparser "github.com/microsoft/typescript-go/shim/parser"
12
+ shimprinter "github.com/microsoft/typescript-go/shim/printer"
13
+ )
14
+
15
+ const (
16
+ runtimeImportPlaceholderName = "__tsf_runtime_import__"
17
+ runtimeNamespacePlaceholderName = "__tsf_runtime_namespace__"
18
+ runtimeAliasPlaceholderName = "__tsf_runtime_alias__"
19
+ )
20
+
21
+ type expressionTemplate struct {
22
+ parsed *shimast.Node
23
+ }
24
+
25
+ func parseExpressionTemplate(source string) (expressionTemplate, error) {
26
+ source = strings.TrimSpace(source)
27
+ if source == "" {
28
+ return expressionTemplate{}, fmt.Errorf("generated expression is empty")
29
+ }
30
+ fileName := filepath.ToSlash(filepath.Join(os.TempDir(), "tsf-generated-metadata-expression.ts"))
31
+ file := shimparser.ParseSourceFile(
32
+ shimast.SourceFileParseOptions{FileName: fileName},
33
+ "("+source+");",
34
+ shimcore.ScriptKindTS,
35
+ )
36
+ if file == nil {
37
+ return expressionTemplate{}, fmt.Errorf("generated expression did not parse")
38
+ }
39
+ if diagnostics := file.Diagnostics(); len(diagnostics) != 0 {
40
+ return expressionTemplate{}, fmt.Errorf("generated expression has %d parse diagnostic(s)", len(diagnostics))
41
+ }
42
+ if file.Statements == nil || len(file.Statements.Nodes) != 1 {
43
+ return expressionTemplate{}, fmt.Errorf("generated expression did not parse as one statement")
44
+ }
45
+ statement := file.Statements.Nodes[0]
46
+ if statement == nil || statement.Kind != shimast.KindExpressionStatement || statement.AsExpressionStatement().Expression == nil {
47
+ return expressionTemplate{}, fmt.Errorf("generated expression did not parse as an expression statement")
48
+ }
49
+ parsed := statement.AsExpressionStatement().Expression.AsNode()
50
+ if err := validateRuntimePlaceholders(parsed); err != nil {
51
+ return expressionTemplate{}, err
52
+ }
53
+ return expressionTemplate{parsed: parsed}, nil
54
+ }
55
+
56
+ func validateRuntimePlaceholders(node *shimast.Node) error {
57
+ var validationErr error
58
+ var walk func(*shimast.Node)
59
+ walk = func(current *shimast.Node) {
60
+ if current == nil || validationErr != nil {
61
+ return
62
+ }
63
+ if current.Kind == shimast.KindCallExpression {
64
+ call := current.AsCallExpression()
65
+ if call != nil && call.Expression != nil && call.Expression.Kind == shimast.KindIdentifier {
66
+ name := call.Expression.Text()
67
+ expected := -1
68
+ switch name {
69
+ case runtimeImportPlaceholderName:
70
+ expected = 3
71
+ case runtimeNamespacePlaceholderName:
72
+ expected = 2
73
+ case runtimeAliasPlaceholderName:
74
+ expected = 3
75
+ }
76
+ if expected >= 0 {
77
+ if call.Arguments == nil || len(call.Arguments.Nodes) != expected {
78
+ validationErr = fmt.Errorf("%s requires %d string arguments", name, expected)
79
+ return
80
+ }
81
+ for _, argument := range call.Arguments.Nodes {
82
+ if argument == nil || !shimast.IsStringLiteral(argument) {
83
+ validationErr = fmt.Errorf("%s arguments must be string literals", name)
84
+ return
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ current.ForEachChild(func(child *shimast.Node) bool {
91
+ walk(child)
92
+ return validationErr != nil
93
+ })
94
+ }
95
+ walk(node)
96
+ return validationErr
97
+ }
98
+
99
+ func (template expressionTemplate) materialize(ec *shimprinter.EmitContext, imports *astImportRegistry) *shimast.Node {
100
+ if template.parsed == nil {
101
+ panic("tsf metadata compiler: nil expression template")
102
+ }
103
+ cloned := ec.Factory.DeepCloneNode(template.parsed)
104
+ var visitor *shimast.NodeVisitor
105
+ visitor = ec.NewNodeVisitor(func(node *shimast.Node) *shimast.Node {
106
+ if node == nil {
107
+ return nil
108
+ }
109
+ if replacement := materializeRuntimePlaceholder(node, imports); replacement != nil {
110
+ return replacement
111
+ }
112
+ return visitor.VisitEachChild(node)
113
+ })
114
+ result := visitor.VisitNode(cloned)
115
+ if containsRuntimePlaceholder(result) {
116
+ panic("tsf metadata compiler: runtime placeholder survived AST materialization")
117
+ }
118
+ return result
119
+ }
120
+
121
+ func materializeRuntimePlaceholder(node *shimast.Node, imports *astImportRegistry) *shimast.Node {
122
+ if node.Kind != shimast.KindCallExpression {
123
+ return nil
124
+ }
125
+ call := node.AsCallExpression()
126
+ if call == nil || call.Expression == nil || call.Expression.Kind != shimast.KindIdentifier || call.Arguments == nil {
127
+ return nil
128
+ }
129
+ args := call.Arguments.Nodes
130
+ switch call.Expression.Text() {
131
+ case runtimeNamespacePlaceholderName:
132
+ if len(args) == 2 && shimast.IsStringLiteral(args[0]) && shimast.IsStringLiteral(args[1]) {
133
+ return imports.namespace(args[0].Text(), args[1].Text())
134
+ }
135
+ case runtimeImportPlaceholderName:
136
+ if len(args) == 3 && shimast.IsStringLiteral(args[0]) && shimast.IsStringLiteral(args[1]) && shimast.IsStringLiteral(args[2]) {
137
+ return imports.member(args[0].Text(), args[1].Text(), args[2].Text())
138
+ }
139
+ case runtimeAliasPlaceholderName:
140
+ if len(args) == 3 && shimast.IsStringLiteral(args[0]) && shimast.IsStringLiteral(args[1]) && shimast.IsStringLiteral(args[2]) {
141
+ arguments := []*shimast.Node{}
142
+ if imports.commonJS {
143
+ arguments = append(arguments,
144
+ imports.ec.Factory.NewIdentifier("require"),
145
+ imports.ec.Factory.NewStringLiteral(imports.resolvedSpecifier(args[0].Text(), ""), shimast.TokenFlagsNone),
146
+ )
147
+ } else {
148
+ arguments = append(arguments, imports.optionalModuleLoader(args[0].Text(), ""))
149
+ }
150
+ arguments = append(arguments,
151
+ imports.ec.Factory.NewStringLiteral(args[1].Text(), shimast.TokenFlagsNone),
152
+ imports.ec.Factory.NewStringLiteral(args[2].Text(), shimast.TokenFlagsNone),
153
+ )
154
+ return imports.ec.Factory.NewCallExpression(
155
+ imports.staticMember(compactMetadataRuntimeSpec, compactMetadataAliasResolverName),
156
+ nil,
157
+ nil,
158
+ imports.ec.Factory.NewNodeList(arguments),
159
+ shimast.NodeFlagsNone,
160
+ )
161
+ }
162
+ }
163
+ return nil
164
+ }
165
+
166
+ func containsRuntimePlaceholder(node *shimast.Node) bool {
167
+ found := false
168
+ var walk func(*shimast.Node)
169
+ walk = func(current *shimast.Node) {
170
+ if current == nil || found {
171
+ return
172
+ }
173
+ if current.Kind == shimast.KindIdentifier {
174
+ name := current.Text()
175
+ if name == runtimeImportPlaceholderName || name == runtimeNamespacePlaceholderName || name == runtimeAliasPlaceholderName {
176
+ found = true
177
+ return
178
+ }
179
+ }
180
+ current.ForEachChild(func(child *shimast.Node) bool {
181
+ walk(child)
182
+ return found
183
+ })
184
+ }
185
+ walk(node)
186
+ return found
187
+ }
@@ -0,0 +1,388 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "strconv"
6
+ "strings"
7
+
8
+ shimast "github.com/microsoft/typescript-go/shim/ast"
9
+ )
10
+
11
+ func resolveImport(fromFile string, spec string, reg *registry) *fileInfo {
12
+ if !strings.HasPrefix(spec, ".") {
13
+ if spec == reflectionPackageSpec {
14
+ return resolveReflectionPackageImport(reg)
15
+ }
16
+ return nil
17
+ }
18
+ base := filepath.Clean(filepath.Join(filepath.Dir(fromFile), spec))
19
+ ext := strings.ToLower(filepath.Ext(base))
20
+ withoutExt := strings.TrimSuffix(base, filepath.Ext(base))
21
+ candidates := []string{}
22
+ switch ext {
23
+ case ".mjs":
24
+ candidates = append(candidates, moduleKey(withoutExt+".mts"), moduleKey(withoutExt+".d.mts"))
25
+ case ".cjs":
26
+ candidates = append(candidates, moduleKey(withoutExt+".cts"), moduleKey(withoutExt+".d.cts"))
27
+ case ".js":
28
+ candidates = append(candidates, moduleKey(withoutExt+".ts"), moduleKey(withoutExt+".tsx"), moduleKey(withoutExt+".d.ts"))
29
+ default:
30
+ candidates = append(candidates,
31
+ moduleKey(base),
32
+ moduleKey(base+".ts"),
33
+ moduleKey(base+".tsx"),
34
+ moduleKey(base+".mts"),
35
+ moduleKey(base+".cts"),
36
+ moduleKey(base+".d.ts"),
37
+ moduleKey(base+".d.mts"),
38
+ moduleKey(base+".d.cts"),
39
+ moduleKey(filepath.Join(base, "index.ts")),
40
+ moduleKey(filepath.Join(base, "index.mts")),
41
+ moduleKey(filepath.Join(base, "index.cts")),
42
+ moduleKey(filepath.Join(base, "index.d.ts")),
43
+ moduleKey(filepath.Join(base, "index.d.mts")),
44
+ moduleKey(filepath.Join(base, "index.d.cts")),
45
+ )
46
+ }
47
+ for _, candidate := range candidates {
48
+ if info := reg.byPath[candidate]; info != nil {
49
+ return info
50
+ }
51
+ }
52
+ return nil
53
+ }
54
+
55
+ func resolveReflectionPackageImport(reg *registry) *fileInfo {
56
+ for _, info := range reg.files {
57
+ fileName := filepath.ToSlash(info.file.FileName())
58
+ if strings.HasSuffix(fileName, "/@zyno-io/ts-reflection/dist/index.d.ts") ||
59
+ strings.HasSuffix(fileName, "/packages/reflection/dist/index.d.ts") {
60
+ return info
61
+ }
62
+ }
63
+ return nil
64
+ }
65
+
66
+ func classFromNode(info *fileInfo, node *shimast.Node) *classInfo {
67
+ file := info.file
68
+ nameNode := node.Name()
69
+ if nameNode == nil {
70
+ return nil
71
+ }
72
+ class := &classInfo{
73
+ name: nameNode.Text(),
74
+ pos: node.Pos(),
75
+ end: node.End(),
76
+ ambient: node.ModifierFlags()&shimast.ModifierFlagsAmbient != 0 || node.Flags&shimast.NodeFlagsAmbient != 0,
77
+ decoratedMethodsOnly: info.decoratedMethodsOnly,
78
+ }
79
+ for _, member := range node.Members() {
80
+ if member.ModifierFlags()&shimast.ModifierFlagsPrivate != 0 {
81
+ continue
82
+ }
83
+ switch member.Kind {
84
+ case shimast.KindPropertyDeclaration:
85
+ if isStaticMember(file, member) {
86
+ continue
87
+ }
88
+ name := memberName(file, member)
89
+ if name == "" {
90
+ continue
91
+ }
92
+ class.properties = append(class.properties, propertyInfo{
93
+ name: name,
94
+ typeText: nodeText(file, member.Type()),
95
+ typeNode: member.Type(),
96
+ optional: member.QuestionToken() != nil,
97
+ })
98
+ case shimast.KindMethodDeclaration:
99
+ name := memberName(file, member)
100
+ if name == "" {
101
+ continue
102
+ }
103
+ preferTypia := isHttpRouteMethod(info, member)
104
+ method := methodInfo{
105
+ name: name,
106
+ description: jsDocDescription(file, member),
107
+ typeParams: typeParameterNames(member),
108
+ returnType: nodeText(file, member.Type()),
109
+ returnTypeNode: member.Type(),
110
+ preferTypia: preferTypia,
111
+ decorated: len(member.Decorators()) != 0,
112
+ params: paramsFromNode(file, member),
113
+ }
114
+ if isStaticMember(file, member) {
115
+ class.staticMethods = append(class.staticMethods, method)
116
+ } else {
117
+ class.methods = append(class.methods, method)
118
+ }
119
+ case shimast.KindConstructor:
120
+ class.ctor = paramsFromNode(file, member)
121
+ class.hasCtor = true
122
+ }
123
+ }
124
+ return class
125
+ }
126
+
127
+ func functionFromNode(file *shimast.SourceFile, node *shimast.Node) functionInfo {
128
+ nameNode := node.Name()
129
+ if nameNode == nil {
130
+ return functionInfo{}
131
+ }
132
+ return functionInfo{
133
+ name: nameNode.Text(),
134
+ typeParams: typeParameterNames(node),
135
+ params: paramsFromNode(file, node),
136
+ pos: node.Pos(),
137
+ }
138
+ }
139
+
140
+ func typeParameterNames(node *shimast.Node) []string {
141
+ params := []string{}
142
+ for _, param := range node.TypeParameters() {
143
+ if param.Name() != nil {
144
+ params = append(params, param.Name().Text())
145
+ }
146
+ }
147
+ return params
148
+ }
149
+
150
+ func isHttpRouteMethod(info *fileInfo, member *shimast.Node) bool {
151
+ for _, decorator := range member.Decorators() {
152
+ if decorator.Kind != shimast.KindDecorator {
153
+ continue
154
+ }
155
+ if isHttpRouteDecoratorExpression(info, decorator.AsDecorator().Expression.AsNode()) {
156
+ return true
157
+ }
158
+ }
159
+ return false
160
+ }
161
+
162
+ func isHttpRouteDecoratorExpression(info *fileInfo, expr *shimast.Node) bool {
163
+ if expr == nil {
164
+ return false
165
+ }
166
+ switch expr.Kind {
167
+ case shimast.KindParenthesizedExpression:
168
+ return isHttpRouteDecoratorExpression(info, expr.Expression())
169
+ case shimast.KindCallExpression:
170
+ callee := expr.Expression()
171
+ if isHttpRouteCallee(info, callee) {
172
+ return true
173
+ }
174
+ return isHttpRouteDecoratorExpression(info, callee)
175
+ case shimast.KindPropertyAccessExpression:
176
+ if isHttpRouteCallee(info, expr) {
177
+ return true
178
+ }
179
+ return isHttpRouteDecoratorExpression(info, expr.Expression())
180
+ default:
181
+ return false
182
+ }
183
+ }
184
+
185
+ func isHttpRouteCallee(info *fileInfo, callee *shimast.Node) bool {
186
+ if callee == nil {
187
+ return false
188
+ }
189
+ switch callee.Kind {
190
+ case shimast.KindIdentifier:
191
+ name := callee.Text()
192
+ return isHttpVerb(name) && isDirectHttpVerbImport(info, name)
193
+ case shimast.KindPropertyAccessExpression:
194
+ access := callee.AsPropertyAccessExpression()
195
+ name := access.Name()
196
+ return name != nil && isHttpVerb(name.Text()) && isHttpNamespaceExpression(info, access.Expression.AsNode())
197
+ default:
198
+ return false
199
+ }
200
+ }
201
+
202
+ func isHttpNamespaceExpression(info *fileInfo, expr *shimast.Node) bool {
203
+ for expr != nil && expr.Kind == shimast.KindParenthesizedExpression {
204
+ expr = expr.Expression()
205
+ }
206
+ if expr == nil || expr.Kind != shimast.KindIdentifier {
207
+ return false
208
+ }
209
+ return isHttpNamespaceImport(info, expr.Text())
210
+ }
211
+
212
+ func isHttpNamespaceImport(info *fileInfo, name string) bool {
213
+ if name != "http" {
214
+ return false
215
+ }
216
+ ref, ok := info.imports[name]
217
+ return ok && ref.exportName == "http" && isFoundationHttpImport(ref)
218
+ }
219
+
220
+ func isDirectHttpVerbImport(info *fileInfo, name string) bool {
221
+ ref, ok := info.imports[name]
222
+ return ok && ref.exportName == name && isFoundationHttpImport(ref)
223
+ }
224
+
225
+ func isFoundationHttpImport(ref importRef) bool {
226
+ spec := filepath.ToSlash(ref.spec)
227
+ source := filepath.ToSlash(ref.source)
228
+ if strings.HasSuffix(source, "/src/index") ||
229
+ strings.HasSuffix(source, "/src/http") ||
230
+ strings.HasSuffix(source, "/src/http/index") ||
231
+ strings.HasSuffix(source, "/src/http/decorators") {
232
+ return true
233
+ }
234
+ return spec == foundationPackageSpec
235
+ }
236
+
237
+ func isHttpVerb(name string) bool {
238
+ switch name {
239
+ case "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD":
240
+ return true
241
+ default:
242
+ return false
243
+ }
244
+ }
245
+
246
+ func isStaticMember(file *shimast.SourceFile, member *shimast.Node) bool {
247
+ return member.ModifierFlags()&shimast.ModifierFlagsStatic != 0
248
+ }
249
+
250
+ func memberName(file *shimast.SourceFile, member *shimast.Node) string {
251
+ name := member.Name()
252
+ if name == nil {
253
+ return ""
254
+ }
255
+ text := strings.TrimSpace(stripTypeComments(nodeText(file, name)))
256
+ if text == "" || strings.HasPrefix(text, "[") || strings.HasPrefix(text, "{") {
257
+ return ""
258
+ }
259
+ if len(text) >= 2 {
260
+ quote := text[0]
261
+ if (quote == '\'' || quote == '"' || quote == '`') && text[len(text)-1] == quote {
262
+ unquoted, err := strconv.Unquote(text)
263
+ if err == nil {
264
+ return unquoted
265
+ }
266
+ return strings.Trim(text, "'\"`")
267
+ }
268
+ }
269
+ return text
270
+ }
271
+
272
+ func paramsFromNode(file *shimast.SourceFile, node *shimast.Node) []paramInfo {
273
+ params := []paramInfo{}
274
+ for _, param := range node.Parameters() {
275
+ name := memberName(file, param)
276
+ if name == "" {
277
+ name = "arg"
278
+ }
279
+ params = append(params, paramInfo{
280
+ name: name,
281
+ typeText: nodeText(file, param.Type()),
282
+ typeNode: param.Type(),
283
+ optional: param.QuestionToken() != nil || param.Initializer() != nil,
284
+ hasDefault: param.Initializer() != nil,
285
+ })
286
+ }
287
+ return params
288
+ }
289
+
290
+ func nodeText(file *shimast.SourceFile, node *shimast.Node) string {
291
+ if node == nil {
292
+ return "unknown"
293
+ }
294
+ return strings.TrimSpace(stripTypeComments(file.Text()[node.Pos():node.End()]))
295
+ }
296
+
297
+ func jsDocDescription(file *shimast.SourceFile, node *shimast.Node) string {
298
+ docs := node.JSDoc(file)
299
+ if len(docs) == 0 {
300
+ return ""
301
+ }
302
+ doc := docs[len(docs)-1]
303
+ return cleanJsDocDescription(file.Text()[doc.Pos():doc.End()])
304
+ }
305
+
306
+ func cleanJsDocDescription(raw string) string {
307
+ raw = strings.TrimSpace(raw)
308
+ raw = strings.TrimPrefix(raw, "/**")
309
+ raw = strings.TrimSuffix(raw, "*/")
310
+ lines := strings.Split(raw, "\n")
311
+ parts := []string{}
312
+ for _, line := range lines {
313
+ line = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "*"))
314
+ if strings.HasPrefix(line, "@") {
315
+ break
316
+ }
317
+ if line == "" {
318
+ if len(parts) > 0 {
319
+ break
320
+ }
321
+ continue
322
+ }
323
+ parts = append(parts, line)
324
+ }
325
+ return strings.Join(parts, " ")
326
+ }
327
+
328
+ func classMetadata(info *fileInfo, reg *registry, class *classInfo, typeRef func(string) string) string {
329
+ props := []string{}
330
+ for _, prop := range class.properties {
331
+ items := []string{
332
+ "name: " + quote(prop.name),
333
+ "type: " + referMetadataType(typeRef, cachedTypeExpr(info, reg, prop.typeText, prop.typeNode, class.pos, prop.metadataText)),
334
+ "optional: " + boolLit(prop.optional),
335
+ }
336
+ flags := collectFlags(info, reg, prop.typeText)
337
+ if flags["primaryKey"] != "" {
338
+ items = append(items, "primaryKey: true")
339
+ }
340
+ if flags["autoIncrement"] != "" {
341
+ items = append(items, "autoIncrement: true")
342
+ }
343
+ for _, key := range []string{"reference", "index", "unique"} {
344
+ if value := flags[key]; value != "" {
345
+ items = append(items, key+": "+value)
346
+ }
347
+ }
348
+ props = append(props, "{"+strings.Join(items, ", ")+"}")
349
+ }
350
+ methods := []string{}
351
+ for _, method := range class.methods {
352
+ if class.decoratedMethodsOnly && !method.decorated {
353
+ continue
354
+ }
355
+ items := []string{
356
+ "name: " + quote(method.name),
357
+ "parameters: " + paramsExpr(info, reg, method.params, class.pos, typeRef),
358
+ "returnType: " + referMetadataType(typeRef, cachedTypeExpr(info, reg, method.returnType, method.returnTypeNode, class.pos, method.returnMetadataText)),
359
+ }
360
+ if method.description != "" {
361
+ items = append(items, "description: "+quote(method.description))
362
+ }
363
+ methods = append(methods, "{"+strings.Join(items, ", ")+"}")
364
+ }
365
+ return "{kind: 16, name: " + quote(class.name) +
366
+ ", typeName: " + quote(class.name) +
367
+ ", classType: () => " + class.name +
368
+ ", properties: [" + strings.Join(props, ", ") + "]" +
369
+ ", methods: [" + strings.Join(methods, ", ") + "]" +
370
+ ", hasConstructor: " + boolLit(class.hasCtor) +
371
+ ", constructorParameters: " + paramsExpr(info, reg, class.ctor, class.pos, typeRef) + "}"
372
+ }
373
+
374
+ func paramsExpr(info *fileInfo, reg *registry, params []paramInfo, pos int, typeRef func(string) string) string {
375
+ out := []string{}
376
+ for _, param := range params {
377
+ typeExpr := referMetadataType(typeRef, cachedTypeExpr(info, reg, param.typeText, param.typeNode, pos, param.metadataText))
378
+ out = append(out, "{name: "+quote(param.name)+", type: "+typeExpr+", optional: "+boolLit(param.optional)+", default: "+boolLit(param.hasDefault)+"}")
379
+ }
380
+ return "[" + strings.Join(out, ", ") + "]"
381
+ }
382
+
383
+ func referMetadataType(typeRef func(string) string, expr string) string {
384
+ if typeRef == nil {
385
+ return expr
386
+ }
387
+ return typeRef(expr)
388
+ }