typia 14.0.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/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/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/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
|
@@ -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
|
+
`
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestProjectDependenciesCalleeShapeTransform verifies where the callee walk
|
|
11
|
+
// stops, in both directions.
|
|
12
|
+
//
|
|
13
|
+
// A function literal written directly in callee position is the most bounded
|
|
14
|
+
// declaration there is: the call resolves to the literal itself, in this very
|
|
15
|
+
// file, so its body is nobody's dependency and the file stays declarable.
|
|
16
|
+
// Reached through a nested call the same literal is the opposite -- the identity
|
|
17
|
+
// is whatever its body returns, and no set of files describes that -- so the
|
|
18
|
+
// file has to be withheld from the completeness declaration instead
|
|
19
|
+
// (samchon/typia#2357).
|
|
20
|
+
//
|
|
21
|
+
// 1. Build a project where `direct.ts` calls an IIFE whose body names a type
|
|
22
|
+
// from `deep.ts`, while `indirect.ts` calls what an IIFE returns and
|
|
23
|
+
// `tagged.ts` calls what a tagged template returns.
|
|
24
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
25
|
+
// 3. Assert `direct.ts` is declared complete and reports nothing from its own
|
|
26
|
+
// IIFE body, so the walk neither charged nor withheld the body's names.
|
|
27
|
+
// 4. Assert `indirect.ts` and `tagged.ts` are withheld, so a call-like form
|
|
28
|
+
// other than `()` does not quietly escape the same rule.
|
|
29
|
+
func TestProjectDependenciesCalleeShapeTransform(t *testing.T) {
|
|
30
|
+
project := projectDependenciesCalleeShapeProject(t)
|
|
31
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
32
|
+
return runTransform([]string{
|
|
33
|
+
"--cwd", project,
|
|
34
|
+
"--tsconfig", "tsconfig.json",
|
|
35
|
+
"--output", "ts",
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
if code != 0 {
|
|
39
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
40
|
+
}
|
|
41
|
+
var envelope struct {
|
|
42
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
43
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
44
|
+
}
|
|
45
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
46
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
47
|
+
}
|
|
48
|
+
declared := map[string]bool{}
|
|
49
|
+
for _, key := range envelope.DependenciesComplete {
|
|
50
|
+
declared[key] = true
|
|
51
|
+
}
|
|
52
|
+
if !declared["src/direct.ts"] {
|
|
53
|
+
t.Fatalf("a call to a function literal written in callee position resolves to that literal and must stay declarable: %v", envelope.DependenciesComplete)
|
|
54
|
+
}
|
|
55
|
+
for _, entry := range envelope.Dependencies["src/direct.ts"] {
|
|
56
|
+
if entry == "src/deep.ts" {
|
|
57
|
+
t.Fatalf("the walk must not charge src/direct.ts with names its own IIFE body uses: %v", envelope.Dependencies["src/direct.ts"])
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if declared["src/indirect.ts"] {
|
|
61
|
+
t.Fatalf("a call to what a function literal returns has no bounded identity and must be withheld: %v", envelope.DependenciesComplete)
|
|
62
|
+
}
|
|
63
|
+
if declared["src/tagged.ts"] {
|
|
64
|
+
t.Fatalf("a tagged template is a call, so what it returns has no bounded identity either: %v", envelope.DependenciesComplete)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func projectDependenciesCalleeShapeProject(t *testing.T) string {
|
|
69
|
+
t.Helper()
|
|
70
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
71
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
72
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
73
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
74
|
+
}
|
|
75
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-callee-shape-")
|
|
76
|
+
if err != nil {
|
|
77
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
78
|
+
}
|
|
79
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
80
|
+
src := filepath.Join(dir, "src")
|
|
81
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
82
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
83
|
+
}
|
|
84
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
85
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
86
|
+
}
|
|
87
|
+
for name, body := range map[string]string{
|
|
88
|
+
"direct.ts": projectDependenciesCalleeShapeSourceDirect,
|
|
89
|
+
"indirect.ts": projectDependenciesCalleeShapeSourceIndirect,
|
|
90
|
+
"tagged.ts": projectDependenciesCalleeShapeSourceTagged,
|
|
91
|
+
"deep.ts": projectDependenciesCalleeShapeSourceDeep,
|
|
92
|
+
} {
|
|
93
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
94
|
+
t.Fatalf("write %s: %v", name, err)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return dir
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const projectDependenciesCalleeShapeSourceDirect = `import { Deep } from "./deep";
|
|
101
|
+
|
|
102
|
+
export const value = (() => {
|
|
103
|
+
const held: Deep = { id: "x" };
|
|
104
|
+
return held.id;
|
|
105
|
+
})();
|
|
106
|
+
`
|
|
107
|
+
|
|
108
|
+
const projectDependenciesCalleeShapeSourceIndirect = `export const value = (() => (input: string): string => input)()("x");
|
|
109
|
+
`
|
|
110
|
+
|
|
111
|
+
const projectDependenciesCalleeShapeSourceTagged = `export const value = ((_: TemplateStringsArray) => (input: string): string => input)` + "`x`" + `("y");
|
|
112
|
+
`
|
|
113
|
+
|
|
114
|
+
const projectDependenciesCalleeShapeSourceDeep = `export interface Deep {
|
|
115
|
+
id: string;
|
|
116
|
+
}
|
|
117
|
+
`
|
package/native/cmd/ttsc-typia/project_dependencies_callee_untransformed_barrel_transform_test.go
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesCalleeUntransformedBarrelTransform verifies a file
|
|
12
|
+
// typia did not transform still reports the modules its callees resolve
|
|
13
|
+
// through, which is what makes declaring that file complete honest.
|
|
14
|
+
//
|
|
15
|
+
// A file with no typia call is declared complete with an empty dependency list,
|
|
16
|
+
// and the claim behind it is that nothing outside the file can change a
|
|
17
|
+
// faithful re-print. Exactly one thing can: an edit that makes one of its calls
|
|
18
|
+
// a typia call. Re-pointing `export { is } from "./local"` at `"typia"` does
|
|
19
|
+
// that without touching the caller, so the barrel has to be in the caller's
|
|
20
|
+
// reported list even though the caller consulted no type at all -- otherwise
|
|
21
|
+
// the narrowed bound has nothing left to invalidate it (samchon/typia#2357).
|
|
22
|
+
//
|
|
23
|
+
// 1. Build a project where `plain.ts` calls `is<Alpha>` imported from
|
|
24
|
+
// `barrel.ts`, which re-exports a local helper rather than typia.
|
|
25
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
26
|
+
// 3. Assert `plain.ts` was left untransformed, so it is a class-2 file.
|
|
27
|
+
// 4. Assert it is declared complete, and that its reported list nevertheless
|
|
28
|
+
// carries `src/barrel.ts` and the `src/local.ts` the barrel resolves to.
|
|
29
|
+
func TestProjectDependenciesCalleeUntransformedBarrelTransform(t *testing.T) {
|
|
30
|
+
project := projectDependenciesCalleeUntransformedBarrelProject(t)
|
|
31
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
32
|
+
return runTransform([]string{
|
|
33
|
+
"--cwd", project,
|
|
34
|
+
"--tsconfig", "tsconfig.json",
|
|
35
|
+
"--output", "ts",
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
if code != 0 {
|
|
39
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
40
|
+
}
|
|
41
|
+
var envelope struct {
|
|
42
|
+
TypeScript map[string]string `json:"typescript"`
|
|
43
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
44
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
45
|
+
}
|
|
46
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
47
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
48
|
+
}
|
|
49
|
+
text := envelope.TypeScript["src/plain.ts"]
|
|
50
|
+
if !strings.Contains(text, "is<Alpha>(input)") {
|
|
51
|
+
t.Fatalf("src/plain.ts calls a non-typia helper and must be left untransformed, got:\n%s", text)
|
|
52
|
+
}
|
|
53
|
+
declared := map[string]bool{}
|
|
54
|
+
for _, key := range envelope.DependenciesComplete {
|
|
55
|
+
declared[key] = true
|
|
56
|
+
}
|
|
57
|
+
if !declared["src/plain.ts"] {
|
|
58
|
+
t.Fatalf("an untransformed file must be declared complete: %v", envelope.DependenciesComplete)
|
|
59
|
+
}
|
|
60
|
+
entries := envelope.Dependencies["src/plain.ts"]
|
|
61
|
+
found := map[string]bool{}
|
|
62
|
+
for _, entry := range entries {
|
|
63
|
+
found[entry] = true
|
|
64
|
+
}
|
|
65
|
+
if !found["src/barrel.ts"] {
|
|
66
|
+
t.Fatalf("dependencies of src/plain.ts must contain src/barrel.ts, whose retarget would make the call typia's: %v", entries)
|
|
67
|
+
}
|
|
68
|
+
if !found["src/local.ts"] {
|
|
69
|
+
t.Fatalf("dependencies of src/plain.ts must contain src/local.ts, which currently declares the callee: %v", entries)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func projectDependenciesCalleeUntransformedBarrelProject(t *testing.T) string {
|
|
74
|
+
t.Helper()
|
|
75
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
76
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
77
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
78
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
79
|
+
}
|
|
80
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-callee-untransformed-barrel-")
|
|
81
|
+
if err != nil {
|
|
82
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
83
|
+
}
|
|
84
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
85
|
+
src := filepath.Join(dir, "src")
|
|
86
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
87
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
88
|
+
}
|
|
89
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
90
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
91
|
+
}
|
|
92
|
+
for name, body := range map[string]string{
|
|
93
|
+
"plain.ts": projectDependenciesCalleeUntransformedBarrelSourcePlain,
|
|
94
|
+
"barrel.ts": projectDependenciesCalleeUntransformedBarrelSourceBarrel,
|
|
95
|
+
"local.ts": projectDependenciesCalleeUntransformedBarrelSourceLocal,
|
|
96
|
+
"alpha.ts": projectDependenciesCalleeUntransformedBarrelSourceAlpha,
|
|
97
|
+
} {
|
|
98
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
99
|
+
t.Fatalf("write %s: %v", name, err)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return dir
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const projectDependenciesCalleeUntransformedBarrelSourcePlain = `import { is } from "./barrel";
|
|
106
|
+
|
|
107
|
+
import { Alpha } from "./alpha";
|
|
108
|
+
|
|
109
|
+
export const checkAlpha = (input: unknown) => is<Alpha>(input);
|
|
110
|
+
`
|
|
111
|
+
|
|
112
|
+
const projectDependenciesCalleeUntransformedBarrelSourceBarrel = `export { is } from "./local";
|
|
113
|
+
`
|
|
114
|
+
|
|
115
|
+
const projectDependenciesCalleeUntransformedBarrelSourceLocal = `export const is = <T>(input: unknown): input is T => input !== null;
|
|
116
|
+
`
|
|
117
|
+
|
|
118
|
+
const projectDependenciesCalleeUntransformedBarrelSourceAlpha = `export interface Alpha {
|
|
119
|
+
id: string;
|
|
120
|
+
}
|
|
121
|
+
`
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestProjectDependenciesCompleteDiagnosticTransform verifies a file whose
|
|
11
|
+
// typia call raised a diagnostic is withheld from the completeness declaration.
|
|
12
|
+
//
|
|
13
|
+
// A diagnostic means the analysis stopped partway, so what it consulted before
|
|
14
|
+
// giving up is not that file's whole input set: an edit to a declaration it
|
|
15
|
+
// never reached could make the same call succeed and publish different text.
|
|
16
|
+
// The envelope is a failure result either way and the protocol allows a partial
|
|
17
|
+
// declaration, but a claim that is false is worth withholding rather than
|
|
18
|
+
// explaining (samchon/typia#2357).
|
|
19
|
+
//
|
|
20
|
+
// 1. Build a project where `rejected.ts` asks for a protobuf message over
|
|
21
|
+
// `bigint`, which no protobuf entry point supports, beside a `control.ts`
|
|
22
|
+
// that transforms cleanly.
|
|
23
|
+
// 2. Run project transform mode; the host reports the diagnostic and exits 3
|
|
24
|
+
// after printing the envelope.
|
|
25
|
+
// 3. Assert the diagnostic names `rejected.ts`, so the fixture failed for the
|
|
26
|
+
// reason the test intends.
|
|
27
|
+
// 4. Assert `control.ts` is declared complete and `rejected.ts` is not.
|
|
28
|
+
func TestProjectDependenciesCompleteDiagnosticTransform(t *testing.T) {
|
|
29
|
+
project := projectDependenciesCompleteDiagnosticProject(t)
|
|
30
|
+
out, _, code := ttscTypiaTestCapture(func() int {
|
|
31
|
+
return runTransform([]string{
|
|
32
|
+
"--cwd", project,
|
|
33
|
+
"--tsconfig", "tsconfig.json",
|
|
34
|
+
"--output", "ts",
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
if code != 3 {
|
|
38
|
+
t.Fatalf("a rejected call must fail the project transform with code 3, got %d:\n%s", code, out)
|
|
39
|
+
}
|
|
40
|
+
var envelope struct {
|
|
41
|
+
Diagnostics []struct {
|
|
42
|
+
File *string `json:"file"`
|
|
43
|
+
} `json:"diagnostics"`
|
|
44
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
45
|
+
}
|
|
46
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
47
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
48
|
+
}
|
|
49
|
+
named := false
|
|
50
|
+
for _, diagnostic := range envelope.Diagnostics {
|
|
51
|
+
if diagnostic.File != nil && filepath.Base(filepath.FromSlash(*diagnostic.File)) == "rejected.ts" {
|
|
52
|
+
named = true
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if !named {
|
|
56
|
+
t.Fatalf("the diagnostic must name src/rejected.ts, so the fixture failed for the intended reason: %s", out)
|
|
57
|
+
}
|
|
58
|
+
declared := map[string]bool{}
|
|
59
|
+
for _, key := range envelope.DependenciesComplete {
|
|
60
|
+
declared[key] = true
|
|
61
|
+
}
|
|
62
|
+
if !declared["src/control.ts"] {
|
|
63
|
+
t.Fatalf("a file that transformed cleanly must still be declared complete: %v", envelope.DependenciesComplete)
|
|
64
|
+
}
|
|
65
|
+
if declared["src/rejected.ts"] {
|
|
66
|
+
t.Fatalf("a file whose call typia could not lower must be withheld from the completeness declaration: %v", envelope.DependenciesComplete)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func projectDependenciesCompleteDiagnosticProject(t *testing.T) string {
|
|
71
|
+
t.Helper()
|
|
72
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
73
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
74
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
75
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
76
|
+
}
|
|
77
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-complete-diagnostic-")
|
|
78
|
+
if err != nil {
|
|
79
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
80
|
+
}
|
|
81
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
82
|
+
src := filepath.Join(dir, "src")
|
|
83
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
84
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
85
|
+
}
|
|
86
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
87
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
88
|
+
}
|
|
89
|
+
for name, body := range map[string]string{
|
|
90
|
+
"rejected.ts": projectDependenciesCompleteDiagnosticSourceRejected,
|
|
91
|
+
"control.ts": projectDependenciesCompleteDiagnosticSourceControl,
|
|
92
|
+
"shape.ts": projectDependenciesCompleteDiagnosticSourceShape,
|
|
93
|
+
} {
|
|
94
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
95
|
+
t.Fatalf("write %s: %v", name, err)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return dir
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const projectDependenciesCompleteDiagnosticSourceRejected = `import typia from "typia";
|
|
102
|
+
|
|
103
|
+
export const message = typia.protobuf.message<bigint>();
|
|
104
|
+
`
|
|
105
|
+
|
|
106
|
+
const projectDependenciesCompleteDiagnosticSourceControl = `import typia from "typia";
|
|
107
|
+
|
|
108
|
+
import { Shape } from "./shape";
|
|
109
|
+
|
|
110
|
+
export const validateShape = (input: unknown) => typia.is<Shape>(input);
|
|
111
|
+
`
|
|
112
|
+
|
|
113
|
+
const projectDependenciesCompleteDiagnosticSourceShape = `export interface Shape {
|
|
114
|
+
id: string;
|
|
115
|
+
}
|
|
116
|
+
`
|