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.
- package/lib/internal/_createStandardSchema.d.ts +1 -2
- package/lib/internal/_createStandardSchema.js.map +1 -1
- package/lib/internal/_createStandardSchema.mjs.map +1 -1
- package/lib/internal/_randomPattern.js +20 -3
- package/lib/internal/_randomPattern.js.map +1 -1
- package/lib/internal/_randomPattern.mjs +5 -1
- package/lib/internal/_randomPattern.mjs.map +1 -1
- package/lib/internal/_randomStringLength.js +9 -2
- package/lib/internal/_randomStringLength.js.map +1 -1
- package/lib/internal/_randomStringLength.mjs +3 -1
- package/lib/internal/_randomStringLength.mjs.map +1 -1
- package/lib/module.d.ts +1 -2
- package/lib/module.js.map +1 -1
- package/lib/module.mjs.map +1 -1
- package/lib/re-exports.d.ts +1 -1
- package/lib/re-exports.js.map +1 -1
- package/lib/transform.mjs +1 -1
- package/native/cmd/ttsc-typia/create_assert_error_factory_arity_test.go +5 -2
- package/native/cmd/ttsc-typia/dependency_collector_virtual_scheme_test.go +52 -0
- package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +206 -0
- package/native/cmd/ttsc-typia/notation_kebab_case_transform_test.go +2 -2
- package/native/cmd/ttsc-typia/project_catalog_transform_coverage_test.go +84 -85
- package/native/cmd/ttsc-typia/project_dependencies_callee_argument_transform_test.go +177 -0
- package/native/cmd/ttsc-typia/project_dependencies_callee_barrel_transform_test.go +115 -0
- package/native/cmd/ttsc-typia/project_dependencies_callee_inference_transform_test.go +285 -0
- package/native/cmd/ttsc-typia/project_dependencies_callee_shape_transform_test.go +117 -0
- package/native/cmd/ttsc-typia/project_dependencies_callee_untransformed_barrel_transform_test.go +121 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_diagnostic_transform_test.go +116 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_envelope_transform_test.go +115 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_inferred_type_transform_test.go +127 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_replaced_library_transform_test.go +153 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_untouched_reprint_transform_test.go +155 -0
- package/native/cmd/ttsc-typia/project_dependencies_computed_key_transform_test.go +218 -0
- package/native/cmd/ttsc-typia/project_dependencies_custom_lib_name_transform_test.go +6 -5
- package/native/cmd/ttsc-typia/project_dependencies_enum_member_value_transform_test.go +131 -0
- package/native/cmd/ttsc-typia/project_dependencies_inferred_declaration_transform_test.go +159 -0
- package/native/cmd/ttsc-typia/project_dependencies_jsdoc_typedef_transform_test.go +134 -0
- package/native/cmd/ttsc-typia/project_dependencies_named_tuple_member_transform_test.go +123 -0
- package/native/cmd/ttsc-typia/project_dependencies_qualified_barrel_transform_test.go +141 -0
- package/native/cmd/ttsc-typia/project_dependencies_type_parameter_default_transform_test.go +127 -0
- package/native/cmd/ttsc-typia/template_literal_type_tags_transform_test.go +3 -1
- package/native/cmd/ttsc-typia/transform.go +195 -21
- package/native/cmd/ttsc-typia/transform_helpers_test.go +12 -0
- package/native/core/factories/MetadataCommentTagFactory.go +96 -27
- package/native/core/factories/metadata_comment_tag_factory_coverage_test.go +2 -2
- package/native/core/factories/metadata_comment_tag_factory_rejects_out_of_range_integer_test.go +124 -0
- package/native/core/programmers/AssertProgrammer.go +20 -6
- package/native/core/programmers/RandomProgrammer.go +78 -6
- package/native/core/programmers/ValidateProgrammer.go +21 -6
- package/native/core/programmers/helpers/RandomJoiner.go +8 -1
- package/native/core/programmers/iterate/check_dynamic_properties.go +177 -3
- package/native/core/programmers/iterate/check_object.go +12 -1
- package/native/core/schemas/metadata/MetadataDependency.go +613 -30
- package/native/transform/CallExpressionTransformer.go +16 -1
- package/package.json +4 -14
- package/src/internal/_createStandardSchema.ts +1 -2
- package/src/internal/_randomPattern.ts +18 -3
- package/src/internal/_randomStringLength.ts +9 -2
- package/src/module.ts +6 -2
- package/src/re-exports.ts +13 -0
- package/lib/executable/FileSystemIdentity.d.ts +0 -44
- package/lib/executable/FileSystemIdentity.js +0 -213
- package/lib/executable/FileSystemIdentity.js.map +0 -1
- package/lib/executable/FileSystemIdentity.mjs +0 -133
- package/lib/executable/FileSystemIdentity.mjs.map +0 -1
- package/lib/executable/TypiaGenerateWizard.d.ts +0 -9
- package/lib/executable/TypiaGenerateWizard.js +0 -852
- package/lib/executable/TypiaGenerateWizard.js.map +0 -1
- package/lib/executable/TypiaGenerateWizard.mjs +0 -618
- package/lib/executable/TypiaGenerateWizard.mjs.map +0 -1
- package/lib/executable/generate/ttsc.d.ts +0 -1
- package/lib/executable/generate/ttsc.js +0 -53
- package/lib/executable/generate/ttsc.js.map +0 -1
- package/lib/executable/generate/ttsc.mjs +0 -37
- package/lib/executable/generate/ttsc.mjs.map +0 -1
- package/lib/executable/typia.d.ts +0 -2
- package/lib/executable/typia.js +0 -84
- package/lib/executable/typia.js.map +0 -1
- package/lib/executable/typia.mjs +0 -60
- package/lib/executable/typia.mjs.map +0 -1
- package/src/executable/FileSystemIdentity.ts +0 -222
- package/src/executable/TypiaGenerateWizard.ts +0 -1133
- package/src/executable/generate/ttsc.ts +0 -70
- 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-
|
|
22
|
-
//
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
`
|