@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,558 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "strings"
6
+ "testing"
7
+
8
+ shimast "github.com/microsoft/typescript-go/shim/ast"
9
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
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
+ func TestExpressionTemplateParsesStructuredMetadata(t *testing.T) {
16
+ template, err := parseExpressionTemplate(`{
17
+ kind: 16,
18
+ classType: () => Date,
19
+ value: (() => ({nested: [true, 1, "two"]}))()
20
+ }`)
21
+ if err != nil {
22
+ t.Fatal(err)
23
+ }
24
+ if template.parsed == nil || template.parsed.Kind != shimast.KindParenthesizedExpression {
25
+ t.Fatalf("parsed expression kind = %v", template.parsed)
26
+ }
27
+ }
28
+
29
+ func TestExpressionTemplateRejectsMalformedSourceAndPlaceholders(t *testing.T) {
30
+ for _, source := range []string{
31
+ `{kind:`,
32
+ `__tsf_runtime_import__("pkg", "Value")`,
33
+ `__tsf_runtime_namespace__("pkg", 1)`,
34
+ } {
35
+ if _, err := parseExpressionTemplate(source); err == nil {
36
+ t.Fatalf("parseExpressionTemplate(%q) unexpectedly succeeded", source)
37
+ }
38
+ }
39
+ }
40
+
41
+ func TestRuntimePlaceholderMaterializationPreservesModuleSemantics(t *testing.T) {
42
+ template, err := parseExpressionTemplate(`{
43
+ member: __tsf_runtime_import__("./dependency", "Value", "/project/dependency.ts"),
44
+ namespace: __tsf_runtime_namespace__("@scope/shared", "")
45
+ }`)
46
+ if err != nil {
47
+ t.Fatal(err)
48
+ }
49
+
50
+ for _, test := range []struct {
51
+ name string
52
+ commonJS bool
53
+ wantImports int
54
+ wantRequire bool
55
+ }{
56
+ {name: "esm", wantImports: 2},
57
+ {name: "commonjs", commonJS: true, wantRequire: true},
58
+ } {
59
+ t.Run(test.name, func(t *testing.T) {
60
+ ec := shimprinter.NewEmitContext()
61
+ file := parseTestSourceFile(t, "/project/consumer.ts", `export const value = 1;`)
62
+ imports := newAstImportRegistry(ec, file, test.commonJS)
63
+ materialized := template.materialize(ec, imports)
64
+ if containsRuntimePlaceholder(materialized) {
65
+ t.Fatal("compiler placeholder survived materialization")
66
+ }
67
+ if got := len(imports.statements()); got != test.wantImports {
68
+ t.Fatalf("generated imports = %d, want %d", got, test.wantImports)
69
+ }
70
+ if got := astContainsIdentifier(materialized, "require"); got != test.wantRequire {
71
+ t.Fatalf("contains require = %v, want %v", got, test.wantRequire)
72
+ }
73
+ })
74
+ }
75
+ }
76
+
77
+ func TestUpdateMetadataCallUsesAbsoluteArgumentSlot(t *testing.T) {
78
+ ec := shimprinter.NewEmitContext()
79
+ imports := newAstImportRegistry(ec, nil, true)
80
+ template, err := parseExpressionTemplate(`{kind: 7}`)
81
+ if err != nil {
82
+ t.Fatal(err)
83
+ }
84
+ call := ec.Factory.NewCallExpression(
85
+ ec.Factory.NewIdentifier("cast"),
86
+ nil,
87
+ nil,
88
+ ec.Factory.NewNodeList([]*shimast.Node{ec.Factory.NewIdentifier("input")}),
89
+ shimast.NodeFlagsNone,
90
+ ).AsCallExpression()
91
+ updated := updateMetadataCall(
92
+ ec,
93
+ call,
94
+ callEmissionPlan{metadataArgIndex: 4, metadata: template},
95
+ imports,
96
+ newCompactMetadataRuntimeInterner(ec, nil),
97
+ "",
98
+ ).AsCallExpression()
99
+ if got := len(updated.Arguments.Nodes); got != 5 {
100
+ t.Fatalf("arguments = %d, want 5", got)
101
+ }
102
+ for index := 1; index < 4; index++ {
103
+ if updated.Arguments.Nodes[index].Kind != shimast.KindIdentifier || updated.Arguments.Nodes[index].Text() != "undefined" {
104
+ t.Fatalf("argument %d was not an undefined filler", index)
105
+ }
106
+ }
107
+ }
108
+
109
+ func TestMetadataTransformMatchesExactCallPosition(t *testing.T) {
110
+ file := parseTestSourceFile(t, "/project/calls.ts", `
111
+ function same<T>(): void {}
112
+ same<number>();
113
+ same<string>();
114
+ `)
115
+ calls := collectAstNodes(file.AsNode(), shimast.KindCallExpression)
116
+ if len(calls) != 2 {
117
+ t.Fatalf("call expressions = %d, want 2", len(calls))
118
+ }
119
+ template, err := parseExpressionTemplate(`{kind: 7}`)
120
+ if err != nil {
121
+ t.Fatal(err)
122
+ }
123
+ plans := emissionPlans{file.FileName(): {
124
+ calls: map[int]callEmissionPlan{
125
+ calls[1].Pos(): {metadataArgIndex: 0, metadata: template},
126
+ },
127
+ classes: map[int]classEmissionPlan{},
128
+ commonJS: true,
129
+ }}
130
+ transformed := metadataTransform(plans)(shimprinter.NewEmitContext(), file)
131
+ transformedCalls := []*shimast.Node{}
132
+ for _, call := range collectAstNodes(transformed.AsNode(), shimast.KindCallExpression) {
133
+ expression := call.AsCallExpression().Expression
134
+ if expression != nil && expression.Kind == shimast.KindIdentifier && expression.Text() == "same" {
135
+ transformedCalls = append(transformedCalls, call)
136
+ }
137
+ }
138
+ if len(transformedCalls) != 2 {
139
+ t.Fatalf("transformed calls = %d, want 2", len(transformedCalls))
140
+ }
141
+ if len(transformedCalls[0].AsCallExpression().Arguments.Nodes) != 0 {
142
+ t.Fatal("unplanned same-named call was transformed")
143
+ }
144
+ if len(transformedCalls[1].AsCallExpression().Arguments.Nodes) != 1 {
145
+ t.Fatal("planned call was not transformed")
146
+ }
147
+ }
148
+
149
+ func TestClassMetadataTransformMaterializesOneSharedObject(t *testing.T) {
150
+ file := parseTestSourceFile(t, "/project/model.ts", `class Model { value: string }`)
151
+ classes := collectAstNodes(file.AsNode(), shimast.KindClassDeclaration)
152
+ if len(classes) != 1 {
153
+ t.Fatalf("class declarations = %d, want 1", len(classes))
154
+ }
155
+ template, err := parseExpressionTemplate(`{kind: 16, properties: []}`)
156
+ if err != nil {
157
+ t.Fatal(err)
158
+ }
159
+ plans := emissionPlans{file.FileName(): {
160
+ calls: map[int]callEmissionPlan{},
161
+ classes: map[int]classEmissionPlan{
162
+ classes[0].Pos(): {name: "Model", metadata: template},
163
+ },
164
+ commonJS: true,
165
+ }}
166
+ transformed := metadataTransform(plans)(shimprinter.NewEmitContext(), file)
167
+ if got := len(collectAstNodes(transformed.AsNode(), shimast.KindObjectLiteralExpression)); got != 0 {
168
+ t.Fatalf("class metadata left %d object literals in the emitted AST", got)
169
+ }
170
+ if !astContainsIdentifier(transformed.AsNode(), compactMetadataDecoderName) {
171
+ t.Fatal("class metadata was not decoded from compact data")
172
+ }
173
+ }
174
+
175
+ func TestPureJSONAliasMetadataDoesNotLoadTheRuntimeDecoder(t *testing.T) {
176
+ file := parseTestSourceFile(t, "/project/shared.ts", `export type Status = "ready" | "busy"`)
177
+ template, err := parseExpressionTemplate(`{Status: {kind: 12, types: [{kind: 10, literal: "ready"}, {kind: 10, literal: "busy"}]}}`)
178
+ if err != nil {
179
+ t.Fatal(err)
180
+ }
181
+ plans := emissionPlans{file.FileName(): {
182
+ calls: map[int]callEmissionPlan{},
183
+ classes: map[int]classEmissionPlan{},
184
+ aliases: &template,
185
+ commonJS: true,
186
+ }}
187
+
188
+ transformed := metadataTransform(plans)(shimprinter.NewEmitContext(), file)
189
+ if astContainsIdentifier(transformed.AsNode(), compactMetadataDecoderName) {
190
+ t.Fatal("pure JSON aliases loaded the compact metadata runtime")
191
+ }
192
+ if !astContainsIdentifier(transformed.AsNode(), "JSON") || !astContainsIdentifier(transformed.AsNode(), "parse") {
193
+ t.Fatal("pure JSON aliases were not decoded with JSON.parse")
194
+ }
195
+ if got := len(collectAstNodes(transformed.AsNode(), shimast.KindImportDeclaration)); got != 0 {
196
+ t.Fatalf("generated imports = %d, want none", got)
197
+ }
198
+ }
199
+
200
+ func TestAliasMetadataWithRuntimeValuesStillLoadsTheRuntimeDecoder(t *testing.T) {
201
+ file := parseTestSourceFile(t, "/project/shared.ts", `export class Model {}; export type Wrapped = { value: Model }`)
202
+ template, err := parseExpressionTemplate(`{Wrapped: {kind: 16, properties: [{key: "value", value: {kind: 16, classType: () => Model}}]}}`)
203
+ if err != nil {
204
+ t.Fatal(err)
205
+ }
206
+ plans := emissionPlans{file.FileName(): {
207
+ calls: map[int]callEmissionPlan{},
208
+ classes: map[int]classEmissionPlan{},
209
+ aliases: &template,
210
+ commonJS: true,
211
+ }}
212
+
213
+ transformed := metadataTransform(plans)(shimprinter.NewEmitContext(), file)
214
+ if !astContainsIdentifier(transformed.AsNode(), compactMetadataDecoderName) {
215
+ t.Fatal("alias metadata with runtime values did not load the compact metadata runtime")
216
+ }
217
+ if got := len(collectAstNodes(transformed.AsNode(), shimast.KindImportDeclaration)); got != 1 {
218
+ t.Fatalf("generated imports = %d, want one decoder import", got)
219
+ }
220
+ }
221
+
222
+ func TestMetadataRuntimeRequirementAllowsPureJSONAliases(t *testing.T) {
223
+ template, err := parseExpressionTemplate(`{Status: {kind: 12, types: [{kind: 10, literal: "ready"}, {kind: 10, literal: "busy"}]}}`)
224
+ if err != nil {
225
+ t.Fatal(err)
226
+ }
227
+ plan := &fileEmissionPlan{
228
+ calls: map[int]callEmissionPlan{},
229
+ classes: map[int]classEmissionPlan{},
230
+ aliases: &template,
231
+ commonJS: true,
232
+ }
233
+ if got := metadataRuntimeRequirement(plan); got != "" {
234
+ t.Fatalf("pure JSON aliases require metadata runtime: %s", got)
235
+ }
236
+ }
237
+
238
+ func TestMetadataRuntimeRequirementRejectsRuntimeAliasValues(t *testing.T) {
239
+ template, err := parseExpressionTemplate(`{RuntimeValue: {kind: 16, classType: () => Date}}`)
240
+ if err != nil {
241
+ t.Fatal(err)
242
+ }
243
+ plan := &fileEmissionPlan{
244
+ calls: map[int]callEmissionPlan{},
245
+ classes: map[int]classEmissionPlan{},
246
+ aliases: &template,
247
+ }
248
+ if got := metadataRuntimeRequirement(plan); got != "reflected alias metadata for RuntimeValue" {
249
+ t.Fatalf("runtime alias requirement = %q", got)
250
+ }
251
+ }
252
+
253
+ func TestMetadataRuntimeRequirementRejectsRuntimeMetadataSurfaces(t *testing.T) {
254
+ template, err := parseExpressionTemplate(`{kind: 6}`)
255
+ if err != nil {
256
+ t.Fatal(err)
257
+ }
258
+ tests := []struct {
259
+ name string
260
+ plan *fileEmissionPlan
261
+ want string
262
+ }{
263
+ {
264
+ name: "class",
265
+ plan: &fileEmissionPlan{classes: map[int]classEmissionPlan{1: {name: "Model", metadata: template}}},
266
+ want: "reflected class metadata",
267
+ },
268
+ {
269
+ name: "call",
270
+ plan: &fileEmissionPlan{calls: map[int]callEmissionPlan{1: {name: "typeOf", metadata: template}}},
271
+ want: "reflected call metadata",
272
+ },
273
+ {
274
+ name: "registry",
275
+ plan: &fileEmissionPlan{metadataTypes: []expressionTemplate{template}},
276
+ want: "the reflected metadata registry",
277
+ },
278
+ }
279
+ for _, test := range tests {
280
+ t.Run(test.name, func(t *testing.T) {
281
+ if got := metadataRuntimeRequirement(test.plan); got != test.want {
282
+ t.Fatalf("metadata runtime requirement = %q, want %q", got, test.want)
283
+ }
284
+ })
285
+ }
286
+ }
287
+
288
+ func TestCompactMetadataHidesGeneratedObjectFromBuiltinTransforms(t *testing.T) {
289
+ file := parseTestSourceFile(t, "/project/model.ts", `class Model {}`)
290
+ template, err := parseExpressionTemplate(`{kind: 16, classType: () => Model}`)
291
+ if err != nil {
292
+ t.Fatal(err)
293
+ }
294
+ plans := emissionPlans{file.FileName(): {
295
+ calls: map[int]callEmissionPlan{},
296
+ classes: map[int]classEmissionPlan{},
297
+ metadataTypes: []expressionTemplate{template},
298
+ metadataTypeResolver: "__tsf_metadata_type",
299
+ commonJS: true,
300
+ }}
301
+
302
+ transformed := metadataTransform(plans)(shimprinter.NewEmitContext(), file)
303
+ if got := len(collectAstNodes(transformed.AsNode(), shimast.KindObjectLiteralExpression)); got != 0 {
304
+ t.Fatalf("compact metadata left %d object literals in the emitted AST", got)
305
+ }
306
+ if astContainsIdentifier(transformed.AsNode(), "eval") {
307
+ t.Fatal("compact metadata emitted direct eval")
308
+ }
309
+ if !astContainsIdentifier(transformed.AsNode(), compactMetadataRegistryName) {
310
+ t.Fatal("compact metadata did not create a lazy runtime registry")
311
+ }
312
+ if got := len(collectAstNodes(transformed.AsNode(), shimast.KindImportDeclaration)); got != 1 {
313
+ t.Fatalf("generated imports = %d, want one decoder import", got)
314
+ }
315
+ }
316
+
317
+ func TestCompactMetadataEncoderExtractsRuntimeExpressions(t *testing.T) {
318
+ template, err := parseExpressionTemplate(`{
319
+ kind: 16,
320
+ classType: () => Model,
321
+ missing: undefined,
322
+ big: 12n,
323
+ nested: [true, -2, "two"]
324
+ }`)
325
+ if err != nil {
326
+ t.Fatal(err)
327
+ }
328
+ encoding := encodeCompactMetadata(template.parsed)
329
+ if got := len(encoding.references); got != 3 {
330
+ t.Fatalf("runtime references = %d, want 3", got)
331
+ }
332
+ for _, want := range []string{
333
+ `[1,{"kind":16`,
334
+ `"classType":{"$tsf":0}`,
335
+ `"missing":{"$tsf":1}`,
336
+ `"big":{"$tsf":2}`,
337
+ `"nested":[true,-2,"two"]`,
338
+ } {
339
+ if !strings.Contains(encoding.serialized, want) {
340
+ t.Fatalf("compact payload is missing %q: %s", want, encoding.serialized)
341
+ }
342
+ }
343
+ }
344
+
345
+ func TestCompactMetadataEncoderEscapesReferenceMarkerCollision(t *testing.T) {
346
+ for _, source := range []string{
347
+ `{"$tsf": 7}`,
348
+ `{"$tsfImport": [0, "Model"]}`,
349
+ `{"$tsfAlias": [0, "Alias", "Alias"]}`,
350
+ `{"$tsfType": 0}`,
351
+ } {
352
+ template, err := parseExpressionTemplate(source)
353
+ if err != nil {
354
+ t.Fatal(err)
355
+ }
356
+ encoding := encodeCompactMetadata(template.parsed)
357
+ if encoding.serialized != `[1,{"$tsf":0}]` {
358
+ t.Fatalf("compact payload for %s = %s", source, encoding.serialized)
359
+ }
360
+ if len(encoding.references) != 1 || encoding.references[0].Kind != shimast.KindObjectLiteralExpression {
361
+ t.Fatalf("marker-shaped application data %s was not escaped through a runtime slot", source)
362
+ }
363
+ }
364
+ }
365
+
366
+ func TestCompactMetadataEncodesCommonJSImportsAndAliasesAsRecipes(t *testing.T) {
367
+ file := parseTestSourceFile(t, "/project/consumer.ts", `export const value = 1`)
368
+ template, err := parseExpressionTemplate(`{
369
+ classType: () => __tsf_runtime_import__("./model", "Model", "/project/model.ts"),
370
+ alias: __tsf_runtime_alias__("@scope/types", "Alias", "RenamedAlias")
371
+ }`)
372
+ if err != nil {
373
+ t.Fatal(err)
374
+ }
375
+ ec := shimprinter.NewEmitContext()
376
+ imports := newAstImportRegistry(ec, file, true)
377
+ interner := newCompactMetadataRuntimeInterner(ec, file)
378
+ materialized := template.materialize(ec, imports)
379
+ encoding := encodeCompactMetadataWithRuntimeRecipes(
380
+ materialized,
381
+ interner.deduplicationKey,
382
+ func(encoder *compactMetadataEncoder, expression *shimast.Node) bool {
383
+ return writeCompactMetadataRuntimeRecipe(encoder, expression, imports, "")
384
+ },
385
+ )
386
+ if !strings.Contains(encoding.serialized, `"classType":{"$tsfImport":[0,"./model","Model"]}`) {
387
+ t.Fatalf("class import was not encoded as a recipe: %s", encoding.serialized)
388
+ }
389
+ if !strings.Contains(encoding.serialized, `"alias":{"$tsfAlias":[0,"@scope/types","Alias","RenamedAlias"]}`) {
390
+ t.Fatalf("alias was not encoded as a recipe: %s", encoding.serialized)
391
+ }
392
+ if len(encoding.references) != 1 {
393
+ t.Fatalf("module loader references = %d, want 1", len(encoding.references))
394
+ }
395
+ for _, reference := range encoding.references {
396
+ if !isCompactMetadataCommonJSRequire(reference, imports) {
397
+ t.Fatal("recipe emitted a per-type runtime expression instead of the file's require function")
398
+ }
399
+ }
400
+ }
401
+
402
+ func TestCompactMetadataEncoderDeduplicatesGeneratedRuntimeThunks(t *testing.T) {
403
+ file := parseTestSourceFile(t, "/project/model.ts", `class Model {}`)
404
+ template, err := parseExpressionTemplate(`{first: () => Model, second: () => Model}`)
405
+ if err != nil {
406
+ t.Fatal(err)
407
+ }
408
+ ec := shimprinter.NewEmitContext()
409
+ interner := newCompactMetadataRuntimeInterner(ec, file)
410
+ metadata := template.materialize(ec, newAstImportRegistry(ec, file, true))
411
+ encoding := encodeCompactMetadataWithReferenceKeys(metadata, interner.deduplicationKey)
412
+ if len(encoding.references) != 1 {
413
+ t.Fatalf("runtime references = %d, want 1", len(encoding.references))
414
+ }
415
+ if strings.Count(encoding.serialized, `{"$tsf":0}`) != 2 {
416
+ t.Fatalf("repeated thunk did not reuse one reference index: %s", encoding.serialized)
417
+ }
418
+ }
419
+
420
+ func TestMetadataTypeInternerKeepsRuntimeExpressionsAtUseSite(t *testing.T) {
421
+ interner := newMetadataTypeInterner("")
422
+ runtime := `{kind: 16, classType: () => LocalModel}`
423
+ if got := interner.reference(runtime); got != runtime {
424
+ t.Fatalf("runtime metadata was moved to module scope: %s", got)
425
+ }
426
+ if len(interner.expressions) != 0 {
427
+ t.Fatalf("shared runtime metadata expressions = %d, want 0", len(interner.expressions))
428
+ }
429
+
430
+ if got := interner.reference(`{kind: 6}`); got != "__tsf_metadata_type(0)" {
431
+ t.Fatalf("JSON metadata reference = %s", got)
432
+ }
433
+ if len(interner.expressions) != 1 {
434
+ t.Fatalf("shared JSON metadata expressions = %d, want 1", len(interner.expressions))
435
+ }
436
+ }
437
+
438
+ func TestCompactMetadataTypeRecipeAcceptsParsedTemplate(t *testing.T) {
439
+ template, err := parseExpressionTemplate(`__tsf_metadata_type(3)`)
440
+ if err != nil {
441
+ t.Fatal(err)
442
+ }
443
+ index, ok := compactMetadataTypeRecipe(template.parsed, "__tsf_metadata_type")
444
+ if !ok || index != 3 {
445
+ t.Fatalf("metadata type recipe = (%d, %t), want (3, true)", index, ok)
446
+ }
447
+ }
448
+
449
+ func TestCompactMetadataEncoderRollsBackUnsupportedNestedValues(t *testing.T) {
450
+ file := parseTestSourceFile(t, "/project/model.ts", `class Model {}`)
451
+ template, err := parseExpressionTemplate(`{
452
+ escaped: {value: () => Model, ...extra},
453
+ again: () => Model
454
+ }`)
455
+ if err != nil {
456
+ t.Fatal(err)
457
+ }
458
+ interner := newCompactMetadataRuntimeInterner(shimprinter.NewEmitContext(), file)
459
+ encoding := encodeCompactMetadataWithReferenceKeys(template.parsed, interner.deduplicationKey)
460
+ if encoding.serialized != `[1,{"escaped":{"$tsf":0},"again":{"$tsf":1}}]` {
461
+ t.Fatalf("compact payload retained a stale nested reference: %s", encoding.serialized)
462
+ }
463
+ if len(encoding.references) != 2 {
464
+ t.Fatalf("runtime references = %d, want 2", len(encoding.references))
465
+ }
466
+ }
467
+
468
+ func TestClassFromNodeRecognizesAmbientContext(t *testing.T) {
469
+ file := parseTestSourceFile(t, "/project/ambient.ts", `
470
+ declare namespace AmbientScope {
471
+ class Nested { value: string }
472
+ }
473
+ `)
474
+ classes := collectAstNodes(file.AsNode(), shimast.KindClassDeclaration)
475
+ if len(classes) != 1 {
476
+ t.Fatalf("class declarations = %d, want 1", len(classes))
477
+ }
478
+ info := &fileInfo{file: file}
479
+ class := classFromNode(info, classes[0])
480
+ if class == nil || !class.ambient {
481
+ t.Fatal("class nested in an ambient namespace was not marked ambient")
482
+ }
483
+ }
484
+
485
+ func TestPrecomputeSkipsAmbientClasses(t *testing.T) {
486
+ file := parseTestSourceFile(t, "/project/precompute.ts", ``)
487
+ ambient := &classInfo{ambient: true, properties: []propertyInfo{{typeText: "string"}}}
488
+ runtimeClass := &classInfo{properties: []propertyInfo{{typeText: "string"}}}
489
+ info := &fileInfo{
490
+ file: file,
491
+ moduleKey: moduleKey(file.FileName()),
492
+ precompute: true,
493
+ aliases: map[string]aliasInfo{},
494
+ interfaces: map[string][]interfaceInfo{},
495
+ enums: map[string]enumInfo{},
496
+ classes: []*classInfo{ambient, runtimeClass},
497
+ imports: map[string]importRef{},
498
+ reexports: map[string]importRef{},
499
+ }
500
+ reg := &registry{
501
+ files: map[string]*fileInfo{file.FileName(): info},
502
+ byPath: map[string]*fileInfo{info.moduleKey: info},
503
+ typiaCache: map[typiaCacheKey]string{},
504
+ typiaFailures: map[*shimchecker.Type]bool{},
505
+ classes: map[string]*classInfo{},
506
+ external: map[string]map[string][]functionInfo{},
507
+ }
508
+
509
+ precomputeMetadataExpressions(reg, true)
510
+
511
+ if ambient.properties[0].metadataText != "" {
512
+ t.Fatal("ambient class metadata was precomputed")
513
+ }
514
+ if runtimeClass.properties[0].metadataText == "" {
515
+ t.Fatal("runtime class metadata was not precomputed")
516
+ }
517
+ }
518
+
519
+ func parseTestSourceFile(t *testing.T, fileName string, source string) *shimast.SourceFile {
520
+ t.Helper()
521
+ file := shimparser.ParseSourceFile(
522
+ shimast.SourceFileParseOptions{FileName: filepath.ToSlash(fileName)},
523
+ source,
524
+ shimcore.ScriptKindTS,
525
+ )
526
+ if file == nil || len(file.Diagnostics()) != 0 {
527
+ t.Fatalf("failed to parse test source %s", fileName)
528
+ }
529
+ return file
530
+ }
531
+
532
+ func collectAstNodes(root *shimast.Node, kind shimast.Kind) []*shimast.Node {
533
+ out := []*shimast.Node{}
534
+ var walk func(*shimast.Node)
535
+ walk = func(node *shimast.Node) {
536
+ if node == nil {
537
+ return
538
+ }
539
+ if node.Kind == kind {
540
+ out = append(out, node)
541
+ }
542
+ node.ForEachChild(func(child *shimast.Node) bool {
543
+ walk(child)
544
+ return false
545
+ })
546
+ }
547
+ walk(root)
548
+ return out
549
+ }
550
+
551
+ func astContainsIdentifier(root *shimast.Node, name string) bool {
552
+ for _, node := range collectAstNodes(root, shimast.KindIdentifier) {
553
+ if node.Text() == name {
554
+ return true
555
+ }
556
+ }
557
+ return false
558
+ }
@@ -0,0 +1,10 @@
1
+ module tsf-type-replacement-plugin
2
+
3
+ go 1.26
4
+
5
+ require (
6
+ // ttsc replaces this placeholder with the installed npm package's Go sources
7
+ // while building source plugins. The nested Go module has no semver tags.
8
+ github.com/samchon/ttsc/packages/ttsc v0.0.0
9
+ github.com/samchon/typia/packages/typia/native v0.0.0-20260709031533-27f105ae8e56
10
+ )