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
package/native/cmd/ttsc-typia/project_dependencies_complete_replaced_library_transform_test.go
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesCompleteReplacedLibraryTransform verifies a file whose
|
|
12
|
+
// analysis consulted an on-disk default library is withheld from the
|
|
13
|
+
// completeness declaration.
|
|
14
|
+
//
|
|
15
|
+
// The envelope has never reported a file the compiler classifies as a default
|
|
16
|
+
// library, because a bundled library is a toolchain input rather than a project
|
|
17
|
+
// input (samchon/typia#2108). Under `libReplacement` that file is nevertheless a
|
|
18
|
+
// real path the ttsc reference graph carries in `graph.globals`, so the
|
|
19
|
+
// host-owned bound watches it. Declaring such a file complete drops `globals`
|
|
20
|
+
// and would stop watching an input the default bound watches -- editing the
|
|
21
|
+
// replacement lib would then serve a stale validator. Withholding the
|
|
22
|
+
// declaration keeps that file on the sound bound while every file that consulted
|
|
23
|
+
// no replaced library still narrows (samchon/typia#2357).
|
|
24
|
+
//
|
|
25
|
+
// 1. Publish `@typescript/lib-es2015/collection.d.ts` and enable
|
|
26
|
+
// `libReplacement`, so `Map` is declared by an on-disk file.
|
|
27
|
+
// 2. Transform a project where `replaced.ts` validates `Map<string, number>`
|
|
28
|
+
// and `control.ts` validates a project-declared interface.
|
|
29
|
+
// 3. Assert both files transformed, so they differ only in what they consulted.
|
|
30
|
+
// 4. Assert `control.ts` is declared complete and `replaced.ts` is not, and
|
|
31
|
+
// that the replacement file never leaked into the reported dependencies.
|
|
32
|
+
// 5. Assert `nocall.ts` -- no typia call at all, only a CALL on a method the
|
|
33
|
+
// replaced library declares -- keeps its declaration. Touching a default
|
|
34
|
+
// library while deciding which declaration a call resolves to cannot change
|
|
35
|
+
// the emit: typia recognizes its own calls by a `/typia/lib` or `/typia/src`
|
|
36
|
+
// path, which a compiler-classified default library never has. Withholding
|
|
37
|
+
// there cost a file its declaration for writing `table.get(...)`, and under
|
|
38
|
+
// `noembed` -- where every library is on disk -- for calling anything at all
|
|
39
|
+
// (samchon/typia#2361). `nocall2.ts` is the control that only annotates the
|
|
40
|
+
// same type, and `replaced.ts` above is the twin that must stay withheld, so
|
|
41
|
+
// the fix cannot degenerate into never withholding on a library.
|
|
42
|
+
func TestProjectDependenciesCompleteReplacedLibraryTransform(t *testing.T) {
|
|
43
|
+
project := projectDependenciesCompleteReplacedLibraryProject(t)
|
|
44
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
45
|
+
return runTransform([]string{
|
|
46
|
+
"--cwd", project,
|
|
47
|
+
"--tsconfig", "tsconfig.json",
|
|
48
|
+
"--output", "ts",
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
if code != 0 {
|
|
52
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
53
|
+
}
|
|
54
|
+
var envelope struct {
|
|
55
|
+
TypeScript map[string]string `json:"typescript"`
|
|
56
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
57
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
58
|
+
}
|
|
59
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
60
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
61
|
+
}
|
|
62
|
+
for _, key := range []string{"src/replaced.ts", "src/control.ts"} {
|
|
63
|
+
if text := envelope.TypeScript[key]; !strings.Contains(text, "input is") {
|
|
64
|
+
t.Fatalf("%s must have been transformed into a generated validator, got:\n%s", key, text)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
declared := map[string]bool{}
|
|
68
|
+
for _, key := range envelope.DependenciesComplete {
|
|
69
|
+
declared[key] = true
|
|
70
|
+
}
|
|
71
|
+
if !declared["src/control.ts"] {
|
|
72
|
+
t.Fatalf("a file that consulted no replaced library must still be declared complete: %v", envelope.DependenciesComplete)
|
|
73
|
+
}
|
|
74
|
+
if declared["src/replaced.ts"] {
|
|
75
|
+
t.Fatalf("a file that consulted an on-disk default library must be withheld from the completeness declaration: %v", envelope.DependenciesComplete)
|
|
76
|
+
}
|
|
77
|
+
for _, entry := range envelope.Dependencies["src/replaced.ts"] {
|
|
78
|
+
if strings.Contains(entry, "@typescript/lib-es2015") {
|
|
79
|
+
t.Fatalf("the reported dependencies must keep excluding compiler-classified default libraries, got %q", entry)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for _, key := range []string{"src/nocall.ts", "src/nocall2.ts"} {
|
|
83
|
+
if !declared[key] {
|
|
84
|
+
t.Fatalf(
|
|
85
|
+
"%s holds no typia call, so nothing about the replaced library can "+
|
|
86
|
+
"change its emitted text; withholding it means the identity channel "+
|
|
87
|
+
"is still charging a default-library touch: %v",
|
|
88
|
+
key,
|
|
89
|
+
envelope.DependenciesComplete,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func projectDependenciesCompleteReplacedLibraryProject(t *testing.T) string {
|
|
96
|
+
t.Helper()
|
|
97
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
98
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
99
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
100
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
101
|
+
}
|
|
102
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-complete-replaced-library-")
|
|
103
|
+
if err != nil {
|
|
104
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
105
|
+
}
|
|
106
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
107
|
+
for name, content := range map[string]string{
|
|
108
|
+
"tsconfig.json": libReplacementNativeIdentityTSConfig,
|
|
109
|
+
"node_modules/@typescript/lib-es2015/package.json": libReplacementNativeIdentityPackageJSON,
|
|
110
|
+
"node_modules/@typescript/lib-es2015/collection.d.ts": libReplacementNativeIdentityCollectionLib,
|
|
111
|
+
"src/replaced.ts": projectDependenciesCompleteReplacedLibrarySourceReplaced,
|
|
112
|
+
"src/control.ts": projectDependenciesCompleteReplacedLibrarySourceControl,
|
|
113
|
+
"src/shape.ts": projectDependenciesCompleteReplacedLibrarySourceShape,
|
|
114
|
+
"src/nocall.ts": projectDependenciesCompleteReplacedLibrarySourceNoCall,
|
|
115
|
+
"src/nocall2.ts": projectDependenciesCompleteReplacedLibrarySourceNoCall2,
|
|
116
|
+
} {
|
|
117
|
+
path := filepath.Join(dir, filepath.FromSlash(name))
|
|
118
|
+
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
119
|
+
t.Fatalf("mkdir fixture path %s: %v", filepath.Dir(path), err)
|
|
120
|
+
}
|
|
121
|
+
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
122
|
+
t.Fatalf("write fixture file %s: %v", path, err)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return dir
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const projectDependenciesCompleteReplacedLibrarySourceReplaced = `import typia from "typia";
|
|
129
|
+
|
|
130
|
+
export const validateMap = (input: unknown) => typia.is<Map<string, number>>(input);
|
|
131
|
+
`
|
|
132
|
+
|
|
133
|
+
const projectDependenciesCompleteReplacedLibrarySourceControl = `import typia from "typia";
|
|
134
|
+
|
|
135
|
+
import { Shape } from "./shape";
|
|
136
|
+
|
|
137
|
+
export const validateShape = (input: unknown) => typia.is<Shape>(input);
|
|
138
|
+
`
|
|
139
|
+
|
|
140
|
+
const projectDependenciesCompleteReplacedLibrarySourceShape = `export interface Shape {
|
|
141
|
+
id: string;
|
|
142
|
+
}
|
|
143
|
+
`
|
|
144
|
+
|
|
145
|
+
// No typia call anywhere, and a CALL on a method the replaced library declares.
|
|
146
|
+
const projectDependenciesCompleteReplacedLibrarySourceNoCall = `const table = new Map<string, number>();
|
|
147
|
+
|
|
148
|
+
export const lookup = (key: string): number | undefined => table.get(key);
|
|
149
|
+
`
|
|
150
|
+
|
|
151
|
+
// The control: the same type, annotated but never called on.
|
|
152
|
+
const projectDependenciesCompleteReplacedLibrarySourceNoCall2 = `export const size = (table: Map<string, number>): number => table.size;
|
|
153
|
+
`
|
package/native/cmd/ttsc-typia/project_dependencies_complete_untouched_reprint_transform_test.go
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesCompleteUntouchedReprintTransform verifies a file typia
|
|
12
|
+
// left untouched is re-printed without any type-driven lowering, which is the
|
|
13
|
+
// premise under which the envelope declares such a file complete.
|
|
14
|
+
//
|
|
15
|
+
// Declaring a file complete with an empty dependency list claims that nothing
|
|
16
|
+
// the type system knows about any other file can change its published text.
|
|
17
|
+
// That holds only because this host prints the parsed AST through a printer
|
|
18
|
+
// handed neither a Checker nor an emit resolver and runs none of the JS script
|
|
19
|
+
// transformers, so no type-driven import elision, `design:type` metadata,
|
|
20
|
+
// `enum`, or `namespace` lowering can reach the envelope. Were any of them to
|
|
21
|
+
// run, an edit to a consulted declaration would change the output of a file
|
|
22
|
+
// with nothing left watching it (samchon/typia#2357, samchon/ttsc#1259).
|
|
23
|
+
//
|
|
24
|
+
// 1. Compile a project with `experimentalDecorators` and
|
|
25
|
+
// `emitDecoratorMetadata` on, whose `untouched.ts` has no typia call and
|
|
26
|
+
// carries a type-only import, an `enum`, a `namespace`, `as const`, and a
|
|
27
|
+
// decorated class with an annotated member.
|
|
28
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
29
|
+
// 3. Assert the published text preserves each construct verbatim and emits no
|
|
30
|
+
// `design:type` metadata, so nothing type-driven ran.
|
|
31
|
+
// 4. Assert the file is declared complete with no dependency entry at all.
|
|
32
|
+
func TestProjectDependenciesCompleteUntouchedReprintTransform(t *testing.T) {
|
|
33
|
+
project := projectDependenciesCompleteUntouchedReprintProject(t)
|
|
34
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
35
|
+
return runTransform([]string{
|
|
36
|
+
"--cwd", project,
|
|
37
|
+
"--tsconfig", "tsconfig.json",
|
|
38
|
+
"--output", "ts",
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
if code != 0 {
|
|
42
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
43
|
+
}
|
|
44
|
+
var envelope struct {
|
|
45
|
+
TypeScript map[string]string `json:"typescript"`
|
|
46
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
47
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
48
|
+
}
|
|
49
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
50
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
51
|
+
}
|
|
52
|
+
text, ok := envelope.TypeScript["src/untouched.ts"]
|
|
53
|
+
if !ok {
|
|
54
|
+
t.Fatalf("the envelope must publish src/untouched.ts:\n%s", out)
|
|
55
|
+
}
|
|
56
|
+
for _, preserved := range []string{
|
|
57
|
+
"import type { Shape }",
|
|
58
|
+
"enum Color",
|
|
59
|
+
"namespace Space",
|
|
60
|
+
"as const",
|
|
61
|
+
"@seal",
|
|
62
|
+
} {
|
|
63
|
+
if !strings.Contains(text, preserved) {
|
|
64
|
+
t.Fatalf("the re-print must preserve %q, got:\n%s", preserved, text)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if strings.Contains(text, "design:type") {
|
|
68
|
+
t.Fatalf("the transform envelope must not carry emitDecoratorMetadata output, got:\n%s", text)
|
|
69
|
+
}
|
|
70
|
+
declared := map[string]bool{}
|
|
71
|
+
for _, key := range envelope.DependenciesComplete {
|
|
72
|
+
declared[key] = true
|
|
73
|
+
}
|
|
74
|
+
if !declared["src/untouched.ts"] {
|
|
75
|
+
t.Fatalf("a faithfully re-printed file must be declared complete: %v", envelope.DependenciesComplete)
|
|
76
|
+
}
|
|
77
|
+
if entries, ok := envelope.Dependencies["src/untouched.ts"]; ok {
|
|
78
|
+
t.Fatalf("a file typia never touched must be declared with no dependency entry: %v", entries)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func projectDependenciesCompleteUntouchedReprintProject(t *testing.T) string {
|
|
83
|
+
t.Helper()
|
|
84
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
85
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
86
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
87
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
88
|
+
}
|
|
89
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-complete-untouched-reprint-")
|
|
90
|
+
if err != nil {
|
|
91
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
92
|
+
}
|
|
93
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
94
|
+
src := filepath.Join(dir, "src")
|
|
95
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
96
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
97
|
+
}
|
|
98
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesCompleteUntouchedReprintTSConfig), 0o644); err != nil {
|
|
99
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
100
|
+
}
|
|
101
|
+
for name, body := range map[string]string{
|
|
102
|
+
"untouched.ts": projectDependenciesCompleteUntouchedReprintSourceUntouched,
|
|
103
|
+
"shape.ts": projectDependenciesCompleteUntouchedReprintSourceShape,
|
|
104
|
+
} {
|
|
105
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
106
|
+
t.Fatalf("write %s: %v", name, err)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return dir
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const projectDependenciesCompleteUntouchedReprintTSConfig = `{
|
|
113
|
+
"compilerOptions": {
|
|
114
|
+
"target": "ES2022",
|
|
115
|
+
"module": "commonjs",
|
|
116
|
+
"moduleResolution": "bundler",
|
|
117
|
+
"ignoreDeprecations": "6.0",
|
|
118
|
+
"esModuleInterop": true,
|
|
119
|
+
"experimentalDecorators": true,
|
|
120
|
+
"emitDecoratorMetadata": true,
|
|
121
|
+
"strict": true,
|
|
122
|
+
"skipLibCheck": true,
|
|
123
|
+
"rootDir": "src",
|
|
124
|
+
"outDir": "dist"
|
|
125
|
+
},
|
|
126
|
+
"include": ["src"]
|
|
127
|
+
}
|
|
128
|
+
`
|
|
129
|
+
|
|
130
|
+
const projectDependenciesCompleteUntouchedReprintSourceUntouched = `import type { Shape } from "./shape";
|
|
131
|
+
|
|
132
|
+
export enum Color {
|
|
133
|
+
Red = 1,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export namespace Space {
|
|
137
|
+
export const unit = 1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const palette = ["red", "blue"] as const;
|
|
141
|
+
|
|
142
|
+
export const seal = (target: unknown): void => {
|
|
143
|
+
void target;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
@seal
|
|
147
|
+
export class Holder {
|
|
148
|
+
public shape?: Shape;
|
|
149
|
+
}
|
|
150
|
+
`
|
|
151
|
+
|
|
152
|
+
const projectDependenciesCompleteUntouchedReprintSourceShape = `export interface Shape {
|
|
153
|
+
id: string;
|
|
154
|
+
}
|
|
155
|
+
`
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestProjectDependenciesComputedKeyTransform verifies a computed member key
|
|
11
|
+
// reports the file that decides the name, and withholds the declaration when
|
|
12
|
+
// that name is borrowed from a file the written surface cannot enumerate.
|
|
13
|
+
//
|
|
14
|
+
// `interface Doc { [Kind.A]: number }` emits `input.alpha`, so the enum
|
|
15
|
+
// member's value is read into the validator exactly the way an `enum` member's
|
|
16
|
+
// own value is — and the checker interns the resolved key as a bare string
|
|
17
|
+
// literal with no symbol to follow, so neither the declaration's written type
|
|
18
|
+
// nor the type graph names `kind.ts`. Renaming `Kind.A` changed the generated
|
|
19
|
+
// validator with no reported edge, which is invisible while a file keeps the
|
|
20
|
+
// reference closure and serves a stale validator once it is declared complete
|
|
21
|
+
// (samchon/typia#2126, samchon/typia#2357).
|
|
22
|
+
//
|
|
23
|
+
// 1. Build a project where `a.ts` validates `Doc`, whose only key is
|
|
24
|
+
// `[Kind.A]` reached through a barrel, and where `b.ts` validates `Spare`,
|
|
25
|
+
// whose key is a constant that borrows its value from a third file.
|
|
26
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
27
|
+
// 3. Assert `dependencies["src/a.ts"]` contains `src/kind.ts` and the
|
|
28
|
+
// `src/barrel.ts` the reference traveled, and that `a.ts` is declared
|
|
29
|
+
// complete so the narrowed bound is the one that carries them.
|
|
30
|
+
// 4. Assert it omits `src/unused.ts`, a sibling export the reference never
|
|
31
|
+
// traverses.
|
|
32
|
+
// 5. Assert `b.ts` is withheld: `const BORROWED = VALUE` takes its value from
|
|
33
|
+
// a file no written key names, which is the same answer an enum member
|
|
34
|
+
// borrowing a value gets.
|
|
35
|
+
// 6. Assert `c.ts` stays declared: `["gamma"]` is written where it is used, so
|
|
36
|
+
// treating every computed key as unbounded would cost narrowing for a name
|
|
37
|
+
// no other file can decide.
|
|
38
|
+
// 7. Assert `d.ts` stays declared AND reports `src/keyconst.ts`: a bare name
|
|
39
|
+
// whose constant fixes its own value is the bounded twin of `b.ts`, and it
|
|
40
|
+
// is the only case that pins both halves of the bare-name branch.
|
|
41
|
+
func TestProjectDependenciesComputedKeyTransform(t *testing.T) {
|
|
42
|
+
project := projectDependenciesComputedKeyProject(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
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
55
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
56
|
+
}
|
|
57
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
58
|
+
t.Fatalf("decode envelope: %v\n%s", err, out)
|
|
59
|
+
}
|
|
60
|
+
entries := envelope.Dependencies["src/a.ts"]
|
|
61
|
+
found := map[string]bool{}
|
|
62
|
+
for _, entry := range entries {
|
|
63
|
+
found[entry] = true
|
|
64
|
+
}
|
|
65
|
+
if !found["src/kind.ts"] {
|
|
66
|
+
t.Fatalf("dependencies of src/a.ts must contain src/kind.ts, whose enum member decides the emitted key: %v", entries)
|
|
67
|
+
}
|
|
68
|
+
if !found["src/barrel.ts"] {
|
|
69
|
+
t.Fatalf("dependencies of src/a.ts must contain src/barrel.ts, which selects the enum the key reads: %v", entries)
|
|
70
|
+
}
|
|
71
|
+
if found["src/unused.ts"] {
|
|
72
|
+
t.Fatalf("dependencies of src/a.ts must not contain src/unused.ts, a sibling the reference never traverses: %v", entries)
|
|
73
|
+
}
|
|
74
|
+
declared := map[string]bool{}
|
|
75
|
+
for _, key := range envelope.DependenciesComplete {
|
|
76
|
+
declared[key] = true
|
|
77
|
+
}
|
|
78
|
+
if !declared["src/a.ts"] {
|
|
79
|
+
t.Fatalf("src/a.ts must be declared complete, so the reported entry is the whole bound: %v", envelope.DependenciesComplete)
|
|
80
|
+
}
|
|
81
|
+
if declared["src/b.ts"] {
|
|
82
|
+
t.Fatalf("a key whose constant borrows its value from another file is not bounded by any written name, so src/b.ts must be withheld: %v", envelope.DependenciesComplete)
|
|
83
|
+
}
|
|
84
|
+
if !declared["src/c.ts"] {
|
|
85
|
+
t.Fatalf("a computed key written as a literal names no other file and must stay declared: %v", envelope.DependenciesComplete)
|
|
86
|
+
}
|
|
87
|
+
if !declared["src/d.ts"] {
|
|
88
|
+
t.Fatalf("a key whose constant fixes its own value is bounded and must stay declared: %v", envelope.DependenciesComplete)
|
|
89
|
+
}
|
|
90
|
+
bare := map[string]bool{}
|
|
91
|
+
for _, entry := range envelope.Dependencies["src/d.ts"] {
|
|
92
|
+
bare[entry] = true
|
|
93
|
+
}
|
|
94
|
+
if !bare["src/keyconst.ts"] {
|
|
95
|
+
t.Fatalf("dependencies of src/d.ts must contain src/keyconst.ts, whose constant decides the emitted key: %v", envelope.Dependencies["src/d.ts"])
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func projectDependenciesComputedKeyProject(t *testing.T) string {
|
|
100
|
+
t.Helper()
|
|
101
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
102
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
103
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
104
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
105
|
+
}
|
|
106
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-computed-key-")
|
|
107
|
+
if err != nil {
|
|
108
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
109
|
+
}
|
|
110
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
111
|
+
src := filepath.Join(dir, "src")
|
|
112
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
113
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
114
|
+
}
|
|
115
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
116
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
117
|
+
}
|
|
118
|
+
for name, body := range map[string]string{
|
|
119
|
+
"a.ts": projectDependenciesComputedKeySourceA,
|
|
120
|
+
"b.ts": projectDependenciesComputedKeySourceB,
|
|
121
|
+
"c.ts": projectDependenciesComputedKeySourceC,
|
|
122
|
+
"d.ts": projectDependenciesComputedKeySourceD,
|
|
123
|
+
"doc.ts": projectDependenciesComputedKeySourceDoc,
|
|
124
|
+
"spare.ts": projectDependenciesComputedKeySourceSpare,
|
|
125
|
+
"plain.ts": projectDependenciesComputedKeySourcePlain,
|
|
126
|
+
"bare.ts": projectDependenciesComputedKeySourceBare,
|
|
127
|
+
"keyconst.ts": projectDependenciesComputedKeySourceKeyConst,
|
|
128
|
+
"barrel.ts": projectDependenciesComputedKeySourceBarrel,
|
|
129
|
+
"kind.ts": projectDependenciesComputedKeySourceKind,
|
|
130
|
+
"unused.ts": projectDependenciesComputedKeySourceUnused,
|
|
131
|
+
"borrow.ts": projectDependenciesComputedKeySourceBorrow,
|
|
132
|
+
"value.ts": projectDependenciesComputedKeySourceValue,
|
|
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 projectDependenciesComputedKeySourceA = `import typia from "typia";
|
|
142
|
+
|
|
143
|
+
import { Doc } from "./doc";
|
|
144
|
+
|
|
145
|
+
export const check = (input: unknown) => typia.is<Doc>(input);
|
|
146
|
+
`
|
|
147
|
+
|
|
148
|
+
const projectDependenciesComputedKeySourceB = `import typia from "typia";
|
|
149
|
+
|
|
150
|
+
import { Spare } from "./spare";
|
|
151
|
+
|
|
152
|
+
export const check = (input: unknown) => typia.is<Spare>(input);
|
|
153
|
+
`
|
|
154
|
+
|
|
155
|
+
const projectDependenciesComputedKeySourceC = `import typia from "typia";
|
|
156
|
+
|
|
157
|
+
import { Plain } from "./plain";
|
|
158
|
+
|
|
159
|
+
export const check = (input: unknown) => typia.is<Plain>(input);
|
|
160
|
+
`
|
|
161
|
+
|
|
162
|
+
const projectDependenciesComputedKeySourcePlain = `export interface Plain {
|
|
163
|
+
["gamma"]: number;
|
|
164
|
+
}
|
|
165
|
+
`
|
|
166
|
+
|
|
167
|
+
const projectDependenciesComputedKeySourceD = `import typia from "typia";
|
|
168
|
+
|
|
169
|
+
import { Bare } from "./bare";
|
|
170
|
+
|
|
171
|
+
export const check = (input: unknown) => typia.is<Bare>(input);
|
|
172
|
+
`
|
|
173
|
+
|
|
174
|
+
const projectDependenciesComputedKeySourceBare = `import { KEY } from "./keyconst";
|
|
175
|
+
|
|
176
|
+
export interface Bare {
|
|
177
|
+
[KEY]: number;
|
|
178
|
+
}
|
|
179
|
+
`
|
|
180
|
+
|
|
181
|
+
const projectDependenciesComputedKeySourceKeyConst = `export const KEY = "epsilon";
|
|
182
|
+
`
|
|
183
|
+
|
|
184
|
+
const projectDependenciesComputedKeySourceDoc = `import { Kind } from "./barrel";
|
|
185
|
+
|
|
186
|
+
export interface Doc {
|
|
187
|
+
[Kind.A]: number;
|
|
188
|
+
}
|
|
189
|
+
`
|
|
190
|
+
|
|
191
|
+
const projectDependenciesComputedKeySourceSpare = `import { BORROWED } from "./borrow";
|
|
192
|
+
|
|
193
|
+
export interface Spare {
|
|
194
|
+
[BORROWED]: number;
|
|
195
|
+
}
|
|
196
|
+
`
|
|
197
|
+
|
|
198
|
+
const projectDependenciesComputedKeySourceBarrel = `export { Kind } from "./kind";
|
|
199
|
+
export { Unused } from "./unused";
|
|
200
|
+
`
|
|
201
|
+
|
|
202
|
+
const projectDependenciesComputedKeySourceKind = `export enum Kind {
|
|
203
|
+
A = "alpha",
|
|
204
|
+
}
|
|
205
|
+
`
|
|
206
|
+
|
|
207
|
+
const projectDependenciesComputedKeySourceUnused = `export enum Unused {
|
|
208
|
+
B = "beta",
|
|
209
|
+
}
|
|
210
|
+
`
|
|
211
|
+
|
|
212
|
+
const projectDependenciesComputedKeySourceBorrow = `import { VALUE } from "./value";
|
|
213
|
+
|
|
214
|
+
export const BORROWED = VALUE;
|
|
215
|
+
`
|
|
216
|
+
|
|
217
|
+
const projectDependenciesComputedKeySourceValue = `export const VALUE = "delta";
|
|
218
|
+
`
|
|
@@ -25,8 +25,12 @@ import (
|
|
|
25
25
|
// analysis touches real default libraries.
|
|
26
26
|
// 2. Run project transform mode and decode the JSON envelope.
|
|
27
27
|
// 3. Assert `dependencies["src/a.ts"]` contains `src/lib.custom.d.ts`.
|
|
28
|
-
// 4. Assert no compiler-owned default library
|
|
29
|
-
//
|
|
28
|
+
// 4. Assert no compiler-owned default library leaks into the entry. Under this
|
|
29
|
+
// fixture they are the program's virtual `bundled:///` sources, so the URI
|
|
30
|
+
// shape is what proves the classification still excludes them. Being outside
|
|
31
|
+
// the project is not evidence of one: the envelope reports a `node_modules`
|
|
32
|
+
// declaration file like any other, and the files that decide a callee's
|
|
33
|
+
// typia identity are exactly such files.
|
|
30
34
|
func TestProjectDependenciesCustomLibNameTransform(t *testing.T) {
|
|
31
35
|
project := projectDependenciesCustomLibNameProject(t)
|
|
32
36
|
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
@@ -56,9 +60,6 @@ func TestProjectDependenciesCustomLibNameTransform(t *testing.T) {
|
|
|
56
60
|
if strings.Contains(entry, "://") {
|
|
57
61
|
t.Fatalf("dependencies must exclude virtual URI sources, got %q in %v", entry, entries)
|
|
58
62
|
}
|
|
59
|
-
if filepath.IsAbs(filepath.FromSlash(entry)) || strings.HasPrefix(entry, "../") {
|
|
60
|
-
t.Fatalf("dependencies must not report files outside the project, got %q in %v", entry, entries)
|
|
61
|
-
}
|
|
62
63
|
}
|
|
63
64
|
if !found["src/lib.custom.d.ts"] {
|
|
64
65
|
t.Fatalf("dependencies of src/a.ts must contain the project-owned src/lib.custom.d.ts: %v", entries)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesEnumMemberValueTransform verifies an enum whose member
|
|
12
|
+
// borrows its value from another file withholds the completeness declaration,
|
|
13
|
+
// while an enum that writes its own values keeps it.
|
|
14
|
+
//
|
|
15
|
+
// An enum member's value is read straight into the generated validator, so
|
|
16
|
+
// `Kind.A === input` becomes `"x" === input` and changes the moment the value
|
|
17
|
+
// changes. When the member writes a literal — including the negative form, which
|
|
18
|
+
// TypeScript reads as one constant — that value is written here and no other
|
|
19
|
+
// file can decide it. When it borrows one (`A = Other.X`), the value lives
|
|
20
|
+
// somewhere the type graph never names: the checker hands the analysis the
|
|
21
|
+
// resolved literal, not the reference that produced it (samchon/typia#2357).
|
|
22
|
+
//
|
|
23
|
+
// 1. Build a project with `plain.ts`, whose enum writes a string and a negative
|
|
24
|
+
// number, and `kind.ts`, whose enum takes a member value from `other.ts`.
|
|
25
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
26
|
+
// 3. Assert both callers transformed into a literal comparison, so both enums
|
|
27
|
+
// really did reach the validator.
|
|
28
|
+
// 4. Assert the caller of the self-contained enum is declared complete and the
|
|
29
|
+
// caller of the borrowing enum is not.
|
|
30
|
+
func TestProjectDependenciesEnumMemberValueTransform(t *testing.T) {
|
|
31
|
+
project := projectDependenciesEnumMemberValueProject(t)
|
|
32
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
33
|
+
return runTransform([]string{
|
|
34
|
+
"--cwd", project,
|
|
35
|
+
"--tsconfig", "tsconfig.json",
|
|
36
|
+
"--output", "ts",
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
if code != 0 {
|
|
40
|
+
t.Fatalf("project transform failed: code=%d stderr=\n%s", code, errText)
|
|
41
|
+
}
|
|
42
|
+
var envelope struct {
|
|
43
|
+
TypeScript map[string]string `json:"typescript"`
|
|
44
|
+
Dependencies map[string][]string `json:"dependencies"`
|
|
45
|
+
DependenciesComplete []string `json:"dependenciesComplete"`
|
|
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/own.ts"]; !strings.Contains(text, `"a" === input`) || !strings.Contains(text, "-1 === input") {
|
|
51
|
+
t.Fatalf("src/own.ts must compare against both written member values, got:\n%s", text)
|
|
52
|
+
}
|
|
53
|
+
if text := envelope.TypeScript["src/borrowed.ts"]; !strings.Contains(text, `"x" === input`) {
|
|
54
|
+
t.Fatalf("src/borrowed.ts must compare against the borrowed member value, got:\n%s", text)
|
|
55
|
+
}
|
|
56
|
+
declared := map[string]bool{}
|
|
57
|
+
for _, key := range envelope.DependenciesComplete {
|
|
58
|
+
declared[key] = true
|
|
59
|
+
}
|
|
60
|
+
if !declared["src/own.ts"] {
|
|
61
|
+
t.Fatalf("an enum that writes its own member values bounds them and must be declared complete: %v", envelope.DependenciesComplete)
|
|
62
|
+
}
|
|
63
|
+
if declared["src/borrowed.ts"] {
|
|
64
|
+
t.Fatalf("an enum member that borrows its value must withhold the declaration: %v", envelope.DependenciesComplete)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func projectDependenciesEnumMemberValueProject(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-enum-member-value-")
|
|
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
|
+
"own.ts": projectDependenciesEnumMemberValueSourceOwn,
|
|
89
|
+
"borrowed.ts": projectDependenciesEnumMemberValueSourceBorrowed,
|
|
90
|
+
"plain.ts": projectDependenciesEnumMemberValueSourcePlain,
|
|
91
|
+
"kind.ts": projectDependenciesEnumMemberValueSourceKind,
|
|
92
|
+
"other.ts": projectDependenciesEnumMemberValueSourceOther,
|
|
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 projectDependenciesEnumMemberValueSourceOwn = `import typia from "typia";
|
|
102
|
+
|
|
103
|
+
import { Plain } from "./plain";
|
|
104
|
+
|
|
105
|
+
export const check = (input: unknown) => typia.is<Plain>(input);
|
|
106
|
+
`
|
|
107
|
+
|
|
108
|
+
const projectDependenciesEnumMemberValueSourceBorrowed = `import typia from "typia";
|
|
109
|
+
|
|
110
|
+
import { Kind } from "./kind";
|
|
111
|
+
|
|
112
|
+
export const check = (input: unknown) => typia.is<Kind>(input);
|
|
113
|
+
`
|
|
114
|
+
|
|
115
|
+
const projectDependenciesEnumMemberValueSourcePlain = `export enum Plain {
|
|
116
|
+
Text = "a",
|
|
117
|
+
Negative = -1,
|
|
118
|
+
}
|
|
119
|
+
`
|
|
120
|
+
|
|
121
|
+
const projectDependenciesEnumMemberValueSourceKind = `import { Other } from "./other";
|
|
122
|
+
|
|
123
|
+
export enum Kind {
|
|
124
|
+
Borrowed = Other.Value,
|
|
125
|
+
}
|
|
126
|
+
`
|
|
127
|
+
|
|
128
|
+
const projectDependenciesEnumMemberValueSourceOther = `export enum Other {
|
|
129
|
+
Value = "x",
|
|
130
|
+
}
|
|
131
|
+
`
|