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,159 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesInferredDeclarationTransform verifies a validated type
|
|
12
|
+
// that reaches a declaration with no written annotation withholds the
|
|
13
|
+
// completeness declaration, while its annotated twin keeps it.
|
|
14
|
+
//
|
|
15
|
+
// The written surface exists because the checker erases an alias of an
|
|
16
|
+
// intrinsic: `type Id = string` interns as the bare intrinsic and leaves no
|
|
17
|
+
// symbol for the type graph to follow, so only the syntax naming the alias can
|
|
18
|
+
// register its file (samchon/typia#2126). A `const` with an annotation has that
|
|
19
|
+
// syntax. A `const` typed by its initializer alone does not, and recovering it
|
|
20
|
+
// means following type inference over arbitrary expressions -- a call's return
|
|
21
|
+
// type, an overload's selection, a contextually typed parameter -- which is
|
|
22
|
+
// exactly the unbounded problem a typia call with no written type argument has.
|
|
23
|
+
// Both get the same answer rather than a partial walk that would leave the file
|
|
24
|
+
// declared on an incomplete list (samchon/typia#2357).
|
|
25
|
+
//
|
|
26
|
+
// 1. Build a project where `cfg.ts` exports one annotated constant, one
|
|
27
|
+
// inferred constant of the same shape, and one destructured binding, and
|
|
28
|
+
// three callers validate `typeof` each.
|
|
29
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
30
|
+
// 3. Assert every caller transformed, so the set differs only in where the
|
|
31
|
+
// type was written.
|
|
32
|
+
// 4. Assert the annotated caller is declared complete and names the
|
|
33
|
+
// annotation's own file, and that the inferred caller is withheld while
|
|
34
|
+
// keeping what it did report.
|
|
35
|
+
// 5. Assert the destructured caller is withheld too. A binding element is a
|
|
36
|
+
// declaration kind the predicate names nowhere, so it answers through the
|
|
37
|
+
// default -- which is what pins that the default is "withhold".
|
|
38
|
+
func TestProjectDependenciesInferredDeclarationTransform(t *testing.T) {
|
|
39
|
+
project := projectDependenciesInferredDeclarationProject(t)
|
|
40
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
41
|
+
return runTransform([]string{
|
|
42
|
+
"--cwd", project,
|
|
43
|
+
"--tsconfig", "tsconfig.json",
|
|
44
|
+
"--output", "ts",
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
if code != 0 {
|
|
48
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
49
|
+
}
|
|
50
|
+
var envelope struct {
|
|
51
|
+
TypeScript map[string]string `json:"typescript"`
|
|
52
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
53
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
54
|
+
}
|
|
55
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
56
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
57
|
+
}
|
|
58
|
+
for _, key := range []string{"src/annotated.ts", "src/inferred.ts", "src/destructured.ts"} {
|
|
59
|
+
if text := envelope.TypeScript[key]; !strings.Contains(text, "typeof input") {
|
|
60
|
+
t.Fatalf("%s must have been transformed into a generated validator, got:\n%s", key, text)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
declared := map[string]bool{}
|
|
64
|
+
for _, key := range envelope.DependenciesComplete {
|
|
65
|
+
declared[key] = true
|
|
66
|
+
}
|
|
67
|
+
if !declared["src/annotated.ts"] {
|
|
68
|
+
t.Fatalf("a validated type written as an annotation bounds its inputs and must be declared complete: %v", envelope.DependenciesComplete)
|
|
69
|
+
}
|
|
70
|
+
found := map[string]bool{}
|
|
71
|
+
for _, entry := range envelope.Dependencies["src/annotated.ts"] {
|
|
72
|
+
found[entry] = true
|
|
73
|
+
}
|
|
74
|
+
if !found["src/shape.ts"] {
|
|
75
|
+
t.Fatalf("the annotated constant's own annotation file must be reported: %v", envelope.Dependencies["src/annotated.ts"])
|
|
76
|
+
}
|
|
77
|
+
if declared["src/inferred.ts"] {
|
|
78
|
+
t.Fatalf("a validated type that reaches a declaration typed by its initializer alone must be withheld: %v", envelope.DependenciesComplete)
|
|
79
|
+
}
|
|
80
|
+
if len(envelope.Dependencies["src/inferred.ts"]) == 0 {
|
|
81
|
+
t.Fatalf("withholding the declaration must not strip what src/inferred.ts did report: %v", envelope.Dependencies)
|
|
82
|
+
}
|
|
83
|
+
if declared["src/destructured.ts"] {
|
|
84
|
+
t.Fatalf("a declaration kind the predicate names nowhere must answer through the default and withhold: %v", envelope.DependenciesComplete)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func projectDependenciesInferredDeclarationProject(t *testing.T) string {
|
|
89
|
+
t.Helper()
|
|
90
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
91
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
92
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
93
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
94
|
+
}
|
|
95
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-inferred-declaration-")
|
|
96
|
+
if err != nil {
|
|
97
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
98
|
+
}
|
|
99
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
100
|
+
src := filepath.Join(dir, "src")
|
|
101
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
102
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
103
|
+
}
|
|
104
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
105
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
106
|
+
}
|
|
107
|
+
for name, body := range map[string]string{
|
|
108
|
+
"annotated.ts": projectDependenciesInferredDeclarationSourceAnnotated,
|
|
109
|
+
"inferred.ts": projectDependenciesInferredDeclarationSourceInferred,
|
|
110
|
+
"destructured.ts": projectDependenciesInferredDeclarationSourceDestructured,
|
|
111
|
+
"cfg.ts": projectDependenciesInferredDeclarationSourceCfg,
|
|
112
|
+
"shape.ts": projectDependenciesInferredDeclarationSourceShape,
|
|
113
|
+
"id.ts": projectDependenciesInferredDeclarationSourceId,
|
|
114
|
+
} {
|
|
115
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
116
|
+
t.Fatalf("write %s: %v", name, err)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return dir
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const projectDependenciesInferredDeclarationSourceAnnotated = `import typia from "typia";
|
|
123
|
+
|
|
124
|
+
import { written } from "./cfg";
|
|
125
|
+
|
|
126
|
+
export const check = (input: unknown) => typia.is<typeof written>(input);
|
|
127
|
+
`
|
|
128
|
+
|
|
129
|
+
const projectDependenciesInferredDeclarationSourceInferred = `import typia from "typia";
|
|
130
|
+
|
|
131
|
+
import { guessed } from "./cfg";
|
|
132
|
+
|
|
133
|
+
export const check = (input: unknown) => typia.is<typeof guessed>(input);
|
|
134
|
+
`
|
|
135
|
+
|
|
136
|
+
const projectDependenciesInferredDeclarationSourceDestructured = `import typia from "typia";
|
|
137
|
+
|
|
138
|
+
import { picked } from "./cfg";
|
|
139
|
+
|
|
140
|
+
export const check = (input: unknown) => typia.is<typeof picked>(input);
|
|
141
|
+
`
|
|
142
|
+
|
|
143
|
+
const projectDependenciesInferredDeclarationSourceCfg = `import { Id } from "./id";
|
|
144
|
+
import { Shape } from "./shape";
|
|
145
|
+
|
|
146
|
+
export const written: Shape = { id: "w" };
|
|
147
|
+
|
|
148
|
+
export const guessed = { id: "g" as Id };
|
|
149
|
+
|
|
150
|
+
export const { id: picked } = written;
|
|
151
|
+
`
|
|
152
|
+
|
|
153
|
+
const projectDependenciesInferredDeclarationSourceShape = `export interface Shape {
|
|
154
|
+
id: string;
|
|
155
|
+
}
|
|
156
|
+
`
|
|
157
|
+
|
|
158
|
+
const projectDependenciesInferredDeclarationSourceId = `export type Id = string;
|
|
159
|
+
`
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestProjectDependenciesJsDocTypedefTransform verifies an `allowJs` project
|
|
11
|
+
// whose type is written as a JSDoc typedef reports the file behind a
|
|
12
|
+
// `@property` tag and still declares the consumer complete.
|
|
13
|
+
//
|
|
14
|
+
// A JSDoc typedef reaches the walk as a `JSTypeAliasDeclaration`, so its whole
|
|
15
|
+
// node is surfaced and every reference written inside it -- including the
|
|
16
|
+
// `import("./id").Id` a `@property` tag names -- resolves from there. The tags
|
|
17
|
+
// themselves never arrive at the boundedness predicate, so this is what pins
|
|
18
|
+
// that they do not need to: were a property enumeration to start handing tags
|
|
19
|
+
// over, the default would withhold and this file would stop being declared
|
|
20
|
+
// (samchon/typia#2357).
|
|
21
|
+
//
|
|
22
|
+
// 1. Build an `allowJs` project where `main.ts` validates a typedef declared
|
|
23
|
+
// in `doc.js`, whose `@property` tag names a typedef in `id.js`.
|
|
24
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
25
|
+
// 3. Assert `dependencies["src/main.ts"]` carries both `src/doc.js` and
|
|
26
|
+
// `src/id.js`.
|
|
27
|
+
// 4. Assert `src/main.ts` is declared complete, so the narrowed bound is the
|
|
28
|
+
// one carrying them.
|
|
29
|
+
func TestProjectDependenciesJsDocTypedefTransform(t *testing.T) {
|
|
30
|
+
project := projectDependenciesJsDocTypedefProject(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
|
+
entries := envelope.Dependencies["src/main.ts"]
|
|
49
|
+
found := map[string]bool{}
|
|
50
|
+
for _, entry := range entries {
|
|
51
|
+
found[entry] = true
|
|
52
|
+
}
|
|
53
|
+
if !found["src/doc.js"] {
|
|
54
|
+
t.Fatalf("dependencies of src/main.ts must contain the typedef's own file src/doc.js: %v", entries)
|
|
55
|
+
}
|
|
56
|
+
if !found["src/id.js"] {
|
|
57
|
+
t.Fatalf("dependencies of src/main.ts must contain src/id.js, which a @property tag names: %v", entries)
|
|
58
|
+
}
|
|
59
|
+
declared := false
|
|
60
|
+
for _, key := range envelope.DependenciesComplete {
|
|
61
|
+
if key == "src/main.ts" {
|
|
62
|
+
declared = true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if !declared {
|
|
66
|
+
t.Fatalf("src/main.ts must be declared complete, so the reported entry is the whole bound: %v", envelope.DependenciesComplete)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func projectDependenciesJsDocTypedefProject(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-jsdoc-typedef-")
|
|
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(projectDependenciesJsDocTypedefTSConfig), 0o644); err != nil {
|
|
87
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
88
|
+
}
|
|
89
|
+
for name, body := range map[string]string{
|
|
90
|
+
"main.ts": projectDependenciesJsDocTypedefSourceMain,
|
|
91
|
+
"doc.js": projectDependenciesJsDocTypedefSourceDoc,
|
|
92
|
+
"id.js": projectDependenciesJsDocTypedefSourceId,
|
|
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 projectDependenciesJsDocTypedefTSConfig = `{
|
|
102
|
+
"compilerOptions": {
|
|
103
|
+
"target": "ES2022",
|
|
104
|
+
"module": "commonjs",
|
|
105
|
+
"moduleResolution": "bundler",
|
|
106
|
+
"ignoreDeprecations": "6.0",
|
|
107
|
+
"esModuleInterop": true,
|
|
108
|
+
"strict": true,
|
|
109
|
+
"skipLibCheck": true,
|
|
110
|
+
"allowJs": true,
|
|
111
|
+
"rootDir": "src",
|
|
112
|
+
"outDir": "dist"
|
|
113
|
+
},
|
|
114
|
+
"include": ["src"]
|
|
115
|
+
}
|
|
116
|
+
`
|
|
117
|
+
|
|
118
|
+
const projectDependenciesJsDocTypedefSourceMain = `import typia from "typia";
|
|
119
|
+
|
|
120
|
+
export const check = (input: unknown) => typia.is<import("./doc").Doc>(input);
|
|
121
|
+
`
|
|
122
|
+
|
|
123
|
+
const projectDependenciesJsDocTypedefSourceDoc = `/**
|
|
124
|
+
* @typedef {Object} Doc
|
|
125
|
+
* @property {import("./id").Id} id
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
export const marker = 1;
|
|
129
|
+
`
|
|
130
|
+
|
|
131
|
+
const projectDependenciesJsDocTypedefSourceId = `/** @typedef {string} Id */
|
|
132
|
+
|
|
133
|
+
export const marker = 1;
|
|
134
|
+
`
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestProjectDependenciesNamedTupleMemberTransform verifies a named tuple
|
|
11
|
+
// member's written type reaches the reported dependencies, and that the file
|
|
12
|
+
// consulting it is still declared complete.
|
|
13
|
+
//
|
|
14
|
+
// `[id: Id]` writes its type as plainly as a property annotation does, and the
|
|
15
|
+
// checker interns `type Id = string` as the bare intrinsic, so the written
|
|
16
|
+
// surface is the only thing that can register `id.ts` (samchon/typia#2126). The
|
|
17
|
+
// member is a declaration kind of its own, so it needs its own place in both the
|
|
18
|
+
// surface that walks a written type and the predicate that decides whether a
|
|
19
|
+
// file may be declared on the strength of one — a kind in the second without the
|
|
20
|
+
// first is exactly an over-declaration (samchon/typia#2357).
|
|
21
|
+
//
|
|
22
|
+
// 1. Build a project where `a.ts` validates a tuple alias whose first member is
|
|
23
|
+
// named and typed `Id`, beside an alias the tuple never uses.
|
|
24
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
25
|
+
// 3. Assert `dependencies["src/a.ts"]` contains `src/id.ts` and that `a.ts` is
|
|
26
|
+
// declared complete, so the narrowed bound is the one carrying it.
|
|
27
|
+
// 4. Assert it omits `src/unused.ts`, which no consulted member names.
|
|
28
|
+
func TestProjectDependenciesNamedTupleMemberTransform(t *testing.T) {
|
|
29
|
+
project := projectDependenciesNamedTupleMemberProject(t)
|
|
30
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
31
|
+
return runTransform([]string{
|
|
32
|
+
"--cwd", project,
|
|
33
|
+
"--tsconfig", "tsconfig.json",
|
|
34
|
+
"--output", "ts",
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
if code != 0 {
|
|
38
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
39
|
+
}
|
|
40
|
+
var envelope struct {
|
|
41
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
42
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
43
|
+
}
|
|
44
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
45
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
46
|
+
}
|
|
47
|
+
entries := envelope.Dependencies["src/a.ts"]
|
|
48
|
+
found := map[string]bool{}
|
|
49
|
+
for _, entry := range entries {
|
|
50
|
+
found[entry] = true
|
|
51
|
+
}
|
|
52
|
+
if !found["src/pair.ts"] {
|
|
53
|
+
t.Fatalf("dependencies of src/a.ts must contain the declaring file src/pair.ts: %v", entries)
|
|
54
|
+
}
|
|
55
|
+
if !found["src/id.ts"] {
|
|
56
|
+
t.Fatalf("dependencies of src/a.ts must contain src/id.ts, which the named tuple member's type names: %v", entries)
|
|
57
|
+
}
|
|
58
|
+
if found["src/unused.ts"] {
|
|
59
|
+
t.Fatalf("dependencies of src/a.ts must not contain src/unused.ts, which no consulted member names: %v", entries)
|
|
60
|
+
}
|
|
61
|
+
declared := false
|
|
62
|
+
for _, key := range envelope.DependenciesComplete {
|
|
63
|
+
if key == "src/a.ts" {
|
|
64
|
+
declared = true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if !declared {
|
|
68
|
+
t.Fatalf("src/a.ts must be declared complete, so the reported entry is the whole bound: %v", envelope.DependenciesComplete)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func projectDependenciesNamedTupleMemberProject(t *testing.T) string {
|
|
73
|
+
t.Helper()
|
|
74
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
75
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
76
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
77
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
78
|
+
}
|
|
79
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-named-tuple-member-")
|
|
80
|
+
if err != nil {
|
|
81
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
82
|
+
}
|
|
83
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
84
|
+
src := filepath.Join(dir, "src")
|
|
85
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
86
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
87
|
+
}
|
|
88
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
89
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
90
|
+
}
|
|
91
|
+
for name, body := range map[string]string{
|
|
92
|
+
"a.ts": projectDependenciesNamedTupleMemberSourceA,
|
|
93
|
+
"pair.ts": projectDependenciesNamedTupleMemberSourcePair,
|
|
94
|
+
"id.ts": projectDependenciesNamedTupleMemberSourceId,
|
|
95
|
+
"unused.ts": projectDependenciesNamedTupleMemberSourceUnused,
|
|
96
|
+
} {
|
|
97
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
98
|
+
t.Fatalf("write %s: %v", name, err)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return dir
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const projectDependenciesNamedTupleMemberSourceA = `import typia from "typia";
|
|
105
|
+
|
|
106
|
+
import { Pair } from "./pair";
|
|
107
|
+
|
|
108
|
+
export const check = (input: unknown) => typia.is<Pair>(input);
|
|
109
|
+
`
|
|
110
|
+
|
|
111
|
+
const projectDependenciesNamedTupleMemberSourcePair = `import { Id } from "./id";
|
|
112
|
+
import { Unused } from "./unused";
|
|
113
|
+
|
|
114
|
+
export type Pair = [id: Id, count: number];
|
|
115
|
+
|
|
116
|
+
export type Spare = [label: Unused];
|
|
117
|
+
`
|
|
118
|
+
|
|
119
|
+
const projectDependenciesNamedTupleMemberSourceId = `export type Id = string;
|
|
120
|
+
`
|
|
121
|
+
|
|
122
|
+
const projectDependenciesNamedTupleMemberSourceUnused = `export type Unused = string;
|
|
123
|
+
`
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesQualifiedBarrelTransform verifies a QUALIFIED type
|
|
12
|
+
// reference reports the barrel that selected its namespace half, not only the
|
|
13
|
+
// module declaring its terminus.
|
|
14
|
+
//
|
|
15
|
+
// `TestProjectDependenciesBarrelReexportTransform` pins the bare spelling, and
|
|
16
|
+
// it works because the checker answers `Alpha` with the local import alias, whose
|
|
17
|
+
// path metadataDependency_touchPath walks. `Kind.A` is answered with the enum
|
|
18
|
+
// member instead -- not an alias, no path to walk -- so the barrel between the
|
|
19
|
+
// caller and the enum went unreported while its value went straight into the
|
|
20
|
+
// validator as `"alpha" === input.kind`. Re-pointing that barrel at another enum
|
|
21
|
+
// changed the emitted constant with nothing left to invalidate the file
|
|
22
|
+
// (samchon/typia#2126, samchon/typia#2357).
|
|
23
|
+
//
|
|
24
|
+
// 1. Build a project where `main.ts` validates `Doc`, whose member type is
|
|
25
|
+
// `Kind.A` for a `Kind` imported from `barrel.ts`, which re-exports it from
|
|
26
|
+
// `kind.ts` and also re-exports an unconsumed `Unused` from `unused.ts`.
|
|
27
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
28
|
+
// 3. Assert the emitted validator compares against the enum member's value, so
|
|
29
|
+
// the fixture really does read `kind.ts` into its output.
|
|
30
|
+
// 4. Assert `dependencies["src/main.ts"]` contains `src/barrel.ts` and
|
|
31
|
+
// `src/kind.ts`, and that `src/main.ts` is declared complete.
|
|
32
|
+
// 5. Assert it omits `src/unused.ts`, the sibling the reference never traverses.
|
|
33
|
+
func TestProjectDependenciesQualifiedBarrelTransform(t *testing.T) {
|
|
34
|
+
project := projectDependenciesQualifiedBarrelProject(t)
|
|
35
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
36
|
+
return runTransform([]string{
|
|
37
|
+
"--cwd", project,
|
|
38
|
+
"--tsconfig", "tsconfig.json",
|
|
39
|
+
"--output", "ts",
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
if code != 0 {
|
|
43
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
44
|
+
}
|
|
45
|
+
var envelope struct {
|
|
46
|
+
TypeScript map[string]string `json:"typescript"`
|
|
47
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
48
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
49
|
+
}
|
|
50
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
51
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
52
|
+
}
|
|
53
|
+
if text := envelope.TypeScript["src/main.ts"]; !strings.Contains(text, `"alpha"`) {
|
|
54
|
+
t.Fatalf("the validator must compare against the enum member's value, so the fixture reads kind.ts into its output, got:\n%s", text)
|
|
55
|
+
}
|
|
56
|
+
entries := envelope.Dependencies["src/main.ts"]
|
|
57
|
+
found := map[string]bool{}
|
|
58
|
+
for _, entry := range entries {
|
|
59
|
+
found[entry] = true
|
|
60
|
+
}
|
|
61
|
+
if !found["src/kind.ts"] {
|
|
62
|
+
t.Fatalf("dependencies of src/main.ts must contain src/kind.ts, which declares the member the type names: %v", entries)
|
|
63
|
+
}
|
|
64
|
+
if !found["src/barrel.ts"] {
|
|
65
|
+
t.Fatalf("dependencies of src/main.ts must contain src/barrel.ts, which selects the enum the qualified name reads: %v", entries)
|
|
66
|
+
}
|
|
67
|
+
if found["src/unused.ts"] {
|
|
68
|
+
t.Fatalf("dependencies of src/main.ts must not contain src/unused.ts, a sibling the reference never traverses: %v", entries)
|
|
69
|
+
}
|
|
70
|
+
declared := false
|
|
71
|
+
for _, key := range envelope.DependenciesComplete {
|
|
72
|
+
if key == "src/main.ts" {
|
|
73
|
+
declared = true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if !declared {
|
|
77
|
+
t.Fatalf("src/main.ts must be declared complete, so the reported entry is the whole bound: %v", envelope.DependenciesComplete)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func projectDependenciesQualifiedBarrelProject(t *testing.T) string {
|
|
82
|
+
t.Helper()
|
|
83
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
84
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
85
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
86
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
87
|
+
}
|
|
88
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-qualified-barrel-")
|
|
89
|
+
if err != nil {
|
|
90
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
91
|
+
}
|
|
92
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
93
|
+
src := filepath.Join(dir, "src")
|
|
94
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
95
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
96
|
+
}
|
|
97
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
98
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
99
|
+
}
|
|
100
|
+
for name, body := range map[string]string{
|
|
101
|
+
"main.ts": projectDependenciesQualifiedBarrelSourceMain,
|
|
102
|
+
"doc.ts": projectDependenciesQualifiedBarrelSourceDoc,
|
|
103
|
+
"barrel.ts": projectDependenciesQualifiedBarrelSourceBarrel,
|
|
104
|
+
"kind.ts": projectDependenciesQualifiedBarrelSourceKind,
|
|
105
|
+
"unused.ts": projectDependenciesQualifiedBarrelSourceUnused,
|
|
106
|
+
} {
|
|
107
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
108
|
+
t.Fatalf("write %s: %v", name, err)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return dir
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const projectDependenciesQualifiedBarrelSourceMain = `import typia from "typia";
|
|
115
|
+
|
|
116
|
+
import { Doc } from "./doc";
|
|
117
|
+
|
|
118
|
+
export const check = (input: unknown) => typia.is<Doc>(input);
|
|
119
|
+
`
|
|
120
|
+
|
|
121
|
+
const projectDependenciesQualifiedBarrelSourceDoc = `import { Kind } from "./barrel";
|
|
122
|
+
|
|
123
|
+
export interface Doc {
|
|
124
|
+
kind: Kind.A;
|
|
125
|
+
}
|
|
126
|
+
`
|
|
127
|
+
|
|
128
|
+
const projectDependenciesQualifiedBarrelSourceBarrel = `export { Kind } from "./kind";
|
|
129
|
+
export { Unused } from "./unused";
|
|
130
|
+
`
|
|
131
|
+
|
|
132
|
+
const projectDependenciesQualifiedBarrelSourceKind = `export enum Kind {
|
|
133
|
+
A = "alpha",
|
|
134
|
+
B = "beta",
|
|
135
|
+
}
|
|
136
|
+
`
|
|
137
|
+
|
|
138
|
+
const projectDependenciesQualifiedBarrelSourceUnused = `export enum Unused {
|
|
139
|
+
C = "gamma",
|
|
140
|
+
}
|
|
141
|
+
`
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestProjectDependenciesTypeParameterDefaultTransform verifies an interface's
|
|
11
|
+
// type-parameter default reports the file its alias comes from.
|
|
12
|
+
//
|
|
13
|
+
// `typia.is<Wrapper>()` over `interface Wrapper<T = Id>` validates whatever `Id`
|
|
14
|
+
// is, and the checker interns `type Id = string` as the bare intrinsic, so the
|
|
15
|
+
// resolved type carries no symbol the type graph can follow to `id.ts`
|
|
16
|
+
// (samchon/typia#2126). The equivalent `type AliasWrapper<T = Id> = { v: T }`
|
|
17
|
+
// always reported it, because a `type` alias surfaces its whole node; an
|
|
18
|
+
// interface surfaced only its index signatures, one declaration kind away from
|
|
19
|
+
// the same defect (samchon/typia#2357).
|
|
20
|
+
//
|
|
21
|
+
// 1. Build a project where `a.ts` validates `Wrapper` from `wrapper.ts`, whose
|
|
22
|
+
// type parameter defaults to `Id` from `id.ts`, and where the same file
|
|
23
|
+
// declares a second interface defaulting to `Unused` from `unused.ts`.
|
|
24
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
25
|
+
// 3. Assert `dependencies["src/a.ts"]` contains `src/id.ts`, and that `a.ts`
|
|
26
|
+
// is declared complete so the narrowed bound is the one that carries it.
|
|
27
|
+
// 4. Assert it omits `src/unused.ts`, whose interface the call never reaches.
|
|
28
|
+
func TestProjectDependenciesTypeParameterDefaultTransform(t *testing.T) {
|
|
29
|
+
project := projectDependenciesTypeParameterDefaultProject(t)
|
|
30
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
31
|
+
return runTransform([]string{
|
|
32
|
+
"--cwd", project,
|
|
33
|
+
"--tsconfig", "tsconfig.json",
|
|
34
|
+
"--output", "ts",
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
if code != 0 {
|
|
38
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
39
|
+
}
|
|
40
|
+
var envelope struct {
|
|
41
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
42
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
43
|
+
}
|
|
44
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
45
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
46
|
+
}
|
|
47
|
+
entries := envelope.Dependencies["src/a.ts"]
|
|
48
|
+
found := map[string]bool{}
|
|
49
|
+
for _, entry := range entries {
|
|
50
|
+
found[entry] = true
|
|
51
|
+
}
|
|
52
|
+
if !found["src/wrapper.ts"] {
|
|
53
|
+
t.Fatalf("dependencies of src/a.ts must contain the declaring file src/wrapper.ts: %v", entries)
|
|
54
|
+
}
|
|
55
|
+
if !found["src/id.ts"] {
|
|
56
|
+
t.Fatalf("dependencies of src/a.ts must contain src/id.ts, which the type-parameter default names: %v", entries)
|
|
57
|
+
}
|
|
58
|
+
if found["src/unused.ts"] {
|
|
59
|
+
t.Fatalf("dependencies of src/a.ts must not contain src/unused.ts, which no consulted type reaches: %v", entries)
|
|
60
|
+
}
|
|
61
|
+
declared := false
|
|
62
|
+
for _, key := range envelope.DependenciesComplete {
|
|
63
|
+
if key == "src/a.ts" {
|
|
64
|
+
declared = true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if !declared {
|
|
68
|
+
t.Fatalf("src/a.ts must be declared complete, so the reported entry is the whole bound: %v", envelope.DependenciesComplete)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func projectDependenciesTypeParameterDefaultProject(t *testing.T) string {
|
|
73
|
+
t.Helper()
|
|
74
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
75
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
76
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
77
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
78
|
+
}
|
|
79
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-type-parameter-default-")
|
|
80
|
+
if err != nil {
|
|
81
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
82
|
+
}
|
|
83
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
84
|
+
src := filepath.Join(dir, "src")
|
|
85
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
86
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
87
|
+
}
|
|
88
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
89
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
90
|
+
}
|
|
91
|
+
for name, body := range map[string]string{
|
|
92
|
+
"a.ts": projectDependenciesTypeParameterDefaultSourceA,
|
|
93
|
+
"wrapper.ts": projectDependenciesTypeParameterDefaultSourceWrapper,
|
|
94
|
+
"id.ts": projectDependenciesTypeParameterDefaultSourceId,
|
|
95
|
+
"unused.ts": projectDependenciesTypeParameterDefaultSourceUnused,
|
|
96
|
+
} {
|
|
97
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
98
|
+
t.Fatalf("write %s: %v", name, err)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return dir
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const projectDependenciesTypeParameterDefaultSourceA = `import typia from "typia";
|
|
105
|
+
|
|
106
|
+
import { Wrapper } from "./wrapper";
|
|
107
|
+
|
|
108
|
+
export const check = (input: unknown) => typia.is<Wrapper>(input);
|
|
109
|
+
`
|
|
110
|
+
|
|
111
|
+
const projectDependenciesTypeParameterDefaultSourceWrapper = `import { Id } from "./id";
|
|
112
|
+
import { Unused } from "./unused";
|
|
113
|
+
|
|
114
|
+
export interface Wrapper<T = Id> {
|
|
115
|
+
value: T;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface Spare<T = Unused> {
|
|
119
|
+
value: T;
|
|
120
|
+
}
|
|
121
|
+
`
|
|
122
|
+
|
|
123
|
+
const projectDependenciesTypeParameterDefaultSourceId = `export type Id = string;
|
|
124
|
+
`
|
|
125
|
+
|
|
126
|
+
const projectDependenciesTypeParameterDefaultSourceUnused = `export type Unused = number;
|
|
127
|
+
`
|