typia 13.3.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/internal/_randomPattern.js +20 -3
- package/lib/internal/_randomPattern.js.map +1 -1
- package/lib/internal/_randomPattern.mjs +5 -1
- package/lib/internal/_randomPattern.mjs.map +1 -1
- package/lib/internal/_randomStringLength.js +9 -2
- package/lib/internal/_randomStringLength.js.map +1 -1
- package/lib/internal/_randomStringLength.mjs +3 -1
- package/lib/internal/_randomStringLength.mjs.map +1 -1
- package/lib/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/transform.mjs +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/dynamic_key_tags_transform_test.go +206 -0
- package/native/cmd/ttsc-typia/notation_kebab_case_transform_test.go +2 -2
- package/native/cmd/ttsc-typia/project_catalog_transform_coverage_test.go +84 -85
- package/native/cmd/ttsc-typia/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/template_literal_type_tags_transform_test.go +3 -1
- package/native/cmd/ttsc-typia/transform.go +195 -21
- package/native/cmd/ttsc-typia/transform_helpers_test.go +12 -0
- package/native/core/factories/MetadataCommentTagFactory.go +96 -27
- package/native/core/factories/metadata_comment_tag_factory_coverage_test.go +2 -2
- package/native/core/factories/metadata_comment_tag_factory_rejects_out_of_range_integer_test.go +124 -0
- package/native/core/programmers/AssertProgrammer.go +20 -6
- package/native/core/programmers/RandomProgrammer.go +78 -6
- package/native/core/programmers/ValidateProgrammer.go +21 -6
- package/native/core/programmers/helpers/RandomJoiner.go +8 -1
- package/native/core/programmers/iterate/check_dynamic_properties.go +177 -3
- package/native/core/programmers/iterate/check_object.go +12 -1
- package/native/core/schemas/metadata/MetadataDependency.go +613 -30
- package/native/transform/CallExpressionTransformer.go +16 -1
- package/package.json +4 -14
- package/src/internal/_createStandardSchema.ts +1 -2
- package/src/internal/_randomPattern.ts +18 -3
- package/src/internal/_randomStringLength.ts +9 -2
- package/src/module.ts +6 -2
- package/src/re-exports.ts +13 -0
- package/lib/executable/FileSystemIdentity.d.ts +0 -44
- package/lib/executable/FileSystemIdentity.js +0 -213
- package/lib/executable/FileSystemIdentity.js.map +0 -1
- package/lib/executable/FileSystemIdentity.mjs +0 -133
- package/lib/executable/FileSystemIdentity.mjs.map +0 -1
- package/lib/executable/TypiaGenerateWizard.d.ts +0 -9
- package/lib/executable/TypiaGenerateWizard.js +0 -852
- package/lib/executable/TypiaGenerateWizard.js.map +0 -1
- package/lib/executable/TypiaGenerateWizard.mjs +0 -618
- package/lib/executable/TypiaGenerateWizard.mjs.map +0 -1
- package/lib/executable/generate/ttsc.d.ts +0 -1
- package/lib/executable/generate/ttsc.js +0 -53
- package/lib/executable/generate/ttsc.js.map +0 -1
- package/lib/executable/generate/ttsc.mjs +0 -37
- package/lib/executable/generate/ttsc.mjs.map +0 -1
- package/lib/executable/typia.d.ts +0 -2
- package/lib/executable/typia.js +0 -84
- package/lib/executable/typia.js.map +0 -1
- package/lib/executable/typia.mjs +0 -60
- package/lib/executable/typia.mjs.map +0 -1
- package/src/executable/FileSystemIdentity.ts +0 -222
- package/src/executable/TypiaGenerateWizard.ts +0 -1133
- package/src/executable/generate/ttsc.ts +0 -70
- package/src/executable/typia.ts +0 -80
|
@@ -0,0 +1,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
|
+
`
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sort"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesCompleteEnvelopeTransform verifies the project
|
|
12
|
+
// transform envelope declares every file it published as having a complete
|
|
13
|
+
// dependency list.
|
|
14
|
+
//
|
|
15
|
+
// `dependencies` alone only widens the consumer's bound; `dependenciesComplete`
|
|
16
|
+
// is what lets the consumer stop widening and validate the ~10 declarations
|
|
17
|
+
// typia consulted instead of the file's whole reference closure plus every
|
|
18
|
+
// global-scope declaration, once per delivered module. The claim covers both
|
|
19
|
+
// classes of envelope file, and the second one is the larger half: a file with
|
|
20
|
+
// no typia call receives no contribution at all, so its empty entry is the
|
|
21
|
+
// strongest claim the envelope carries (samchon/typia#2357).
|
|
22
|
+
//
|
|
23
|
+
// 1. Build a project where `a.ts` validates `Bee` from `b.ts` (which reaches
|
|
24
|
+
// `Cee` in `c.ts`), plus `d.ts` with no typia call at all.
|
|
25
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
26
|
+
// 3. Assert `dependenciesComplete` names exactly the published `typescript`
|
|
27
|
+
// keys, so neither the transformed file nor the untouched ones are left on
|
|
28
|
+
// the conservative bound.
|
|
29
|
+
// 4. Assert the declared list for `src/a.ts` still carries `src/b.ts` and
|
|
30
|
+
// `src/c.ts`, because the declaration narrows to that entry and an entry
|
|
31
|
+
// that lost a consulted file would now serve a stale validator.
|
|
32
|
+
func TestProjectDependenciesCompleteEnvelopeTransform(t *testing.T) {
|
|
33
|
+
project := projectDependenciesCompleteEnvelopeProject(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
|
+
|
|
53
|
+
published := make([]string, 0, len(envelope.TypeScript))
|
|
54
|
+
for key := range envelope.TypeScript {
|
|
55
|
+
published = append(published, key)
|
|
56
|
+
}
|
|
57
|
+
sort.Strings(published)
|
|
58
|
+
declared := append([]string{}, envelope.DependenciesComplete...)
|
|
59
|
+
sort.Strings(declared)
|
|
60
|
+
if len(published) != len(declared) {
|
|
61
|
+
t.Fatalf("dependenciesComplete must name every published file; published=%v declared=%v", published, declared)
|
|
62
|
+
}
|
|
63
|
+
for index, key := range published {
|
|
64
|
+
if declared[index] != key {
|
|
65
|
+
t.Fatalf("dependenciesComplete must name every published file; published=%v declared=%v", published, declared)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
entries := envelope.Dependencies["src/a.ts"]
|
|
70
|
+
found := map[string]bool{}
|
|
71
|
+
for _, entry := range entries {
|
|
72
|
+
found[entry] = true
|
|
73
|
+
}
|
|
74
|
+
if !found["src/b.ts"] {
|
|
75
|
+
t.Fatalf("the declared entry for src/a.ts must keep the direct declaration file src/b.ts: %v", entries)
|
|
76
|
+
}
|
|
77
|
+
if !found["src/c.ts"] {
|
|
78
|
+
t.Fatalf("the declared entry for src/a.ts must keep the transitive declaration file src/c.ts: %v", entries)
|
|
79
|
+
}
|
|
80
|
+
if _, ok := envelope.Dependencies["src/d.ts"]; ok {
|
|
81
|
+
t.Fatalf("src/d.ts has no typia call and must be declared with no dependency at all: %v", envelope.Dependencies["src/d.ts"])
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func projectDependenciesCompleteEnvelopeProject(t *testing.T) string {
|
|
86
|
+
t.Helper()
|
|
87
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
88
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
89
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
90
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
91
|
+
}
|
|
92
|
+
dir, err := os.MkdirTemp(base, "project-dependencies-complete-envelope-")
|
|
93
|
+
if err != nil {
|
|
94
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
95
|
+
}
|
|
96
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
97
|
+
src := filepath.Join(dir, "src")
|
|
98
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
99
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
100
|
+
}
|
|
101
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(projectDependenciesEnvelopeTSConfig), 0o644); err != nil {
|
|
102
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
103
|
+
}
|
|
104
|
+
for name, body := range map[string]string{
|
|
105
|
+
"a.ts": projectDependenciesEnvelopeSourceA,
|
|
106
|
+
"b.ts": projectDependenciesEnvelopeSourceB,
|
|
107
|
+
"c.ts": projectDependenciesEnvelopeSourceC,
|
|
108
|
+
"d.ts": projectDependenciesEnvelopeSourceD,
|
|
109
|
+
} {
|
|
110
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
111
|
+
t.Fatalf("write %s: %v", name, err)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return dir
|
|
115
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestProjectDependenciesCompleteInferredTypeTransform verifies a file whose
|
|
12
|
+
// typia call takes its validated type from the value argument is withheld from
|
|
13
|
+
// the completeness declaration, while its written-type-argument twin is not.
|
|
14
|
+
//
|
|
15
|
+
// A written type argument bounds the analysis: every declaration reachable from
|
|
16
|
+
// it is touched and reported, so the reported list is the whole input set. An
|
|
17
|
+
// inferred one does not, because contextual typing can put the deciding
|
|
18
|
+
// annotation in a file the resolved type never names -- `const handler:
|
|
19
|
+
// Handler = (input) => typia.assert(input)` validates whatever `Handler`
|
|
20
|
+
// declares, and `Handler`'s file is nowhere in the consulted-declaration set.
|
|
21
|
+
// Declaring such a file complete would drop the reference closure that is the
|
|
22
|
+
// only thing still watching it (samchon/typia#2357).
|
|
23
|
+
//
|
|
24
|
+
// 1. Build a project where `written.ts` calls `typia.assert<Shape>(input)` and
|
|
25
|
+
// `inferred.ts` calls `typia.assert(input)` on a parameter contextually
|
|
26
|
+
// typed through `handler.ts`.
|
|
27
|
+
// 2. Run project transform mode and decode the JSON envelope.
|
|
28
|
+
// 3. Assert both files transformed, so the two differ only in where the type
|
|
29
|
+
// came from.
|
|
30
|
+
// 4. Assert `written.ts` is declared complete and `inferred.ts` is not, and
|
|
31
|
+
// that `inferred.ts` still keeps its reported dependency entry -- it falls
|
|
32
|
+
// back to the host-owned bound, it is not stripped of what it did report.
|
|
33
|
+
func TestProjectDependenciesCompleteInferredTypeTransform(t *testing.T) {
|
|
34
|
+
project := projectDependenciesCompleteInferredTypeProject(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
|
+
for _, key := range []string{"src/written.ts", "src/inferred.ts"} {
|
|
54
|
+
if text := envelope.TypeScript[key]; !strings.Contains(text, "typeof input") {
|
|
55
|
+
t.Fatalf("%s must have been transformed into a generated validator, got:\n%s", key, text)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
declared := map[string]bool{}
|
|
59
|
+
for _, key := range envelope.DependenciesComplete {
|
|
60
|
+
declared[key] = true
|
|
61
|
+
}
|
|
62
|
+
if !declared["src/written.ts"] {
|
|
63
|
+
t.Fatalf("a call with a written type argument bounds its inputs and must be declared complete: %v", envelope.DependenciesComplete)
|
|
64
|
+
}
|
|
65
|
+
if declared["src/inferred.ts"] {
|
|
66
|
+
t.Fatalf("a call that infers its type from the value argument must be withheld from the completeness declaration: %v", envelope.DependenciesComplete)
|
|
67
|
+
}
|
|
68
|
+
if len(envelope.Dependencies["src/inferred.ts"]) == 0 {
|
|
69
|
+
t.Fatalf("withholding the declaration must not strip what src/inferred.ts did report: %v", envelope.Dependencies)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func projectDependenciesCompleteInferredTypeProject(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-complete-inferred-")
|
|
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
|
+
"shape.ts": projectDependenciesCompleteInferredSourceShape,
|
|
94
|
+
"handler.ts": projectDependenciesCompleteInferredSourceHandler,
|
|
95
|
+
"written.ts": projectDependenciesCompleteInferredSourceWritten,
|
|
96
|
+
"inferred.ts": projectDependenciesCompleteInferredSourceInferred,
|
|
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 projectDependenciesCompleteInferredSourceShape = `export interface Shape {
|
|
106
|
+
id: string;
|
|
107
|
+
}
|
|
108
|
+
`
|
|
109
|
+
|
|
110
|
+
const projectDependenciesCompleteInferredSourceHandler = `import { Shape } from "./shape";
|
|
111
|
+
|
|
112
|
+
export type Handler = (input: Shape) => unknown;
|
|
113
|
+
`
|
|
114
|
+
|
|
115
|
+
const projectDependenciesCompleteInferredSourceWritten = `import typia from "typia";
|
|
116
|
+
|
|
117
|
+
import { Shape } from "./shape";
|
|
118
|
+
|
|
119
|
+
export const validateShape = (input: Shape) => typia.assert<Shape>(input);
|
|
120
|
+
`
|
|
121
|
+
|
|
122
|
+
const projectDependenciesCompleteInferredSourceInferred = `import typia from "typia";
|
|
123
|
+
|
|
124
|
+
import { Handler } from "./handler";
|
|
125
|
+
|
|
126
|
+
export const handle: Handler = (input) => typia.assert(input);
|
|
127
|
+
`
|
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
|
+
`
|