typia 13.2.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/lib/http.d.ts +3 -3
  2. package/lib/http.js.map +1 -1
  3. package/lib/http.mjs.map +1 -1
  4. package/lib/internal/_assertGuard.js +13 -3
  5. package/lib/internal/_assertGuard.js.map +1 -1
  6. package/lib/internal/_assertGuard.mjs +4 -2
  7. package/lib/internal/_assertGuard.mjs.map +1 -1
  8. package/lib/internal/_isFormatIdnHostname.js +5 -1
  9. package/lib/internal/_isFormatIdnHostname.js.map +1 -1
  10. package/lib/internal/_isFormatIdnHostname.mjs +1 -1
  11. package/lib/internal/_isFormatIdnHostname.mjs.map +1 -1
  12. package/lib/internal/_isFormatTime.js +19 -2
  13. package/lib/internal/_isFormatTime.js.map +1 -1
  14. package/lib/internal/_isFormatTime.mjs +9 -2
  15. package/lib/internal/_isFormatTime.mjs.map +1 -1
  16. package/lib/internal/_randomArray.js +37 -4
  17. package/lib/internal/_randomArray.js.map +1 -1
  18. package/lib/internal/_randomArray.mjs +16 -3
  19. package/lib/internal/_randomArray.mjs.map +1 -1
  20. package/lib/internal/_randomFormatByte.js +15 -12
  21. package/lib/internal/_randomFormatByte.js.map +1 -1
  22. package/lib/internal/_randomFormatByte.mjs +11 -10
  23. package/lib/internal/_randomFormatByte.mjs.map +1 -1
  24. package/lib/internal/_randomFormatDatetime.mjs +1 -1
  25. package/lib/internal/_randomFormatHostname.d.ts +2 -2
  26. package/lib/internal/_randomFormatHostname.js +2 -2
  27. package/lib/internal/_randomFormatHostname.mjs +2 -2
  28. package/lib/internal/_randomFormatHostname.mjs.map +1 -1
  29. package/lib/internal/_randomFormatIdnHostname.js +9 -10
  30. package/lib/internal/_randomFormatIdnHostname.js.map +1 -1
  31. package/lib/internal/_randomFormatIdnHostname.mjs +3 -4
  32. package/lib/internal/_randomFormatIdnHostname.mjs.map +1 -1
  33. package/lib/internal/_randomFormatRegex.js +4 -4
  34. package/lib/internal/_randomFormatRegex.js.map +1 -1
  35. package/lib/internal/_randomFormatRegex.mjs +1 -2
  36. package/lib/internal/_randomFormatRegex.mjs.map +1 -1
  37. package/lib/internal/_randomFormatTime.js +15 -10
  38. package/lib/internal/_randomFormatTime.js.map +1 -1
  39. package/lib/internal/_randomFormatTime.mjs +6 -3
  40. package/lib/internal/_randomFormatTime.mjs.map +1 -1
  41. package/lib/internal/_randomPattern.js +20 -3
  42. package/lib/internal/_randomPattern.js.map +1 -1
  43. package/lib/internal/_randomPattern.mjs +5 -1
  44. package/lib/internal/_randomPattern.mjs.map +1 -1
  45. package/lib/internal/_randomStringLength.js +9 -2
  46. package/lib/internal/_randomStringLength.js.map +1 -1
  47. package/lib/internal/_randomStringLength.mjs +3 -1
  48. package/lib/internal/_randomStringLength.mjs.map +1 -1
  49. package/lib/internal/private/__randomComposition.d.ts +8 -0
  50. package/lib/internal/private/__randomComposition.js +21 -1
  51. package/lib/internal/private/__randomComposition.js.map +1 -1
  52. package/lib/internal/private/__randomComposition.mjs +18 -1
  53. package/lib/internal/private/__randomComposition.mjs.map +1 -1
  54. package/lib/json.d.ts +2 -2
  55. package/lib/json.js.map +1 -1
  56. package/lib/json.mjs.map +1 -1
  57. package/lib/module.d.ts +28 -13
  58. package/lib/module.js.map +1 -1
  59. package/lib/module.mjs.map +1 -1
  60. package/lib/notations.d.ts +4 -4
  61. package/lib/notations.js.map +1 -1
  62. package/lib/notations.mjs.map +1 -1
  63. package/lib/plain.d.ts +3 -3
  64. package/lib/plain.js.map +1 -1
  65. package/lib/plain.mjs.map +1 -1
  66. package/lib/protobuf.d.ts +2 -2
  67. package/lib/protobuf.js.map +1 -1
  68. package/lib/protobuf.mjs.map +1 -1
  69. package/lib/transform.mjs +1 -1
  70. package/native/cmd/ttsc-typia/assert_guard_factory_contract_test.go +17 -4
  71. package/native/cmd/ttsc-typia/create_assert_error_factory_arity_test.go +309 -0
  72. package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +206 -0
  73. package/native/cmd/ttsc-typia/local_function_named_like_typia_operation_transform_test.go +185 -0
  74. package/native/cmd/ttsc-typia/notation_kebab_case_transform_test.go +2 -2
  75. package/native/cmd/ttsc-typia/project_catalog_transform_coverage_test.go +84 -85
  76. package/native/cmd/ttsc-typia/recursive_conditional_alias_name_transform_test.go +307 -0
  77. package/native/cmd/ttsc-typia/shadowed_typia_module_declaration_diagnostic_test.go +152 -0
  78. package/native/cmd/ttsc-typia/transform.go +2 -2
  79. package/native/cmd/ttsc-typia/transform_helpers_test.go +96 -18
  80. package/native/core/factories/FormatCheatSheet.go +2 -2
  81. package/native/core/factories/MetadataCommentTagFactory.go +91 -7
  82. package/native/core/factories/factory_utility_coverage_test.go +896 -907
  83. package/native/core/factories/internal/metadata/MetadataHelper.go +5 -28
  84. package/native/core/factories/metadata_comment_tag_factory_rejects_out_of_range_integer_test.go +124 -0
  85. package/native/core/programmers/AssertProgrammer.go +20 -6
  86. package/native/core/programmers/RandomProgrammer.go +78 -3
  87. package/native/core/programmers/ValidateProgrammer.go +21 -6
  88. package/native/core/programmers/helpers/RandomJoiner.go +8 -1
  89. package/native/core/programmers/iterate/check_dynamic_properties.go +177 -3
  90. package/native/core/programmers/iterate/check_object.go +12 -1
  91. package/native/core/schemas/metadata/MetadataCollection.go +47 -5
  92. package/native/transform/CallExpressionTransformer.go +167 -0
  93. package/package.json +3 -12
  94. package/src/http.ts +18 -4
  95. package/src/internal/_assertGuard.ts +13 -2
  96. package/src/internal/_isFormatIdnHostname.ts +5 -1
  97. package/src/internal/_isFormatTime.ts +19 -2
  98. package/src/internal/_randomArray.ts +42 -6
  99. package/src/internal/_randomFormatByte.ts +17 -12
  100. package/src/internal/_randomFormatHostname.ts +2 -2
  101. package/src/internal/_randomFormatIdnHostname.ts +8 -9
  102. package/src/internal/_randomFormatRegex.ts +4 -6
  103. package/src/internal/_randomFormatTime.ts +15 -10
  104. package/src/internal/_randomPattern.ts +18 -3
  105. package/src/internal/_randomStringLength.ts +9 -2
  106. package/src/internal/private/__randomComposition.ts +24 -0
  107. package/src/json.ts +16 -4
  108. package/src/module.ts +56 -22
  109. package/src/notations.ts +16 -4
  110. package/src/plain.ts +12 -3
  111. package/src/protobuf.ts +16 -4
  112. package/lib/executable/FileSystemIdentity.d.ts +0 -44
  113. package/lib/executable/FileSystemIdentity.js +0 -213
  114. package/lib/executable/FileSystemIdentity.js.map +0 -1
  115. package/lib/executable/FileSystemIdentity.mjs +0 -133
  116. package/lib/executable/FileSystemIdentity.mjs.map +0 -1
  117. package/lib/executable/TypiaGenerateWizard.d.ts +0 -9
  118. package/lib/executable/TypiaGenerateWizard.js +0 -852
  119. package/lib/executable/TypiaGenerateWizard.js.map +0 -1
  120. package/lib/executable/TypiaGenerateWizard.mjs +0 -618
  121. package/lib/executable/TypiaGenerateWizard.mjs.map +0 -1
  122. package/lib/executable/generate/ttsc.d.ts +0 -1
  123. package/lib/executable/generate/ttsc.js +0 -53
  124. package/lib/executable/generate/ttsc.js.map +0 -1
  125. package/lib/executable/generate/ttsc.mjs +0 -37
  126. package/lib/executable/generate/ttsc.mjs.map +0 -1
  127. package/lib/executable/typia.d.ts +0 -2
  128. package/lib/executable/typia.js +0 -84
  129. package/lib/executable/typia.js.map +0 -1
  130. package/lib/executable/typia.mjs +0 -60
  131. package/lib/executable/typia.mjs.map +0 -1
  132. package/src/executable/FileSystemIdentity.ts +0 -222
  133. package/src/executable/TypiaGenerateWizard.ts +0 -1133
  134. package/src/executable/generate/ttsc.ts +0 -70
  135. package/src/executable/typia.ts +0 -80
