typia 13.3.0 → 14.0.1

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 (84) hide show
  1. package/lib/internal/_createStandardSchema.d.ts +1 -2
  2. package/lib/internal/_createStandardSchema.js.map +1 -1
  3. package/lib/internal/_createStandardSchema.mjs.map +1 -1
  4. package/lib/internal/_randomPattern.js +20 -3
  5. package/lib/internal/_randomPattern.js.map +1 -1
  6. package/lib/internal/_randomPattern.mjs +5 -1
  7. package/lib/internal/_randomPattern.mjs.map +1 -1
  8. package/lib/internal/_randomStringLength.js +9 -2
  9. package/lib/internal/_randomStringLength.js.map +1 -1
  10. package/lib/internal/_randomStringLength.mjs +3 -1
  11. package/lib/internal/_randomStringLength.mjs.map +1 -1
  12. package/lib/module.d.ts +1 -2
  13. package/lib/module.js.map +1 -1
  14. package/lib/module.mjs.map +1 -1
  15. package/lib/re-exports.d.ts +1 -1
  16. package/lib/re-exports.js.map +1 -1
  17. package/lib/transform.mjs +1 -1
  18. package/native/cmd/ttsc-typia/create_assert_error_factory_arity_test.go +5 -2
  19. package/native/cmd/ttsc-typia/dependency_collector_virtual_scheme_test.go +52 -0
  20. package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +206 -0
  21. package/native/cmd/ttsc-typia/notation_kebab_case_transform_test.go +2 -2
  22. package/native/cmd/ttsc-typia/project_catalog_transform_coverage_test.go +84 -85
  23. package/native/cmd/ttsc-typia/project_dependencies_callee_argument_transform_test.go +177 -0
  24. package/native/cmd/ttsc-typia/project_dependencies_callee_barrel_transform_test.go +115 -0
  25. package/native/cmd/ttsc-typia/project_dependencies_callee_inference_transform_test.go +285 -0
  26. package/native/cmd/ttsc-typia/project_dependencies_callee_shape_transform_test.go +117 -0
  27. package/native/cmd/ttsc-typia/project_dependencies_callee_untransformed_barrel_transform_test.go +121 -0
  28. package/native/cmd/ttsc-typia/project_dependencies_complete_diagnostic_transform_test.go +116 -0
  29. package/native/cmd/ttsc-typia/project_dependencies_complete_envelope_transform_test.go +115 -0
  30. package/native/cmd/ttsc-typia/project_dependencies_complete_inferred_type_transform_test.go +127 -0
  31. package/native/cmd/ttsc-typia/project_dependencies_complete_replaced_library_transform_test.go +153 -0
  32. package/native/cmd/ttsc-typia/project_dependencies_complete_untouched_reprint_transform_test.go +155 -0
  33. package/native/cmd/ttsc-typia/project_dependencies_computed_key_transform_test.go +218 -0
  34. package/native/cmd/ttsc-typia/project_dependencies_custom_lib_name_transform_test.go +6 -5
  35. package/native/cmd/ttsc-typia/project_dependencies_enum_member_value_transform_test.go +131 -0
  36. package/native/cmd/ttsc-typia/project_dependencies_inferred_declaration_transform_test.go +159 -0
  37. package/native/cmd/ttsc-typia/project_dependencies_jsdoc_typedef_transform_test.go +134 -0
  38. package/native/cmd/ttsc-typia/project_dependencies_named_tuple_member_transform_test.go +123 -0
  39. package/native/cmd/ttsc-typia/project_dependencies_qualified_barrel_transform_test.go +141 -0
  40. package/native/cmd/ttsc-typia/project_dependencies_type_parameter_default_transform_test.go +127 -0
  41. package/native/cmd/ttsc-typia/template_literal_type_tags_transform_test.go +3 -1
  42. package/native/cmd/ttsc-typia/transform.go +195 -21
  43. package/native/cmd/ttsc-typia/transform_helpers_test.go +12 -0
  44. package/native/core/factories/MetadataCommentTagFactory.go +96 -27
  45. package/native/core/factories/metadata_comment_tag_factory_coverage_test.go +2 -2
  46. package/native/core/factories/metadata_comment_tag_factory_rejects_out_of_range_integer_test.go +124 -0
  47. package/native/core/programmers/AssertProgrammer.go +20 -6
  48. package/native/core/programmers/RandomProgrammer.go +78 -6
  49. package/native/core/programmers/ValidateProgrammer.go +21 -6
  50. package/native/core/programmers/helpers/RandomJoiner.go +8 -1
  51. package/native/core/programmers/iterate/check_dynamic_properties.go +177 -3
  52. package/native/core/programmers/iterate/check_object.go +12 -1
  53. package/native/core/schemas/metadata/MetadataDependency.go +613 -30
  54. package/native/transform/CallExpressionTransformer.go +16 -1
  55. package/package.json +4 -14
  56. package/src/internal/_createStandardSchema.ts +1 -2
  57. package/src/internal/_randomPattern.ts +18 -3
  58. package/src/internal/_randomStringLength.ts +9 -2
  59. package/src/module.ts +6 -2
  60. package/src/re-exports.ts +13 -0
  61. package/lib/executable/FileSystemIdentity.d.ts +0 -44
  62. package/lib/executable/FileSystemIdentity.js +0 -213
  63. package/lib/executable/FileSystemIdentity.js.map +0 -1
  64. package/lib/executable/FileSystemIdentity.mjs +0 -133
  65. package/lib/executable/FileSystemIdentity.mjs.map +0 -1
  66. package/lib/executable/TypiaGenerateWizard.d.ts +0 -9
  67. package/lib/executable/TypiaGenerateWizard.js +0 -852
  68. package/lib/executable/TypiaGenerateWizard.js.map +0 -1
  69. package/lib/executable/TypiaGenerateWizard.mjs +0 -618
  70. package/lib/executable/TypiaGenerateWizard.mjs.map +0 -1
  71. package/lib/executable/generate/ttsc.d.ts +0 -1
  72. package/lib/executable/generate/ttsc.js +0 -53
  73. package/lib/executable/generate/ttsc.js.map +0 -1
  74. package/lib/executable/generate/ttsc.mjs +0 -37
  75. package/lib/executable/generate/ttsc.mjs.map +0 -1
  76. package/lib/executable/typia.d.ts +0 -2
  77. package/lib/executable/typia.js +0 -84
  78. package/lib/executable/typia.js.map +0 -1
  79. package/lib/executable/typia.mjs +0 -60
  80. package/lib/executable/typia.mjs.map +0 -1
  81. package/src/executable/FileSystemIdentity.ts +0 -222
  82. package/src/executable/TypiaGenerateWizard.ts +0 -1133
  83. package/src/executable/generate/ttsc.ts +0 -70
  84. 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
