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.
- package/lib/http.d.ts +3 -3
- package/lib/http.js.map +1 -1
- package/lib/http.mjs.map +1 -1
- package/lib/internal/_assertGuard.js +13 -3
- package/lib/internal/_assertGuard.js.map +1 -1
- package/lib/internal/_assertGuard.mjs +4 -2
- package/lib/internal/_assertGuard.mjs.map +1 -1
- package/lib/internal/_isFormatIdnHostname.js +5 -1
- package/lib/internal/_isFormatIdnHostname.js.map +1 -1
- package/lib/internal/_isFormatIdnHostname.mjs +1 -1
- package/lib/internal/_isFormatIdnHostname.mjs.map +1 -1
- package/lib/internal/_isFormatTime.js +19 -2
- package/lib/internal/_isFormatTime.js.map +1 -1
- package/lib/internal/_isFormatTime.mjs +9 -2
- package/lib/internal/_isFormatTime.mjs.map +1 -1
- package/lib/internal/_randomArray.js +37 -4
- package/lib/internal/_randomArray.js.map +1 -1
- package/lib/internal/_randomArray.mjs +16 -3
- package/lib/internal/_randomArray.mjs.map +1 -1
- package/lib/internal/_randomFormatByte.js +15 -12
- package/lib/internal/_randomFormatByte.js.map +1 -1
- package/lib/internal/_randomFormatByte.mjs +11 -10
- package/lib/internal/_randomFormatByte.mjs.map +1 -1
- package/lib/internal/_randomFormatDatetime.mjs +1 -1
- package/lib/internal/_randomFormatHostname.d.ts +2 -2
- package/lib/internal/_randomFormatHostname.js +2 -2
- package/lib/internal/_randomFormatHostname.mjs +2 -2
- package/lib/internal/_randomFormatHostname.mjs.map +1 -1
- package/lib/internal/_randomFormatIdnHostname.js +9 -10
- package/lib/internal/_randomFormatIdnHostname.js.map +1 -1
- package/lib/internal/_randomFormatIdnHostname.mjs +3 -4
- package/lib/internal/_randomFormatIdnHostname.mjs.map +1 -1
- package/lib/internal/_randomFormatRegex.js +4 -4
- package/lib/internal/_randomFormatRegex.js.map +1 -1
- package/lib/internal/_randomFormatRegex.mjs +1 -2
- package/lib/internal/_randomFormatRegex.mjs.map +1 -1
- package/lib/internal/_randomFormatTime.js +15 -10
- package/lib/internal/_randomFormatTime.js.map +1 -1
- package/lib/internal/_randomFormatTime.mjs +6 -3
- package/lib/internal/_randomFormatTime.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/internal/private/__randomComposition.d.ts +8 -0
- package/lib/internal/private/__randomComposition.js +21 -1
- package/lib/internal/private/__randomComposition.js.map +1 -1
- package/lib/internal/private/__randomComposition.mjs +18 -1
- package/lib/internal/private/__randomComposition.mjs.map +1 -1
- package/lib/json.d.ts +2 -2
- package/lib/json.js.map +1 -1
- package/lib/json.mjs.map +1 -1
- package/lib/module.d.ts +28 -13
- package/lib/module.js.map +1 -1
- package/lib/module.mjs.map +1 -1
- package/lib/notations.d.ts +4 -4
- package/lib/notations.js.map +1 -1
- package/lib/notations.mjs.map +1 -1
- package/lib/plain.d.ts +3 -3
- package/lib/plain.js.map +1 -1
- package/lib/plain.mjs.map +1 -1
- package/lib/protobuf.d.ts +2 -2
- package/lib/protobuf.js.map +1 -1
- package/lib/protobuf.mjs.map +1 -1
- package/lib/transform.mjs +1 -1
- package/native/cmd/ttsc-typia/assert_guard_factory_contract_test.go +17 -4
- package/native/cmd/ttsc-typia/create_assert_error_factory_arity_test.go +309 -0
- package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +206 -0
- package/native/cmd/ttsc-typia/local_function_named_like_typia_operation_transform_test.go +185 -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/recursive_conditional_alias_name_transform_test.go +307 -0
- package/native/cmd/ttsc-typia/shadowed_typia_module_declaration_diagnostic_test.go +152 -0
- package/native/cmd/ttsc-typia/transform.go +2 -2
- package/native/cmd/ttsc-typia/transform_helpers_test.go +96 -18
- package/native/core/factories/FormatCheatSheet.go +2 -2
- package/native/core/factories/MetadataCommentTagFactory.go +91 -7
- package/native/core/factories/factory_utility_coverage_test.go +896 -907
- package/native/core/factories/internal/metadata/MetadataHelper.go +5 -28
- 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 -3
- 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/MetadataCollection.go +47 -5
- package/native/transform/CallExpressionTransformer.go +167 -0
- package/package.json +3 -12
- package/src/http.ts +18 -4
- package/src/internal/_assertGuard.ts +13 -2
- package/src/internal/_isFormatIdnHostname.ts +5 -1
- package/src/internal/_isFormatTime.ts +19 -2
- package/src/internal/_randomArray.ts +42 -6
- package/src/internal/_randomFormatByte.ts +17 -12
- package/src/internal/_randomFormatHostname.ts +2 -2
- package/src/internal/_randomFormatIdnHostname.ts +8 -9
- package/src/internal/_randomFormatRegex.ts +4 -6
- package/src/internal/_randomFormatTime.ts +15 -10
- package/src/internal/_randomPattern.ts +18 -3
- package/src/internal/_randomStringLength.ts +9 -2
- package/src/internal/private/__randomComposition.ts +24 -0
- package/src/json.ts +16 -4
- package/src/module.ts +56 -22
- package/src/notations.ts +16 -4
- package/src/plain.ts +12 -3
- package/src/protobuf.ts +16 -4
- 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
|
+
`
|
|
@@ -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-
|
|
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
|
}
|