@@ -0,0 +1,206 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "os/exec"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ // TestDynamicKeyTagsTransform verifies a type tag on a dynamic key is enforced.
12
+ //
13
+ // `Check_dynamic_key` has always held a complete implementation: it filters the
14
+ // key's tag rows through `check_dynamic_key_fully_validated_tag_rows` and routes
15
+ // a tagged string atomic into `Check_string`, a tagged numeric one into
16
+ // `Check_number`. Nothing reached it, so `[key: string & tags.MinLength<3>]`
17
+ // accepted a two-character key and every other key tag was equally inert
18
+ // (#2347).
19
+ //
20
+ // Values were always checked, which is what made this invisible: the
21
+ // `@typia/template` `DynamicTag` fixture declares exactly this shape and every
22
+ // one of its spoilers corrupts a value, never a key.
23
+ //
24
+ // 1. Transform a fixture whose index signatures carry a pattern, a length, and
25
+ // a numeric range on the key.
26
+ // 2. Require the emitted checker to contain a key predicate at all.
27
+ // 3. Execute runtime cases: a key violating its tag must be rejected, a key
28
+ // satisfying it accepted, and neither a plain `[key: string]` signature nor
29
+ // a template literal one may become constrained.
30
+ //
31
+ // The two negative controls carry the boundary. What the fix makes binding is a
32
+ // broken *tag* on a key the declaration covers; a key no signature's *type*
33
+ // covers is declared nowhere, which is what a surplus property is, and stays
34
+ // accepted. `test_validate_dynamic_key_surplus` pins that distinction against
35
+ // both spellings of the same predicate.
36
+ //
37
+ // The report such a key produces is its own concern. `Superfluous`, the report
38
+ // an extra property already used, says the property is not defined in the object
39
+ // type and advises removing it -- false and unhelpful when the property is
40
+ // declared and only its key broke a constraint. `InvalidKey` names the key type
41
+ // instead, and `test_validate_dynamic_key_report` covers the wording.
42
+ func TestDynamicKeyTagsTransform(t *testing.T) {
43
+ project := dynamicKeyTagsProject(t)
44
+ out, errText, code := ttscTypiaTestCapture(func() int {
45
+ return runTransform([]string{
46
+ "--cwd", project,
47
+ "--tsconfig", "tsconfig.json",
48
+ "--file", "src/main.ts",
49
+ "--output", "js",
50
+ })
51
+ })
52
+ if code != 0 {
53
+ t.Fatalf("dynamic key tags transform failed: code=%d stderr=\n%s", code, errText)
54
+ }
55
+ // The key is read into a local before it is tested, so its predicate is the
56
+ // one place the emitted text can show the check exists at all.
57
+ if !strings.Contains(out, "RegExp(") || !strings.Contains(out, "_stringLength") {
58
+ t.Fatalf("emitted checker should test the dynamic key, not only its value:\n%s", out)
59
+ }
60
+ dynamicKeyTagsRunRuntimeCases(t, project, out)
61
+ }
62
+
63
+ func dynamicKeyTagsProject(t *testing.T) string {
64
+ t.Helper()
65
+ root := ttscTypiaTestRepoRoot(t)
66
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
67
+ if err := os.MkdirAll(base, 0o755); err != nil {
68
+ t.Fatalf("mkdir temp base: %v", err)
69
+ }
70
+ dir, err := os.MkdirTemp(base, "dynamic-key-tags-")
71
+ if err != nil {
72
+ t.Fatalf("create temp fixture: %v", err)
73
+ }
74
+ t.Cleanup(func() {
75
+ _ = os.RemoveAll(dir)
76
+ })
77
+ src := filepath.Join(dir, "src")
78
+ if err := os.MkdirAll(src, 0o755); err != nil {
79
+ t.Fatalf("mkdir fixture src: %v", err)
80
+ }
81
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(dynamicKeyTagsTSConfig), 0o644); err != nil {
82
+ t.Fatalf("write tsconfig: %v", err)
83
+ }
84
+ if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(dynamicKeyTagsSource), 0o644); err != nil {
85
+ t.Fatalf("write source: %v", err)
86
+ }
87
+ return dir
88
+ }
89
+
90
+ func dynamicKeyTagsRunRuntimeCases(t *testing.T, project string, js string) {
91
+ t.Helper()
92
+ node, err := exec.LookPath("node")
93
+ if err != nil {
94
+ t.Skip("node executable not found")
95
+ }
96
+ runtimeDir := filepath.Join(project, "runtime")
97
+ if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
98
+ t.Fatalf("mkdir runtime dir: %v", err)
99
+ }
100
+ ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
101
+ runtimeJS := ttscTypiaTestRewriteCommonJS(t, js)
102
+ if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
103
+ t.Fatalf("write runtime module: %v", err)
104
+ }
105
+ runner := filepath.Join(runtimeDir, "run.cjs")
106
+ if err := os.WriteFile(runner, []byte(dynamicKeyTagsRuntimeRunner), 0o644); err != nil {
107
+ t.Fatalf("write runtime runner: %v", err)
108
+ }
109
+ cmd := exec.Command(node, runner)
110
+ cmd.Dir = runtimeDir
111
+ output, err := cmd.CombinedOutput()
112
+ if err != nil {
113
+ t.Fatalf("dynamic key tags runtime cases failed: %v\n%s", err, output)
114
+ }
115
+ }
116
+
117
+ const dynamicKeyTagsTSConfig = `{
118
+ "compilerOptions": {
119
+ "target": "ES2022",
120
+ "module": "commonjs",
121
+ "moduleResolution": "bundler",
122
+ "ignoreDeprecations": "6.0",
123
+ "types": ["*"],
124
+ "esModuleInterop": true,
125
+ "strict": true,
126
+ "skipLibCheck": true
127
+ },
128
+ "include": ["src"]
129
+ }
130
+ `
131
+
132
+ const dynamicKeyTagsSource = `import typia, { tags } from "typia";
133
+
134
+ interface IPatternKey {
135
+ [key: string & tags.Pattern<"^ab+$">]: string;
136
+ }
137
+ interface ILengthKey {
138
+ [key: string & tags.MinLength<3>]: string;
139
+ }
140
+ interface INumericKey {
141
+ [key: number & tags.Minimum<0> & tags.Maximum<9>]: string;
142
+ }
143
+ interface IPlainKey {
144
+ [key: string]: string;
145
+ }
146
+ interface ITemplateKey {
147
+ [key: ` + "`" + `prefix_${string}` + "`" + `]: string;
148
+ }
149
+
150
+ export const isPatternKey = typia.createIs<IPatternKey>();
151
+ export const isLengthKey = typia.createIs<ILengthKey>();
152
+ export const isNumericKey = typia.createIs<INumericKey>();
153
+ export const isPlainKey = typia.createIs<IPlainKey>();
154
+ export const isTemplateKey = typia.createIs<ITemplateKey>();
155
+ `
156
+
157
+ const dynamicKeyTagsRuntimeRunner = `const assert = require("assert");
158
+ const main = require("./main.cjs");
159
+
160
+ // A key that satisfies its tag is accepted; one that violates it is not. Both
161
+ // directions matter: before the fix every one of these returned true.
162
+ assert.strictEqual(main.isPatternKey({ abb: "x" }), true, "pattern key accepted");
163
+ assert.strictEqual(main.isPatternKey({ zzz: "x" }), false, "non-pattern key rejected");
164
+
165
+ assert.strictEqual(main.isLengthKey({ abc: "x" }), true, "long enough key accepted");
166
+ assert.strictEqual(main.isLengthKey({ ab: "x" }), false, "too short key rejected");
167
+
168
+ // The length is counted in characters, as MinLength means everywhere else: two
169
+ // astral characters are four code units but two characters, so they are short.
170
+ assert.strictEqual(
171
+ main.isLengthKey({ "\u{1f600}\u{1f600}\u{1f600}": "x" }),
172
+ true,
173
+ "three astral characters accepted",
174
+ );
175
+ assert.strictEqual(
176
+ main.isLengthKey({ "\u{1f600}\u{1f600}": "x" }),
177
+ false,
178
+ "two astral characters rejected",
179
+ );
180
+
181
+ assert.strictEqual(main.isNumericKey({ 5: "x" }), true, "in-range numeric key accepted");
182
+ assert.strictEqual(main.isNumericKey({ 10: "x" }), false, "out-of-range numeric key rejected");
183
+ assert.strictEqual(main.isNumericKey({ "-1": "x" }), false, "negative numeric key rejected");
184
+
185
+ // NEGATIVE CONTROL: an untagged signature stays unconstrained, so the fix
186
+ // constrains only what was declared.
187
+ assert.strictEqual(main.isPlainKey({ "anything at all": "x" }), true, "plain key accepted");
188
+ assert.strictEqual(main.isPlainKey({ "": "x" }), true, "empty plain key accepted");
189
+
190
+ // NEGATIVE CONTROL: a template literal constrains the key's *type*, not a tag on
191
+ // it, so a key outside it is declared nowhere -- a surplus property, which "is"
192
+ // accepts as it accepts any surplus property. Rejecting this one alongside a
193
+ // broken tag is what the first attempt at the fix did, and it made the
194
+ // equals-mode surplus report unreachable for the DynamicTemplate, DynamicUnion,
195
+ // and DynamicComposite shapes.
196
+ assert.strictEqual(main.isTemplateKey({ prefix_a: "x" }), true, "matching template key accepted");
197
+ assert.strictEqual(main.isTemplateKey({ prefix_a: 1 }), false, "matching template key with a wrong value rejected");
198
+ assert.strictEqual(
199
+ main.isTemplateKey({ prefix_a: "x", wrong: "y" }),
200
+ true,
201
+ "a key outside the template is surplus, not a violation",
202
+ );
203
+
204
+ // The value is still checked, which it always was.
205
+ assert.strictEqual(main.isLengthKey({ abc: 1 }), false, "wrong value type rejected");
206
+ `
@@ -0,0 +1,185 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ // TestLocalFunctionNamedLikeTypiaOperationTransform verifies the shadowed-typia
12
+ // diagnostic does not fire on a name collision (#2328).
13
+ //
14
+ // The diagnostic that reports a redeclared typia call keys off the called name,
15
+ // and those names are ordinary words: a project may well own its own
16
+ // `createAssert`, import one from an unrelated package, or augment typia with a
17
+ // name that only exists under one of its namespaces. Reporting any of them
18
+ // would break builds that are correct today, so the name alone must never be
19
+ // enough — the callee has to reach typia through typia's own module specifier
20
+ // and spell the operation in the namespace it lives in.
21
+ //
22
+ // 1. Build a project that defines a local `createAssert`, imports a `createIs`
23
+ // from a relative module and a `createValidate` from a package whose name
24
+ // merely begins with "typia", and augments typia with a root `parse` — an
25
+ // operation name that exists only under a namespace.
26
+ // 2. Require a clean no-emit build, which proves every decoy really resolves,
27
+ // and a project transform with no diagnostic.
28
+ // 3. Require the one genuine `typia.createIs` call in the same file to still
29
+ // be rewritten, so silence is not the transform giving up on the file.
30
+ func TestLocalFunctionNamedLikeTypiaOperationTransform(t *testing.T) {
31
+ project := localTypiaNameCollisionProject(t)
32
+
33
+ // A no-emit build reports semantic diagnostics as well as typia's own, so a
34
+ // decoy that silently stopped resolving fails here instead of turning the
35
+ // rest of this test into a tautology.
36
+ out, errText, code := ttscTypiaTestCapture(func() int {
37
+ return runBuild([]string{
38
+ "--cwd", project,
39
+ "--tsconfig", "tsconfig.json",
40
+ "--noEmit",
41
+ })
42
+ })
43
+ if code != 0 {
44
+ t.Fatalf("name collision fixture is not clean, got %d\nstdout=%s\nstderr=%s", code, out, errText)
45
+ }
46
+
47
+ out, errText, code = ttscTypiaTestCapture(func() int {
48
+ return runTransform([]string{
49
+ "--cwd", project,
50
+ "--tsconfig", "tsconfig.json",
51
+ })
52
+ })
53
+ if code != 0 {
54
+ t.Fatalf("name collisions must not fail the transform, got %d\nstdout=%s\nstderr=%s", code, out, errText)
55
+ }
56
+ var result transformProjectOutput
57
+ if err := json.Unmarshal([]byte(out), &result); err != nil {
58
+ t.Fatalf("decode transform output: %v\n%s", err, out)
59
+ }
60
+ if len(result.Diagnostics) != 0 {
61
+ t.Fatalf("name collisions produced diagnostics: %+v", result.Diagnostics)
62
+ }
63
+
64
+ js, errText, code := ttscTypiaTestCapture(func() int {
65
+ return runTransform([]string{
66
+ "--cwd", project,
67
+ "--tsconfig", "tsconfig.json",
68
+ "--file", "src/main.ts",
69
+ "--output", "js",
70
+ })
71
+ })
72
+ if code != 0 {
73
+ t.Fatalf("name collision fixture failed to emit: code=%d stderr=\n%s", code, errText)
74
+ }
75
+ // The genuine call becomes an inline validator; the decoys stay ordinary
76
+ // calls. Type arguments are erased either way, so the emitted body is the
77
+ // only evidence that the transform ran on one and not the others.
78
+ if !strings.Contains(js, `"number" === typeof`) {
79
+ t.Fatalf("the genuine typia call did not emit its validator:\n%s", js)
80
+ }
81
+ for _, decoy := range []string{"createAssert()", "createIs)()", "createValidate)()", "parse)(\"{}\")"} {
82
+ if !strings.Contains(js, decoy) {
83
+ t.Fatalf("decoy call %q was rewritten or lost:\n%s", decoy, js)
84
+ }
85
+ }
86
+ }
87
+
88
+ func localTypiaNameCollisionProject(t *testing.T) string {
89
+ t.Helper()
90
+ dir := t.TempDir()
91
+ src := filepath.Join(dir, "src")
92
+ if err := os.MkdirAll(src, 0o755); err != nil {
93
+ t.Fatalf("mkdir fixture src: %v", err)
94
+ }
95
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(transformDiagnosticTSConfig), 0o644); err != nil {
96
+ t.Fatalf("write tsconfig: %v", err)
97
+ }
98
+ ttscTypiaTestWriteFactoryStub(t, dir)
99
+ localTypiaNameCollisionWriteNeighborStub(t, dir)
100
+ for name, body := range map[string]string{
101
+ "helpers.ts": localTypiaNameCollisionHelpers,
102
+ "augmentation.ts": localTypiaNameCollisionAugmentation,
103
+ "main.ts": localTypiaNameCollisionSource,
104
+ } {
105
+ if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
106
+ t.Fatalf("write fixture %s: %v", name, err)
107
+ }
108
+ }
109
+ return dir
110
+ }
111
+
112
+ // localTypiaNameCollisionWriteNeighborStub installs a package whose name merely
113
+ // begins with "typia" and exports an operation name typia owns. It pins the
114
+ // one-past-boundary of the specifier test: the call reaches the specifier check
115
+ // under a name that matches, and only the specifier can reject it.
116
+ func localTypiaNameCollisionWriteNeighborStub(t *testing.T, project string) {
117
+ t.Helper()
118
+ root := filepath.Join(project, "node_modules", "typia-neighbor")
119
+ if err := os.MkdirAll(root, 0o755); err != nil {
120
+ t.Fatalf("mkdir typia-neighbor stub: %v", err)
121
+ }
122
+ files := map[string]string{
123
+ "package.json": `{
124
+ "name": "typia-neighbor",
125
+ "version": "0.0.0-test",
126
+ "main": "./index.js",
127
+ "types": "./index.d.ts",
128
+ "exports": {
129
+ ".": {
130
+ "types": "./index.d.ts",
131
+ "default": "./index.js"
132
+ }
133
+ }
134
+ }
135
+ `,
136
+ "index.d.ts": "export declare function createValidate<T>(): (input: unknown) => T;\n",
137
+ "index.js": "exports.createValidate = () => (input) => input;\n",
138
+ }
139
+ for name, body := range files {
140
+ if err := os.WriteFile(filepath.Join(root, name), []byte(body), 0o644); err != nil {
141
+ t.Fatalf("write typia-neighbor stub %s: %v", name, err)
142
+ }
143
+ }
144
+ }
145
+
146
+ const localTypiaNameCollisionHelpers = `export const createIs =
147
+ <T,>() =>
148
+ (input: unknown): input is T =>
149
+ input !== undefined;
150
+ `
151
+
152
+ // A module augmentation that adds a root-level `parse` to typia. The leading
153
+ // `import "typia"` makes this file a module, so the block merges with the
154
+ // installed typia instead of replacing it. `parse` is an operation name under
155
+ // the `llm` namespace and nowhere else, so a gate that matched a flat union of
156
+ // every namespace's names would report this call — a build that works today.
157
+ const localTypiaNameCollisionAugmentation = `import "typia";
158
+
159
+ declare module "typia" {
160
+ export function parse(text: string): unknown;
161
+ }
162
+ `
163
+
164
+ const localTypiaNameCollisionSource = `import { createValidate } from "typia-neighbor";
165
+ import { parse } from "typia";
166
+ import typia from "typia";
167
+
168
+ import "./augmentation";
169
+ import { createIs } from "./helpers";
170
+
171
+ export interface User {
172
+ id: number;
173
+ }
174
+
175
+ const createAssert =
176
+ <T,>() =>
177
+ (input: unknown): T =>
178
+ input as T;
179
+
180
+ export const localAssert = createAssert<User>();
181
+ export const relativeIs = createIs<User>();
182
+ export const neighborValidate = createValidate<User>();
183
+ export const augmented = parse("{}");
184
+ export const genuineIs = typia.createIs<User>();
185
+ `
@@ -18,8 +18,8 @@ import (
18
18
  // 1. Transform a fixture covering camelCase, snake_case, leading-underscore,
19
19
  // consecutive-uppercase (the acronym run collapses, so `XMLParser` becomes
20
20
  // `xmlparser`), and nested property names. The matching compile-time
21
- // `KebabCase<T>` assertions live in the test-typia-generate fixture, where
22
- // ttsc enforces type diagnostics.
21
+ // `KebabCase<T>` assertions live in the test-interface fixture, where ttsc
22
+ // enforces type diagnostics.
23
23
  // 2. Require the emitted converter to reference the kebab-case keys.
24
24
  // 3. Execute kebab, isKebab, assertKebab, and validateKebab runtime cases
25
25
  // over valid and invalid inputs.
@@ -4,16 +4,16 @@
4
4
  package main
5
5
 
6
6
  import (
7
- "encoding/json"
8
- "path/filepath"
9
- "strings"
10
- "testing"
7
+ "encoding/json"
8
+ "path/filepath"
9
+ "strings"
10
+ "testing"
11
11
  )
12
12
 
13
13
  // TestProjectCatalogTransformCoverage reuses broad TypeScript test projects.
14
14
  //
15
15
  // The focused ttsc fixtures are intentionally small, but the repository already
16
- // has schema and generation projects that exercise many more typia API shapes.
16
+ // has feature projects that exercise many more typia API shapes.
17
17
  // Running them through the same project transform mode gives Go coverage for
18
18
  // those native branches without duplicating that TypeScript corpus here.
19
19
  //
@@ -31,89 +31,88 @@ import (
31
31
  // 3. Require JSON project output to include a TypeScript map.
32
32
  // 4. Hold each project to its own purpose's diagnostic contract.
33
33
  func TestProjectCatalogTransformCoverage(t *testing.T) {
34
- root := transformCoverageRepoRoot(t)
35
- for _, project := range []string{
36
- "template",
37
- "test-langchain",
38
- "test-mcp",
39
- "test-typia-automated",
40
- "test-typia-generate",
41
- "test-typia-schema",
42
- "test-utils",
43
- "test-utils-automated",
44
- "test-vercel",
45
- } {
46
- project := project
47
- t.Run(project, func(t *testing.T) {
48
- out, errText, code := transformCatalogProject(root, project)
49
- if code != 0 {
50
- t.Fatalf("%s transform failed: code=%d stderr=\n%s", project, code, errText)
51
- }
52
- if !strings.Contains(out, `"typescript"`) {
53
- t.Fatalf("%s project output should include TypeScript map:\n%s", project, out)
54
- }
55
- })
56
- }
57
- // tests/test-error compiles deliberately invalid call sites, and reporting them
58
- // is that workspace's entire reason to exist. Its diagnostics are a contract,
59
- // not an accident of what it currently holds.
60
- t.Run("test-error", func(t *testing.T) {
61
- out, _, code := transformCatalogProject(root, "test-error")
62
- if code != 3 {
63
- t.Fatalf("test-error transform should report diagnostics with code 3, got %d", code)
64
- }
65
- if !strings.Contains(out, `"diagnostics"`) || !strings.Contains(out, `"typescript"`) {
66
- t.Fatalf("test-error output should include diagnostics and TypeScript map:\n%s", out)
67
- }
68
- })
69
- // tests/debug is a one-off repro scratchpad, so nothing about its sources is
70
- // promised: it holds valid code between investigations and invalid code during
71
- // one. What survives both states is the project transform's own envelope
72
- // contract, so that is what this pins. The project must resolve and reach a
73
- // transform decision rather than fail to load (exit 2), it must still
74
- // contribute transformed sources, and its exit code must agree with the
75
- // envelope it published.
76
- //
77
- // Accepting either 0 or 3 is derived, not observed: transform mode never
78
- // typechecks, so exit 2 is reachable only from a flag, cwd, or tsconfig-load
79
- // failure (semantic diagnostics belong to the build route). No source content a
80
- // repro leaves behind can produce it -- even an outright type error still exits
81
- // 0 here -- which is what makes the band content-agnostic.
82
- //
83
- // The envelope is decoded rather than substring-matched because `"typescript"`
84
- // carries no `omitempty` and runTransformProject initializes the map before its
85
- // source loop: the key is present even when the workspace resolved no source at
86
- // all. That empty envelope is the shape a `rootDir` or `include` rot leaves
87
- // behind, and this workspace is the one that already shipped such a rot
88
- // (samchon/typia#2132), so a key-presence check would pass over the one failure
89
- // worth catching here.
90
- t.Run("debug", func(t *testing.T) {
91
- out, errText, code := transformCatalogProject(root, "debug")
92
- if code != 0 && code != 3 {
93
- t.Fatalf("debug transform should load and transform the project, got code=%d stderr=\n%s", code, errText)
94
- }
95
- var envelope transformProjectOutput
96
- if err := json.Unmarshal([]byte(out), &envelope); err != nil {
97
- t.Fatalf("debug project output should be a transform envelope: %v\n%s", err, out)
98
- }
99
- if len(envelope.TypeScript) == 0 {
100
- t.Fatalf("debug project should contribute transformed sources, got an empty TypeScript map:\n%s", out)
101
- }
102
- if diagnosed := len(envelope.Diagnostics) > 0; diagnosed != (code == 3) {
103
- t.Fatalf("debug exit code %d disagrees with its envelope (diagnostics present: %v):\n%s", code, diagnosed, out)
104
- }
105
- })
34
+ root := transformCoverageRepoRoot(t)
35
+ for _, project := range []string{
36
+ "template",
37
+ "test-langchain",
38
+ "test-mcp",
39
+ "test-typia-automated",
40
+ "test-typia-schema",
41
+ "test-utils",
42
+ "test-utils-automated",
43
+ "test-vercel",
44
+ } {
45
+ project := project
46
+ t.Run(project, func(t *testing.T) {
47
+ out, errText, code := transformCatalogProject(root, project)
48
+ if code != 0 {
49
+ t.Fatalf("%s transform failed: code=%d stderr=\n%s", project, code, errText)
50
+ }
51
+ if !strings.Contains(out, `"typescript"`) {
52
+ t.Fatalf("%s project output should include TypeScript map:\n%s", project, out)
53
+ }
54
+ })
55
+ }
56
+ // tests/test-error compiles deliberately invalid call sites, and reporting them
57
+ // is that workspace's entire reason to exist. Its diagnostics are a contract,
58
+ // not an accident of what it currently holds.
59
+ t.Run("test-error", func(t *testing.T) {
60
+ out, _, code := transformCatalogProject(root, "test-error")
61
+ if code != 3 {
62
+ t.Fatalf("test-error transform should report diagnostics with code 3, got %d", code)
63
+ }
64
+ if !strings.Contains(out, `"diagnostics"`) || !strings.Contains(out, `"typescript"`) {
65
+ t.Fatalf("test-error output should include diagnostics and TypeScript map:\n%s", out)
66
+ }
67
+ })
68
+ // tests/debug is a one-off repro scratchpad, so nothing about its sources is
69
+ // promised: it holds valid code between investigations and invalid code during
70
+ // one. What survives both states is the project transform's own envelope
71
+ // contract, so that is what this pins. The project must resolve and reach a
72
+ // transform decision rather than fail to load (exit 2), it must still
73
+ // contribute transformed sources, and its exit code must agree with the
74
+ // envelope it published.
75
+ //
76
+ // Accepting either 0 or 3 is derived, not observed: transform mode never
77
+ // typechecks, so exit 2 is reachable only from a flag, cwd, or tsconfig-load
78
+ // failure (semantic diagnostics belong to the build route). No source content a
79
+ // repro leaves behind can produce it -- even an outright type error still exits
80
+ // 0 here -- which is what makes the band content-agnostic.
81
+ //
82
+ // The envelope is decoded rather than substring-matched because `"typescript"`
83
+ // carries no `omitempty` and runTransformProject initializes the map before its
84
+ // source loop: the key is present even when the workspace resolved no source at
85
+ // all. That empty envelope is the shape a `rootDir` or `include` rot leaves
86
+ // behind, and this workspace is the one that already shipped such a rot
87
+ // (samchon/typia#2132), so a key-presence check would pass over the one failure
88
+ // worth catching here.
89
+ t.Run("debug", func(t *testing.T) {
90
+ out, errText, code := transformCatalogProject(root, "debug")
91
+ if code != 0 && code != 3 {
92
+ t.Fatalf("debug transform should load and transform the project, got code=%d stderr=\n%s", code, errText)
93
+ }
94
+ var envelope transformProjectOutput
95
+ if err := json.Unmarshal([]byte(out), &envelope); err != nil {
96
+ t.Fatalf("debug project output should be a transform envelope: %v\n%s", err, out)
97
+ }
98
+ if len(envelope.TypeScript) == 0 {
99
+ t.Fatalf("debug project should contribute transformed sources, got an empty TypeScript map:\n%s", out)
100
+ }
101
+ if diagnosed := len(envelope.Diagnostics) > 0; diagnosed != (code == 3) {
102
+ t.Fatalf("debug exit code %d disagrees with its envelope (diagnostics present: %v):\n%s", code, diagnosed, out)
103
+ }
104
+ })
106
105
  }
107
106
 
108
107
  // transformCatalogProject runs project transform mode over one repository test
109
108
  // project and returns its captured stdout, stderr, and exit code.
110
109
  func transformCatalogProject(root string, project string) (string, string, int) {
111
- cwd := filepath.Join(root, "tests", project)
112
- return transformCoverageCapture(func() int {
113
- return runTransform([]string{
114
- "--cwd", cwd,
115
- "--tsconfig", "tsconfig.json",
116
- "--output", "ts",
117
- })
118
- })
110
+ cwd := filepath.Join(root, "tests", project)
111
+ return transformCoverageCapture(func() int {
112
+ return runTransform([]string{
113
+ "--cwd", cwd,
114
+ "--tsconfig", "tsconfig.json",
115
+ "--output", "ts",
116
+ })
117
+ })
119
118
  }