+ `
@@ -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
  }
@@ -0,0 +1,177 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ // TestProjectDependenciesCalleeArgumentTransform verifies a nested call's
12
+ // arguments report the names they write, and that their shape never withholds
13
+ // the caller.
14
+ //
15
+ // The callee chain names every indirection a caller writes -- a factory, a
16
+ // barrel, a namespace alias -- but a generic pass-through carries identity
17
+ // through what the call is APPLIED to instead: `pick(ns).is<Foo>(x)` is rewritten
18
+ // because `ns` holds the typia namespace, so `ns.ts` decides whether this file
19
+ // becomes a validator at all. Reproduced: the envelope declared the caller
20
+ // complete while reporting only `foo.ts` and `pick.ts`, so re-pointing `ns.ts`
21
+ // would have turned the validator back into a plain call with nothing to
22
+ // invalidate it (samchon/typia#2357).
23
+ //
24
+ // The twin is why the walk's answer is dropped rather than combined: a callback
25
+ // handed to a chained array method decides nothing about which `join` the outer
26
+ // call names, and withholding on it would cost every file that chains one.
27
+ //
28
+ // 1. Build a project where `main.ts` calls `pick(ns).is<Foo>(input)` for a
29
+ // `pick` that returns its argument, and `chain.ts` writes
30
+ // `["a"].map((x) => x + HIDDEN).join(",")` for a `HIDDEN` from another file.
31
+ // 2. Run project transform mode and decode the JSON envelope.
32
+ // 3. Assert `main.ts` really was rewritten into a validator, so the pass-through
33
+ // is the shape the test intends.
34
+ // 4. Assert `dependencies["src/main.ts"]` carries `src/ns.ts` and `src/pick.ts`,
35
+ // and that the file is declared complete.
36
+ // 5. Assert `chain.ts` is declared complete and never reports `src/hidden.ts`,
37
+ // the name only its callback body writes.
38
+ // 6. Assert the tagged-template spelling of the same pass-through --
39
+ // “tag`x${ns}`.is<Foo>(input)“ -- reports `src/ns.ts` as well, because a
40
+ // template is what its tag is applied to.
41
+ func TestProjectDependenciesCalleeArgumentTransform(t *testing.T) {
42
+ project := projectDependenciesCalleeArgumentProject(t)
43
+ out, errText, code := ttscTypiaTestCapture(func() int {
44
+ return runTransform([]string{
45
+ "--cwd", project,
46
+ "--tsconfig", "tsconfig.json",
47
+ "--output", "ts",
48
+ })
49
+ })
50
+ if code != 0 {
51
+ t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
52
+ }
53
+ var envelope struct {
54
+ TypeScript map[string]string `json:"typescript"`
55
+ Dependencies map[string][]string `json:"dependencies"`
56
+ DependenciesComplete []string `json:"dependenciesComplete"`
57
+ }
58
+ if err := json.Unmarshal([]byte(out), &envelope); err != nil {
59
+ t.Fatalf("decode envelope: %v\n%s", err, out)
60
+ }
61
+ if text := envelope.TypeScript["src/main.ts"]; !strings.Contains(text, "input is Foo") {
62
+ t.Fatalf("the pass-through call must have been rewritten into a validator, got:\n%s", text)
63
+ }
64
+ entries := envelope.Dependencies["src/main.ts"]
65
+ found := map[string]bool{}
66
+ for _, entry := range entries {
67
+ found[entry] = true
68
+ }
69
+ if !found["src/ns.ts"] {
70
+ t.Fatalf("dependencies of src/main.ts must contain src/ns.ts, the argument that makes this call typia's: %v", entries)
71
+ }
72
+ if !found["src/pick.ts"] {
73
+ t.Fatalf("dependencies of src/main.ts must contain src/pick.ts, the pass-through on the callee chain: %v", entries)
74
+ }
75
+ declared := map[string]bool{}
76
+ for _, key := range envelope.DependenciesComplete {
77
+ declared[key] = true
78
+ }
79
+ if !declared["src/main.ts"] {
80
+ t.Fatalf("src/main.ts must be declared complete, so the reported entry is the whole bound: %v", envelope.DependenciesComplete)
81
+ }
82
+ if !declared["src/chain.ts"] {
83
+ t.Fatalf("a callback handed to a chained array method decides no callee identity and must not withhold its file: %v", envelope.DependenciesComplete)
84
+ }
85
+ for _, entry := range envelope.Dependencies["src/chain.ts"] {
86
+ if entry == "src/hidden.ts" {
87
+ t.Fatalf("the walk must stop at a function literal, so src/hidden.ts must not be charged to src/chain.ts: %v", envelope.Dependencies["src/chain.ts"])
88
+ }
89
+ }
90
+ if text := envelope.TypeScript["src/tagged.ts"]; !strings.Contains(text, "input is Foo") {
91
+ t.Fatalf("the tagged-template pass-through must have been rewritten into a validator, got:\n%s", text)
92
+ }
93
+ tagged := map[string]bool{}
94
+ for _, entry := range envelope.Dependencies["src/tagged.ts"] {
95
+ tagged[entry] = true
96
+ }
97
+ if !tagged["src/ns.ts"] {
98
+ t.Fatalf("dependencies of src/tagged.ts must contain src/ns.ts, the substitution that makes this call typia's: %v", envelope.Dependencies["src/tagged.ts"])
99
+ }
100
+ if !declared["src/tagged.ts"] {
101
+ t.Fatalf("src/tagged.ts must be declared complete, so the reported entry is the whole bound: %v", envelope.DependenciesComplete)
102
+ }
103
+ }
104
+
105
+ func projectDependenciesCalleeArgumentProject(t *testing.T) string {
106
+ t.Helper()
107
+ root := ttscTypiaTestRepoRoot(t)
108
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
109
+ if err := os.MkdirAll(base, 0o755); err != nil {
110
+ t.Fatalf("mkdir temp base: %v", err)
111
+ }
112
+ dir, err := os.MkdirTemp(base, "project-dependencies-callee-argument-")
113
+ if err != nil {
114
+ t.Fatalf("create temp fixture: %v", err)
115
+ }
116
+ t.Cleanup(func() { _ = os.RemoveAll(dir) })
117
+ src := filepath.Join(dir, "src")
118
+ if err := os.MkdirAll(src, 0o755); err != nil {
119
+ t.Fatalf("mkdir fixture src: %v", err)
120
+ }
121
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
122
+ t.Fatalf("write tsconfig: %v", err)
123
+ }
124
+ for name, body := range map[string]string{
125
+ "main.ts": projectDependenciesCalleeArgumentSourceMain,
126
+ "ns.ts": projectDependenciesCalleeArgumentSourceNs,
127
+ "pick.ts": projectDependenciesCalleeArgumentSourcePick,
128
+ "foo.ts": projectDependenciesCalleeArgumentSourceFoo,
129
+ "chain.ts": projectDependenciesCalleeArgumentSourceChain,
130
+ "hidden.ts": projectDependenciesCalleeArgumentSourceHidden,
131
+ "tagged.ts": projectDependenciesCalleeArgumentSourceTagged,
132
+ "tag.ts": projectDependenciesCalleeArgumentSourceTag,
133
+ } {
134
+ if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
135
+ t.Fatalf("write %s: %v", name, err)
136
+ }
137
+ }
138
+ return dir
139
+ }
140
+
141
+ const projectDependenciesCalleeArgumentSourceMain = `import { Foo } from "./foo";
142
+ import { ns } from "./ns";
143
+ import { pick } from "./pick";
144
+
145
+ export const check = (input: unknown) => pick(ns).is<Foo>(input);
146
+ `
147
+
148
+ const projectDependenciesCalleeArgumentSourceNs = `import typia from "typia";
149
+
150
+ export const ns = typia;
151
+ `
152
+
153
+ const projectDependenciesCalleeArgumentSourcePick = `export const pick = <T>(value: T): T => value;
154
+ `
155
+
156
+ const projectDependenciesCalleeArgumentSourceFoo = `export interface Foo {
157
+ id: string;
158
+ }
159
+ `
160
+
161
+ const projectDependenciesCalleeArgumentSourceChain = `import { HIDDEN } from "./hidden";
162
+
163
+ export const joined = ["a"].map((x) => x + HIDDEN).join(",");
164
+ `
165
+
166
+ const projectDependenciesCalleeArgumentSourceHidden = `export const HIDDEN = "hidden";
167
+ `
168
+
169
+ const projectDependenciesCalleeArgumentSourceTagged = `import { Foo } from "./foo";
170
+ import { ns } from "./ns";
171
+ import { tag } from "./tag";
172
+
173
+ export const check = (input: unknown) => tag` + "`" + `x${ns}` + "`" + `.is<Foo>(input);
174
+ `
175
+
176
+ const projectDependenciesCalleeArgumentSourceTag = `export const tag = <T>(_strings: TemplateStringsArray, value: T): T => value;
177
+ `