typia 14.0.0 → 14.0.2
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/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/transformers/NoTransformConfigurationError.js +19 -4
- package/lib/transformers/NoTransformConfigurationError.js.map +1 -1
- package/lib/transformers/NoTransformConfigurationError.mjs +12 -6
- package/lib/transformers/NoTransformConfigurationError.mjs.map +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/json_schema_nonsensible_intersection_diagnostic_test.go +80 -0
- 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/transform.go +193 -19
- package/native/core/schemas/metadata/MetadataDependency.go +613 -30
- package/native/transform/CallExpressionTransformer.go +16 -1
- package/package.json +4 -5
- package/src/internal/_createStandardSchema.ts +1 -2
- package/src/module.ts +6 -2
- package/src/re-exports.ts +13 -0
- package/src/transformers/NoTransformConfigurationError.ts +19 -4
|
@@ -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
|
+
`
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesCalleeBarrelTransform verifies the modules an import
|
|
12
|
+
// chain traverses to reach a typia callee are reported for the file that calls
|
|
13
|
+
// it.
|
|
14
|
+
//
|
|
15
|
+
// typia decides its own call sites by the file that declares the resolved
|
|
16
|
+
// signature, so a barrel between the caller and typia chooses whether the call
|
|
17
|
+
// is rewritten at all: re-pointing `export { is } from "typia"` at a local
|
|
18
|
+
// helper turns a generated validator back into a plain call. The consulted-type
|
|
19
|
+
// touches cannot see that -- they report what the validated type is, never what
|
|
20
|
+
// selected the callee -- so without this the barrel is nowhere in the reported
|
|
21
|
+
// list, and a consumer that narrows to that list serves the stale validator
|
|
22
|
+
// (samchon/typia#2357).
|
|
23
|
+
//
|
|
24
|
+
// 1. Build a project where `consumer.ts` calls `is<Alpha>` imported from
|
|
25
|
+
// `barrel.ts`, which re-exports it from `typia`.
|
|
26
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
27
|
+
// 3. Assert the call was rewritten, so the barrel really did select typia.
|
|
28
|
+
// 4. Assert `dependencies["src/consumer.ts"]` contains `src/barrel.ts`, and
|
|
29
|
+
// not `src/unused.ts`, which the barrel also re-exports but this reference
|
|
30
|
+
// never traverses.
|
|
31
|
+
func TestProjectDependenciesCalleeBarrelTransform(t *testing.T) {
|
|
32
|
+
project := projectDependenciesCalleeBarrelProject(t)
|
|
33
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
34
|
+
return runTransform([]string{
|
|
35
|
+
"--cwd", project,
|
|
36
|
+
"--tsconfig", "tsconfig.json",
|
|
37
|
+
"--output", "ts",
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
if code != 0 {
|
|
41
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
42
|
+
}
|
|
43
|
+
var envelope struct {
|
|
44
|
+
TypeScript map[string]string `json:"typescript"`
|
|
45
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
46
|
+
}
|
|
47
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
48
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
49
|
+
}
|
|
50
|
+
if text := envelope.TypeScript["src/consumer.ts"]; !strings.Contains(text, "input is") {
|
|
51
|
+
t.Fatalf("the re-exported callee must still be recognized as typia's, got:\n%s", text)
|
|
52
|
+
}
|
|
53
|
+
entries := envelope.Dependencies["src/consumer.ts"]
|
|
54
|
+
found := map[string]bool{}
|
|
55
|
+
for _, entry := range entries {
|
|
56
|
+
found[entry] = true
|
|
57
|
+
}
|
|
58
|
+
if !found["src/barrel.ts"] {
|
|
59
|
+
t.Fatalf("dependencies of src/consumer.ts must contain the barrel src/barrel.ts that selects the typia callee: %v", entries)
|
|
60
|
+
}
|
|
61
|
+
if found["src/unused.ts"] {
|
|
62
|
+
t.Fatalf("dependencies of src/consumer.ts must not contain src/unused.ts, which the barrel re-exports but this reference never traverses: %v", entries)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func projectDependenciesCalleeBarrelProject(t *testing.T) string {
|
|
67
|
+
t.Helper()
|
|
68
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
69
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
70
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
71
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
72
|
+
}
|
|
73
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-callee-barrel-")
|
|
74
|
+
if err != nil {
|
|
75
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
76
|
+
}
|
|
77
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
78
|
+
src := filepath.Join(dir, "src")
|
|
79
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
80
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
81
|
+
}
|
|
82
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
83
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
84
|
+
}
|
|
85
|
+
for name, body := range map[string]string{
|
|
86
|
+
"consumer.ts": projectDependenciesCalleeBarrelSourceConsumer,
|
|
87
|
+
"barrel.ts": projectDependenciesCalleeBarrelSourceBarrel,
|
|
88
|
+
"alpha.ts": projectDependenciesCalleeBarrelSourceAlpha,
|
|
89
|
+
"unused.ts": projectDependenciesCalleeBarrelSourceUnused,
|
|
90
|
+
} {
|
|
91
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
92
|
+
t.Fatalf("write %s: %v", name, err)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return dir
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const projectDependenciesCalleeBarrelSourceConsumer = `import { is } from "./barrel";
|
|
99
|
+
|
|
100
|
+
import { Alpha } from "./alpha";
|
|
101
|
+
|
|
102
|
+
export const checkAlpha = (input: unknown) => is<Alpha>(input);
|
|
103
|
+
`
|
|
104
|
+
|
|
105
|
+
const projectDependenciesCalleeBarrelSourceBarrel = `export { is } from "typia";
|
|
106
|
+
export { unused } from "./unused";
|
|
107
|
+
`
|
|
108
|
+
|
|
109
|
+
const projectDependenciesCalleeBarrelSourceAlpha = `export interface Alpha {
|
|
110
|
+
id: string;
|
|
111
|
+
}
|
|
112
|
+
`
|
|
113
|
+
|
|
114
|
+
const projectDependenciesCalleeBarrelSourceUnused = `export const unused = (): number => 1;
|
|
115
|
+
`
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesCalleeInferenceTransform verifies a nested call whose
|
|
12
|
+
// identity comes from an inferred return type or a borrowed constant withholds
|
|
13
|
+
// its file, while the same indirection written with a type keeps it.
|
|
14
|
+
//
|
|
15
|
+
// The callee walk reports which files a callee's name resolves through, and for
|
|
16
|
+
// the OUTERMOST callee that is the whole answer: the identity is the resolved
|
|
17
|
+
// declaration, whose file is reported. One call in it stops being the answer,
|
|
18
|
+
// because there the identity is the inner call's return type. `const getNs = ()
|
|
19
|
+
// => ns` and `const FLAG = RAW` both carry it from a file no name on the chain
|
|
20
|
+
// writes, so reporting the names without admitting that declared the caller
|
|
21
|
+
// complete while an edit to the unreported file deleted the generated validator
|
|
22
|
+
// outright (samchon/typia#2360).
|
|
23
|
+
//
|
|
24
|
+
// A function DECLARATION with an inferred return type is the same defect one
|
|
25
|
+
// keyword apart, and it needs its own case: the shared boundedness predicate
|
|
26
|
+
// calls every callable bounded, because the type walk emits one as `typeof x
|
|
27
|
+
// === "function"` and never reads its signature. On this channel the signature
|
|
28
|
+
// is the whole question.
|
|
29
|
+
//
|
|
30
|
+
// The bounded twins are what keep the fix from degenerating into "withhold on
|
|
31
|
+
// every nested call": an indirection with a written return type pins its
|
|
32
|
+
// identity in the file that declares it, and that file must stay declared.
|
|
33
|
+
//
|
|
34
|
+
// The third shape samchon/typia#2360 reports -- an overload selected by a
|
|
35
|
+
// borrowed VALUE -- is NOT pinned here, and the fixture keeps it as the record
|
|
36
|
+
// of that. `const FLAG = RAW` is structurally identical to `const ns = typia`,
|
|
37
|
+
// whose caller must stay declared (samchon/typia#2357), so no boundedness
|
|
38
|
+
// answer separates them; reporting the initializer chain would.
|
|
39
|
+
//
|
|
40
|
+
// 1. Build a project with three unbounded shapes -- a computed argument, a
|
|
41
|
+
// computed callee, and a function declaration with an inferred return type
|
|
42
|
+
// -- plus a written-type twin for the `const` and the `function` spellings,
|
|
43
|
+
// and the borrowed-value overload that stays declared.
|
|
44
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
45
|
+
// 3. Assert each file really was rewritten into a validator, so the test is
|
|
46
|
+
// measuring the shape it intends rather than a call typia never touched.
|
|
47
|
+
// 4. Assert the three unbounded files are absent from `dependenciesComplete`.
|
|
48
|
+
// 5. Assert both annotated twins are present, and that a file whose OUTERMOST
|
|
49
|
+
// callee resolves to a local `const helper = () => ...` keeps its
|
|
50
|
+
// declaration -- that name decides nothing, and charging it costs 65 more
|
|
51
|
+
// files across the test workspaces than the fix needs.
|
|
52
|
+
func TestProjectDependenciesCalleeInferenceTransform(t *testing.T) {
|
|
53
|
+
project := projectDependenciesCalleeInferenceProject(t)
|
|
54
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
55
|
+
return runTransform([]string{
|
|
56
|
+
"--cwd", project,
|
|
57
|
+
"--tsconfig", "tsconfig.json",
|
|
58
|
+
"--output", "ts",
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
if code != 0 {
|
|
62
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
63
|
+
}
|
|
64
|
+
var envelope struct {
|
|
65
|
+
TypeScript map[string]string `json:"typescript"`
|
|
66
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
67
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
68
|
+
}
|
|
69
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
70
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Guard first: an assertion about dependencies means nothing on a file the
|
|
74
|
+
// transform never rewrote, and that is exactly how this class hides.
|
|
75
|
+
for _, file := range []string{
|
|
76
|
+
"src/main_computed_arg.ts",
|
|
77
|
+
"src/main_computed_callee.ts",
|
|
78
|
+
"src/main_overload.ts",
|
|
79
|
+
"src/main_annotated.ts",
|
|
80
|
+
"src/main_function.ts",
|
|
81
|
+
"src/main_function_annotated.ts",
|
|
82
|
+
"src/main_outermost.ts",
|
|
83
|
+
} {
|
|
84
|
+
if text := envelope.TypeScript[file]; !strings.Contains(text, "input is Foo") {
|
|
85
|
+
t.Fatalf("%s must have been rewritten into a validator, got:\n%s", file, text)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
declared := map[string]bool{}
|
|
90
|
+
for _, key := range envelope.DependenciesComplete {
|
|
91
|
+
declared[key] = true
|
|
92
|
+
}
|
|
93
|
+
for _, file := range []string{
|
|
94
|
+
"src/main_computed_arg.ts",
|
|
95
|
+
"src/main_computed_callee.ts",
|
|
96
|
+
"src/main_function.ts",
|
|
97
|
+
} {
|
|
98
|
+
if declared[file] {
|
|
99
|
+
t.Fatalf(
|
|
100
|
+
"%s must be withheld: its callee identity comes from an inferred "+
|
|
101
|
+
"return type or a borrowed constant, and %v does not name the file "+
|
|
102
|
+
"that decides it",
|
|
103
|
+
file,
|
|
104
|
+
envelope.Dependencies[file],
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for _, file := range []string{
|
|
109
|
+
"src/main_annotated.ts",
|
|
110
|
+
"src/main_function_annotated.ts",
|
|
111
|
+
} {
|
|
112
|
+
if declared[file] {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
t.Fatalf(
|
|
116
|
+
"%s must stay declared: its indirection carries a written return type, "+
|
|
117
|
+
"so the reported files do pin the identity. Withholding it means the "+
|
|
118
|
+
"boundedness answer stopped reading the written type: %v",
|
|
119
|
+
file,
|
|
120
|
+
envelope.DependenciesComplete,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
if !declared["src/main_outermost.ts"] {
|
|
124
|
+
t.Fatalf(
|
|
125
|
+
"src/main_outermost.ts must stay declared: its outermost callee IS the "+
|
|
126
|
+
"declaration it resolves to, so a local arrow with an inferred return "+
|
|
127
|
+
"type decides nothing. Withholding it means the nested guard is gone, "+
|
|
128
|
+
"which costs 65 more files across the test workspaces: %v",
|
|
129
|
+
envelope.DependenciesComplete,
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func projectDependenciesCalleeInferenceProject(t *testing.T) string {
|
|
135
|
+
t.Helper()
|
|
136
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
137
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
138
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
139
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
140
|
+
}
|
|
141
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-callee-inference-")
|
|
142
|
+
if err != nil {
|
|
143
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
144
|
+
}
|
|
145
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
146
|
+
src := filepath.Join(dir, "src")
|
|
147
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
148
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
149
|
+
}
|
|
150
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
151
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
152
|
+
}
|
|
153
|
+
for name, body := range map[string]string{
|
|
154
|
+
"foo.ts": projectDependenciesCalleeInferenceSourceFoo,
|
|
155
|
+
"ns.ts": projectDependenciesCalleeInferenceSourceNs,
|
|
156
|
+
"getns.ts": projectDependenciesCalleeInferenceSourceGetNs,
|
|
157
|
+
"pick.ts": projectDependenciesCalleeInferenceSourcePick,
|
|
158
|
+
"rawflag.ts": projectDependenciesCalleeInferenceSourceRawFlag,
|
|
159
|
+
"flag.ts": projectDependenciesCalleeInferenceSourceFlag,
|
|
160
|
+
"other.ts": projectDependenciesCalleeInferenceSourceOther,
|
|
161
|
+
"sel.ts": projectDependenciesCalleeInferenceSourceSel,
|
|
162
|
+
"main_computed_arg.ts": projectDependenciesCalleeInferenceSourceComputedArg,
|
|
163
|
+
"main_computed_callee.ts": projectDependenciesCalleeInferenceSourceComputedCallee,
|
|
164
|
+
"main_overload.ts": projectDependenciesCalleeInferenceSourceOverload,
|
|
165
|
+
"main_annotated.ts": projectDependenciesCalleeInferenceSourceAnnotated,
|
|
166
|
+
"main_function.ts": projectDependenciesCalleeInferenceSourceFunction,
|
|
167
|
+
"main_function_annotated.ts": projectDependenciesCalleeInferenceSourceFunctionAnnotated,
|
|
168
|
+
"main_outermost.ts": projectDependenciesCalleeInferenceSourceOutermost,
|
|
169
|
+
} {
|
|
170
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
171
|
+
t.Fatalf("write %s: %v", name, err)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return dir
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const projectDependenciesCalleeInferenceSourceFoo = `export interface Foo {
|
|
178
|
+
id: string;
|
|
179
|
+
}
|
|
180
|
+
`
|
|
181
|
+
|
|
182
|
+
const projectDependenciesCalleeInferenceSourceNs = `import typia from "typia";
|
|
183
|
+
|
|
184
|
+
export const ns = typia;
|
|
185
|
+
`
|
|
186
|
+
|
|
187
|
+
// The return type is inferred, so nothing written here says the result is
|
|
188
|
+
// typia's namespace; `ns.ts` decides it and no name on the callee chain reaches
|
|
189
|
+
// that file.
|
|
190
|
+
const projectDependenciesCalleeInferenceSourceGetNs = `import { ns } from "./ns";
|
|
191
|
+
|
|
192
|
+
export const getNs = () => ns;
|
|
193
|
+
|
|
194
|
+
export const getNsAnnotated: () => typeof ns = () => ns;
|
|
195
|
+
|
|
196
|
+
export function getNsFn() {
|
|
197
|
+
return ns;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function getNsFnAnnotated(): typeof ns {
|
|
201
|
+
return ns;
|
|
202
|
+
}
|
|
203
|
+
`
|
|
204
|
+
|
|
205
|
+
const projectDependenciesCalleeInferenceSourcePick = `export const pick = <T>(value: T): T => value;
|
|
206
|
+
`
|
|
207
|
+
|
|
208
|
+
const projectDependenciesCalleeInferenceSourceRawFlag = `export const RAW = 1;
|
|
209
|
+
`
|
|
210
|
+
|
|
211
|
+
// The value is borrowed from another file, so the overload this selects is
|
|
212
|
+
// decided by `rawflag.ts`.
|
|
213
|
+
const projectDependenciesCalleeInferenceSourceFlag = `import { RAW } from "./rawflag";
|
|
214
|
+
|
|
215
|
+
export const FLAG = RAW;
|
|
216
|
+
`
|
|
217
|
+
|
|
218
|
+
const projectDependenciesCalleeInferenceSourceOther = `export const other = {
|
|
219
|
+
is: <T>(_input: unknown): _input is T => false,
|
|
220
|
+
};
|
|
221
|
+
`
|
|
222
|
+
|
|
223
|
+
const projectDependenciesCalleeInferenceSourceSel = `import typia from "typia";
|
|
224
|
+
|
|
225
|
+
import { other } from "./other";
|
|
226
|
+
|
|
227
|
+
export function sel(x: 0): typeof other;
|
|
228
|
+
export function sel(x: 1): typeof typia;
|
|
229
|
+
export function sel(x: number): unknown {
|
|
230
|
+
return x === 1 ? typia : other;
|
|
231
|
+
}
|
|
232
|
+
`
|
|
233
|
+
|
|
234
|
+
const projectDependenciesCalleeInferenceSourceComputedArg = `import { Foo } from "./foo";
|
|
235
|
+
import { getNs } from "./getns";
|
|
236
|
+
import { pick } from "./pick";
|
|
237
|
+
|
|
238
|
+
export const check = (input: unknown) => pick(getNs()).is<Foo>(input);
|
|
239
|
+
`
|
|
240
|
+
|
|
241
|
+
const projectDependenciesCalleeInferenceSourceComputedCallee = `import { Foo } from "./foo";
|
|
242
|
+
import { getNs } from "./getns";
|
|
243
|
+
|
|
244
|
+
export const check = (input: unknown) => getNs().is<Foo>(input);
|
|
245
|
+
`
|
|
246
|
+
|
|
247
|
+
const projectDependenciesCalleeInferenceSourceOverload = `import { Foo } from "./foo";
|
|
248
|
+
import { FLAG } from "./flag";
|
|
249
|
+
import { sel } from "./sel";
|
|
250
|
+
|
|
251
|
+
export const check = (input: unknown) => sel(FLAG).is<Foo>(input);
|
|
252
|
+
`
|
|
253
|
+
|
|
254
|
+
// The bounded twin: the indirection's return type is written, so `getns.ts`
|
|
255
|
+
// pins the identity and this file must keep its declaration.
|
|
256
|
+
const projectDependenciesCalleeInferenceSourceAnnotated = `import { Foo } from "./foo";
|
|
257
|
+
import { getNsAnnotated } from "./getns";
|
|
258
|
+
|
|
259
|
+
export const check = (input: unknown) => getNsAnnotated().is<Foo>(input);
|
|
260
|
+
`
|
|
261
|
+
|
|
262
|
+
const projectDependenciesCalleeInferenceSourceFunction = `import { Foo } from "./foo";
|
|
263
|
+
import { getNsFn } from "./getns";
|
|
264
|
+
|
|
265
|
+
export const check = (input: unknown) => getNsFn().is<Foo>(input);
|
|
266
|
+
`
|
|
267
|
+
|
|
268
|
+
const projectDependenciesCalleeInferenceSourceFunctionAnnotated = `import { Foo } from "./foo";
|
|
269
|
+
import { getNsFnAnnotated } from "./getns";
|
|
270
|
+
|
|
271
|
+
export const check = (input: unknown) => getNsFnAnnotated().is<Foo>(input);
|
|
272
|
+
`
|
|
273
|
+
|
|
274
|
+
// The outermost callee's identity IS the declaration it resolves to, so a local
|
|
275
|
+
// arrow with an inferred return type decides nothing here and must not withhold
|
|
276
|
+
// the file. Measured: charging it drops the eight test workspaces from 806
|
|
277
|
+
// declared to 741.
|
|
278
|
+
const projectDependenciesCalleeInferenceSourceOutermost = `import typia from "typia";
|
|
279
|
+
|
|
280
|
+
import { Foo } from "./foo";
|
|
281
|
+
|
|
282
|
+
const label = () => "x";
|
|
283
|
+
|
|
284
|
+
export const check = (input: unknown) => typia.is<Foo>(input) && label() !== "";
|
|
285
|
+
`
|