typia 13.1.19 → 13.2.0
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/executable/FileSystemIdentity.d.ts +23 -0
- package/lib/executable/FileSystemIdentity.js +30 -0
- package/lib/executable/FileSystemIdentity.js.map +1 -1
- package/lib/executable/FileSystemIdentity.mjs +4 -0
- package/lib/executable/FileSystemIdentity.mjs.map +1 -1
- package/lib/executable/TypiaGenerateWizard.js +15 -9
- package/lib/executable/TypiaGenerateWizard.js.map +1 -1
- package/lib/executable/TypiaGenerateWizard.mjs +11 -7
- package/lib/executable/TypiaGenerateWizard.mjs.map +1 -1
- package/lib/internal/_ProtobufReader.d.ts +18 -3
- package/lib/internal/_ProtobufReader.js +39 -11
- package/lib/internal/_ProtobufReader.js.map +1 -1
- package/lib/internal/_ProtobufReader.mjs +37 -10
- package/lib/internal/_ProtobufReader.mjs.map +1 -1
- package/lib/internal/_jsonStringifyArray.d.ts +1 -0
- package/lib/internal/_jsonStringifyArray.js +48 -0
- package/lib/internal/_jsonStringifyArray.js.map +1 -0
- package/lib/internal/_jsonStringifyArray.mjs +40 -0
- package/lib/internal/_jsonStringifyArray.mjs.map +1 -0
- package/lib/internal/_jsonStringifyElement.d.ts +1 -0
- package/lib/internal/_jsonStringifyElement.js +23 -0
- package/lib/internal/_jsonStringifyElement.js.map +1 -0
- package/lib/internal/_jsonStringifyElement.mjs +23 -0
- package/lib/internal/_jsonStringifyElement.mjs.map +1 -0
- package/lib/internal/_jsonStringifyProperty.d.ts +1 -0
- package/lib/internal/_jsonStringifyProperty.js +31 -0
- package/lib/internal/_jsonStringifyProperty.js.map +1 -0
- package/lib/internal/_jsonStringifyProperty.mjs +31 -0
- package/lib/internal/_jsonStringifyProperty.mjs.map +1 -0
- package/lib/internal/_llmApplicationFinalize.js +1 -1
- package/lib/internal/_llmApplicationFinalize.js.map +1 -1
- package/lib/internal/_llmApplicationFinalize.mjs +1 -1
- package/lib/internal/_llmApplicationFinalize.mjs.map +1 -1
- package/lib/internal/_randomFormatByte.js +24 -1
- package/lib/internal/_randomFormatByte.js.map +1 -1
- package/lib/internal/_randomFormatByte.mjs +20 -2
- package/lib/internal/_randomFormatByte.mjs.map +1 -1
- package/lib/internal/_randomFormatDate.d.ts +3 -6
- package/lib/internal/_randomFormatDate.js +2 -10
- package/lib/internal/_randomFormatDate.js.map +1 -1
- package/lib/internal/_randomFormatDate.mjs +2 -6
- package/lib/internal/_randomFormatDate.mjs.map +1 -1
- package/lib/internal/_randomFormatDatetime.d.ts +3 -6
- package/lib/internal/_randomFormatDatetime.js +25 -8
- package/lib/internal/_randomFormatDatetime.js.map +1 -1
- package/lib/internal/_randomFormatDatetime.mjs +17 -7
- package/lib/internal/_randomFormatDatetime.mjs.map +1 -1
- package/lib/internal/_randomFormatDuration.js +23 -15
- package/lib/internal/_randomFormatDuration.js.map +1 -1
- package/lib/internal/_randomFormatDuration.mjs +23 -15
- package/lib/internal/_randomFormatDuration.mjs.map +1 -1
- package/lib/internal/_randomFormatIpv4.js +31 -1
- package/lib/internal/_randomFormatIpv4.js.map +1 -1
- package/lib/internal/_randomFormatIpv4.mjs +23 -2
- package/lib/internal/_randomFormatIpv4.mjs.map +1 -1
- package/lib/internal/_randomFormatIpv6.js +57 -1
- package/lib/internal/_randomFormatIpv6.js.map +1 -1
- package/lib/internal/_randomFormatIpv6.mjs +45 -2
- package/lib/internal/_randomFormatIpv6.mjs.map +1 -1
- package/lib/internal/_randomFormatJsonPointer.js +14 -2
- package/lib/internal/_randomFormatJsonPointer.js.map +1 -1
- package/lib/internal/_randomFormatJsonPointer.mjs +13 -3
- package/lib/internal/_randomFormatJsonPointer.mjs.map +1 -1
- package/lib/internal/_randomFormatRegex.js +14 -1
- package/lib/internal/_randomFormatRegex.js.map +1 -1
- package/lib/internal/_randomFormatRegex.mjs +14 -2
- package/lib/internal/_randomFormatRegex.mjs.map +1 -1
- package/lib/internal/_randomFormatRelativeJsonPointer.js +19 -5
- package/lib/internal/_randomFormatRelativeJsonPointer.js.map +1 -1
- package/lib/internal/_randomFormatRelativeJsonPointer.mjs +21 -6
- package/lib/internal/_randomFormatRelativeJsonPointer.mjs.map +1 -1
- package/lib/internal/_randomFormatTime.js +33 -7
- package/lib/internal/_randomFormatTime.js.map +1 -1
- package/lib/internal/_randomFormatTime.mjs +37 -5
- package/lib/internal/_randomFormatTime.mjs.map +1 -1
- package/lib/internal/_randomPattern.js.map +1 -1
- package/lib/internal/_randomPattern.mjs.map +1 -1
- package/lib/internal/_randomStringLength.d.ts +41 -10
- package/lib/internal/_randomStringLength.js +49 -13
- package/lib/internal/_randomStringLength.js.map +1 -1
- package/lib/internal/_randomStringLength.mjs +48 -13
- package/lib/internal/_randomStringLength.mjs.map +1 -1
- package/lib/internal/private/__notationRename.js.map +1 -1
- package/lib/internal/private/__notationRename.mjs.map +1 -1
- package/lib/internal/private/__randomComposition.d.ts +23 -0
- package/lib/internal/private/__randomComposition.js +48 -0
- package/lib/internal/private/__randomComposition.js.map +1 -0
- package/lib/internal/private/__randomComposition.mjs +54 -0
- package/lib/internal/private/__randomComposition.mjs.map +1 -0
- package/lib/internal/private/__randomEpoch.d.ts +19 -0
- package/lib/internal/private/__randomEpoch.js +31 -0
- package/lib/internal/private/__randomEpoch.js.map +1 -0
- package/lib/internal/private/__randomEpoch.mjs +28 -0
- package/lib/internal/private/__randomEpoch.mjs.map +1 -0
- package/native/cmd/ttsc-typia/build.go +3 -0
- package/native/cmd/ttsc-typia/callable_interface_function_semantics_transform_test.go +331 -0
- package/native/cmd/ttsc-typia/callable_type_literal_boundaries_transform_test.go +1184 -0
- package/native/cmd/ttsc-typia/compare_functional_union_membership_transform_test.go +434 -0
- package/native/cmd/ttsc-typia/compare_template_literal_union_transform_test.go +241 -0
- package/native/cmd/ttsc-typia/default_library_spoof_native_identity_transform_test.go +216 -0
- package/native/cmd/ttsc-typia/json_schema_dynamic_key_determinism_transform_test.go +246 -0
- package/native/cmd/ttsc-typia/json_stringify_contextual_undefined_transform_test.go +556 -0
- package/native/cmd/ttsc-typia/lib_replacement_native_identity_transform_test.go +277 -0
- package/native/cmd/ttsc-typia/native_named_intersection_union_transform_test.go +390 -0
- package/native/cmd/ttsc-typia/node_buffer_native_exports_is_transform_test.go +526 -0
- package/native/cmd/ttsc-typia/package_declaration_native_identity_is_transform_test.go +293 -0
- package/native/cmd/ttsc-typia/protobuf_diagnostic_code_transform_test.go +167 -0
- package/native/cmd/ttsc-typia/symbol_index_domain_transform_test.go +316 -0
- package/native/cmd/ttsc-typia/transform.go +23 -0
- package/native/cmd/ttsc-typia/transform_helpers_test.go +77 -2
- package/native/cmd/ttsc-typia/user_global_native_identity_transform_test.go +484 -0
- package/native/core/factories/ProtobufFactory.go +6 -1
- package/native/core/factories/internal/metadata/MetadataHelper.go +185 -30
- package/native/core/factories/internal/metadata/emplace_metadata_object.go +25 -0
- package/native/core/factories/internal/metadata/iterate_metadata_function.go +1 -1
- package/native/core/factories/internal/metadata/iterate_metadata_intersection.go +103 -23
- package/native/core/factories/internal/metadata/iterate_metadata_map.go +3 -7
- package/native/core/factories/internal/metadata/iterate_metadata_native.go +32 -14
- package/native/core/factories/internal/metadata/iterate_metadata_set.go +3 -7
- package/native/core/programmers/compare/CompareEqualProgrammer.go +27 -13
- package/native/core/programmers/helpers/FunctionProgrammer.go +6 -13
- package/native/core/programmers/helpers/StringifyJoinder.go +129 -50
- package/native/core/programmers/helpers/joiner_branch_coverage_test.go +116 -120
- package/native/core/programmers/iterate/iterate_check_coverage_test.go +76 -158
- package/native/core/programmers/iterate/iterate_utility_coverage_test.go +231 -270
- package/native/core/programmers/iterate/json_schema_object.go +49 -7
- package/native/core/programmers/iterate/json_schema_pattern_property_order_test.go +44 -0
- package/native/core/programmers/iterate/openapi_v3_downgrader.go +34 -1
- package/native/core/programmers/iterate/template_runtime_pattern.go +11 -0
- package/native/core/programmers/json/JsonStringifyProgrammer.go +47 -6
- package/native/core/programmers/llm/LlmApplicationProgrammer.go +9 -1
- package/native/core/programmers/protobuf/ProtobufMessageProgrammer.go +1 -1
- package/native/core/schemas/metadata/MetadataCollection.go +0 -9
- package/native/core/schemas/metadata/MetadataDefaultLibrary.go +81 -0
- package/native/core/schemas/metadata/MetadataObjectType.go +7 -18
- package/native/go.mod +4 -4
- package/native/transform/features/protobuf/ProtobufMessageTransformer.go +1 -1
- package/package.json +3 -7
- package/src/executable/FileSystemIdentity.ts +33 -0
- package/src/executable/TypiaGenerateWizard.ts +18 -12
- package/src/internal/_ProtobufReader.ts +40 -11
- package/src/internal/_jsonStringifyArray.ts +50 -0
- package/src/internal/_jsonStringifyElement.ts +19 -0
- package/src/internal/_jsonStringifyProperty.ts +30 -0
- package/src/internal/_llmApplicationFinalize.ts +6 -1
- package/src/internal/_randomFormatByte.ts +30 -3
- package/src/internal/_randomFormatDate.ts +4 -20
- package/src/internal/_randomFormatDatetime.ts +34 -20
- package/src/internal/_randomFormatDuration.ts +19 -4
- package/src/internal/_randomFormatIpv4.ts +43 -3
- package/src/internal/_randomFormatIpv6.ts +67 -3
- package/src/internal/_randomFormatJsonPointer.ts +23 -3
- package/src/internal/_randomFormatRegex.ts +21 -3
- package/src/internal/_randomFormatRelativeJsonPointer.ts +23 -10
- package/src/internal/_randomFormatTime.ts +45 -12
- package/src/internal/_randomPattern.ts +4 -1
- package/src/internal/_randomStringLength.ts +61 -17
- package/src/internal/private/__notationRename.ts +1 -4
- package/src/internal/private/__randomComposition.ts +50 -0
- package/src/internal/private/__randomEpoch.ts +34 -0
- package/native/adapter/visit.go +0 -173
- package/native/adapter/visit_property_chain_edges_test.go +0 -129
- package/native/core/programmers/helpers/disable_function_programmer_declare.go +0 -10
- package/native/core/programmers/iterate/check_union_array_like.go +0 -422
- package/native/core/programmers/iterate/prune_object_properties.go +0 -105
- package/native/core/programmers/iterate/stringify_dynamic_properties.go +0 -219
- package/native/core/programmers/iterate/stringify_native.go +0 -14
- package/native/core/programmers/iterate/stringify_regular_properties.go +0 -112
- package/native/core/schemas/metadata/MetadataApplication.go +0 -41
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"os/exec"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// TestJsonStringifyContextualUndefinedTransform pins every json stringify family
|
|
13
|
+
// against ECMAScript JSON.stringify wherever a child serialization evaluates to
|
|
14
|
+
// JavaScript undefined (#2253).
|
|
15
|
+
//
|
|
16
|
+
// A child can serialize to undefined while the value itself is present: `any`
|
|
17
|
+
// and `unknown` delegate to JSON.stringify, which answers undefined for a
|
|
18
|
+
// function, a symbol, and a toJSON that returns undefined; a property typed
|
|
19
|
+
// `undefined` has nothing to serialize; and an array hole reads as undefined at
|
|
20
|
+
// any element type. The object joiner decided omission from a test on the input
|
|
21
|
+
// and the array joiner joined the mapped results, so those children concatenated
|
|
22
|
+
// the token `undefined` into a static property, produced `{"k":undefined}` for a
|
|
23
|
+
// dynamic one, and left an empty array slot - text that fails JSON.parse, which
|
|
24
|
+
// the is/assert/validate families then reported as success.
|
|
25
|
+
//
|
|
26
|
+
// 1. Transform one fixture that exports the direct and factory forms of
|
|
27
|
+
// stringify, isStringify, assertStringify, and validateStringify for `any`
|
|
28
|
+
// and `unknown` in static, optional, dynamic, array, tuple, rest, and nested
|
|
29
|
+
// positions, plus the toJSON, sparse-array, and typed controls beside them.
|
|
30
|
+
// 2. Execute the emitted serializers in Node and compare every success payload
|
|
31
|
+
// with native JSON.stringify of the same input, parsing both so the
|
|
32
|
+
// comparison is contextual equivalence rather than the current emit.
|
|
33
|
+
// 3. Require the guarded families to reject each one-axis invalid twin, run the
|
|
34
|
+
// whole matrix again under `undefined: false`, and report the exact executed
|
|
35
|
+
// case count so a runner that silently skipped cells cannot pass as green.
|
|
36
|
+
func TestJsonStringifyContextualUndefinedTransform(t *testing.T) {
|
|
37
|
+
for _, row := range jsonStringifyContextualUndefinedRows() {
|
|
38
|
+
t.Run(row.name, func(t *testing.T) {
|
|
39
|
+
project := jsonStringifyContextualUndefinedProject(t)
|
|
40
|
+
js := jsonStringifyContextualUndefinedTransform(t, project, row.payload())
|
|
41
|
+
jsonStringifyContextualUndefinedRunRuntime(t, project, js, row)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type jsonStringifyContextualUndefinedRow struct {
|
|
47
|
+
name string
|
|
48
|
+
// config is the JSON fragment appended to typia's resolved plugin entry.
|
|
49
|
+
config string
|
|
50
|
+
// strict marks the row where `undefined: false` makes the checkers reject an
|
|
51
|
+
// explicitly undefined optional property. The serialized text of everything
|
|
52
|
+
// the checkers still accept must not move.
|
|
53
|
+
strict bool
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func (row jsonStringifyContextualUndefinedRow) payload() string {
|
|
57
|
+
return `[{"config":{"transform":"typia/lib/transform"` + row.config + `},"name":"typia","stage":"transform"}]`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func jsonStringifyContextualUndefinedRows() []jsonStringifyContextualUndefinedRow {
|
|
61
|
+
return []jsonStringifyContextualUndefinedRow{
|
|
62
|
+
{name: "default options", config: ``, strict: false},
|
|
63
|
+
{name: "undefined false", config: `,"undefined":false`, strict: true},
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// jsonStringifyContextualUndefinedTypes names every fixture type the runner
|
|
68
|
+
// drives. The Go side owns the list so the exported symbol names and the
|
|
69
|
+
// runner's lookup keys cannot drift apart.
|
|
70
|
+
var jsonStringifyContextualUndefinedTypes = []struct{ Key, TS string }{
|
|
71
|
+
{"anyProperty", "IAnyProperty"},
|
|
72
|
+
{"unknownProperty", "IUnknownProperty"},
|
|
73
|
+
{"undefinedProperty", "IUndefinedProperty"},
|
|
74
|
+
{"optionalAny", "IOptionalAny"},
|
|
75
|
+
{"recordAny", "IRecordAny"},
|
|
76
|
+
{"recordUnknown", "IRecordUnknown"},
|
|
77
|
+
{"mixedRecord", "IMixedRecord"},
|
|
78
|
+
{"anyArray", "IAnyArray"},
|
|
79
|
+
{"unknownArray", "IUnknownArray"},
|
|
80
|
+
{"numberArray", "INumberArray"},
|
|
81
|
+
{"tuple", "ITuple"},
|
|
82
|
+
{"restTuple", "IRestTuple"},
|
|
83
|
+
{"nested", "INested"},
|
|
84
|
+
{"jsonableProperty", "IJsonableProperty"},
|
|
85
|
+
{"maybeJsonableProperty", "IMaybeJsonableProperty"},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func jsonStringifyContextualUndefinedProject(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, "json-stringify-contextual-undefined-")
|
|
96
|
+
if err != nil {
|
|
97
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
98
|
+
}
|
|
99
|
+
t.Cleanup(func() {
|
|
100
|
+
_ = os.RemoveAll(dir)
|
|
101
|
+
})
|
|
102
|
+
src := filepath.Join(dir, "src")
|
|
103
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
104
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
105
|
+
}
|
|
106
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(jsonStringifyContextualUndefinedTSConfig), 0o644); err != nil {
|
|
107
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
108
|
+
}
|
|
109
|
+
if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(jsonStringifyContextualUndefinedSource()), 0o644); err != nil {
|
|
110
|
+
t.Fatalf("write source: %v", err)
|
|
111
|
+
}
|
|
112
|
+
return dir
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func jsonStringifyContextualUndefinedTransform(t *testing.T, project string, payload string) string {
|
|
116
|
+
t.Helper()
|
|
117
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
118
|
+
return runTransform([]string{
|
|
119
|
+
"--cwd", project,
|
|
120
|
+
"--tsconfig", "tsconfig.json",
|
|
121
|
+
"--file", "src/main.ts",
|
|
122
|
+
"--output", "js",
|
|
123
|
+
"--plugins-json", payload,
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
if code != 0 {
|
|
127
|
+
t.Fatalf("json stringify contextual-undefined transform failed: code=%d stderr=\n%s", code, errText)
|
|
128
|
+
}
|
|
129
|
+
return out
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func jsonStringifyContextualUndefinedRunRuntime(t *testing.T, project string, js string, row jsonStringifyContextualUndefinedRow) {
|
|
133
|
+
t.Helper()
|
|
134
|
+
node, err := exec.LookPath("node")
|
|
135
|
+
if err != nil {
|
|
136
|
+
t.Skip("node executable not found")
|
|
137
|
+
}
|
|
138
|
+
runtimeDir := filepath.Join(project, "runtime-"+strings.ReplaceAll(row.name, " ", "-"))
|
|
139
|
+
if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
|
|
140
|
+
t.Fatalf("mkdir runtime dir: %v", err)
|
|
141
|
+
}
|
|
142
|
+
ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
|
|
143
|
+
stubs := map[string]string{
|
|
144
|
+
"json-stringify-number-stub.cjs": jsonStringifyContextualUndefinedNumberStub,
|
|
145
|
+
"json-stringify-string-stub.cjs": jsonStringifyContextualUndefinedStringStub,
|
|
146
|
+
"json-stringify-tail-stub.cjs": jsonStringifyContextualUndefinedTailStub,
|
|
147
|
+
"json-stringify-rest-stub.cjs": jsonStringifyContextualUndefinedRestStub,
|
|
148
|
+
"throw-type-guard-error-stub.cjs": jsonStringifyContextualUndefinedThrowStub,
|
|
149
|
+
}
|
|
150
|
+
for name, content := range stubs {
|
|
151
|
+
if err := os.WriteFile(filepath.Join(runtimeDir, name), []byte(content), 0o644); err != nil {
|
|
152
|
+
t.Fatalf("write %s: %v", name, err)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
runtimeJS := js
|
|
156
|
+
for from, to := range map[string]string{
|
|
157
|
+
`require("typia/lib/internal/_jsonStringifyNumber")`: `require("./json-stringify-number-stub.cjs")`,
|
|
158
|
+
`require("typia/lib/internal/_jsonStringifyString")`: `require("./json-stringify-string-stub.cjs")`,
|
|
159
|
+
`require("typia/lib/internal/_jsonStringifyTail")`: `require("./json-stringify-tail-stub.cjs")`,
|
|
160
|
+
`require("typia/lib/internal/_jsonStringifyRest")`: `require("./json-stringify-rest-stub.cjs")`,
|
|
161
|
+
`require("typia/lib/internal/_throwTypeGuardError")`: `require("./throw-type-guard-error-stub.cjs")`,
|
|
162
|
+
} {
|
|
163
|
+
runtimeJS = strings.ReplaceAll(runtimeJS, from, to)
|
|
164
|
+
}
|
|
165
|
+
runtimeJS = ttscTypiaTestRewriteCommonJS(t, runtimeJS)
|
|
166
|
+
if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
|
|
167
|
+
t.Fatalf("write runtime module: %v", err)
|
|
168
|
+
}
|
|
169
|
+
runner := filepath.Join(runtimeDir, "run.cjs")
|
|
170
|
+
prelude := fmt.Sprintf("const STRICT_UNDEFINED = %v;\n", row.strict)
|
|
171
|
+
if err := os.WriteFile(runner, []byte(prelude+jsonStringifyContextualUndefinedRunner), 0o644); err != nil {
|
|
172
|
+
t.Fatalf("write runtime runner: %v", err)
|
|
173
|
+
}
|
|
174
|
+
cmd := exec.Command(node, runner)
|
|
175
|
+
cmd.Dir = runtimeDir
|
|
176
|
+
output, err := cmd.CombinedOutput()
|
|
177
|
+
if err != nil {
|
|
178
|
+
t.Fatalf("json stringify contextual-undefined runtime failed (%s): %v\n%s", row.name, err, output)
|
|
179
|
+
}
|
|
180
|
+
// The count is asserted rather than the exit code so a row that silently stops
|
|
181
|
+
// running cannot pass as a row that ran and agreed with the oracle. 59 value
|
|
182
|
+
// rows x 6 executions each: direct and factory forms across stringify,
|
|
183
|
+
// isStringify, assertStringify, and validateStringify, minus the two guarded
|
|
184
|
+
// entry points for rows whose contract is rejection.
|
|
185
|
+
const expected = "RAN 354 CASES"
|
|
186
|
+
if !strings.Contains(string(output), expected) {
|
|
187
|
+
t.Fatalf("json stringify contextual-undefined runner did not report %q (%s); got:\n%s", expected, row.name, output)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const jsonStringifyContextualUndefinedTSConfig = `{
|
|
192
|
+
"compilerOptions": {
|
|
193
|
+
"target": "ES2022",
|
|
194
|
+
"module": "commonjs",
|
|
195
|
+
"moduleResolution": "bundler",
|
|
196
|
+
"ignoreDeprecations": "6.0",
|
|
197
|
+
"types": ["*"],
|
|
198
|
+
"esModuleInterop": true,
|
|
199
|
+
"strict": true,
|
|
200
|
+
"skipLibCheck": true
|
|
201
|
+
},
|
|
202
|
+
"include": ["src"]
|
|
203
|
+
}
|
|
204
|
+
`
|
|
205
|
+
|
|
206
|
+
const jsonStringifyContextualUndefinedDeclarations = `import typia from "typia";
|
|
207
|
+
|
|
208
|
+
interface IAnyProperty {
|
|
209
|
+
keep: number;
|
|
210
|
+
value: any;
|
|
211
|
+
}
|
|
212
|
+
interface IUnknownProperty {
|
|
213
|
+
keep: number;
|
|
214
|
+
value: unknown;
|
|
215
|
+
}
|
|
216
|
+
interface IUndefinedProperty {
|
|
217
|
+
keep: number;
|
|
218
|
+
value: undefined;
|
|
219
|
+
}
|
|
220
|
+
interface IOptionalAny {
|
|
221
|
+
keep: number;
|
|
222
|
+
value?: any;
|
|
223
|
+
}
|
|
224
|
+
type IRecordAny = Record<string, any>;
|
|
225
|
+
type IRecordUnknown = Record<string, unknown>;
|
|
226
|
+
interface IMixedRecord {
|
|
227
|
+
keep: number;
|
|
228
|
+
[key: string]: any;
|
|
229
|
+
}
|
|
230
|
+
type IAnyArray = any[];
|
|
231
|
+
type IUnknownArray = unknown[];
|
|
232
|
+
type INumberArray = number[];
|
|
233
|
+
type ITuple = [any, unknown, number];
|
|
234
|
+
type IRestTuple = [number, ...any[]];
|
|
235
|
+
interface INested {
|
|
236
|
+
outer: { inner: any }[];
|
|
237
|
+
}
|
|
238
|
+
interface IJsonable {
|
|
239
|
+
toJSON(): string;
|
|
240
|
+
}
|
|
241
|
+
interface IMaybeJsonable {
|
|
242
|
+
toJSON(): string | undefined;
|
|
243
|
+
}
|
|
244
|
+
interface IJsonableProperty {
|
|
245
|
+
keep: number;
|
|
246
|
+
value: IJsonable;
|
|
247
|
+
}
|
|
248
|
+
interface IMaybeJsonableProperty {
|
|
249
|
+
keep: number;
|
|
250
|
+
value: IMaybeJsonable;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
`
|
|
254
|
+
|
|
255
|
+
// jsonStringifyContextualUndefinedSource emits the direct and factory form of
|
|
256
|
+
// all four stringify families for every fixture type, so one transform covers
|
|
257
|
+
// the whole family x form axis of the matrix.
|
|
258
|
+
func jsonStringifyContextualUndefinedSource() string {
|
|
259
|
+
var builder strings.Builder
|
|
260
|
+
builder.WriteString(jsonStringifyContextualUndefinedDeclarations)
|
|
261
|
+
for _, entry := range jsonStringifyContextualUndefinedTypes {
|
|
262
|
+
for _, family := range []string{"stringify", "isStringify", "assertStringify", "validateStringify"} {
|
|
263
|
+
creator := "create" + strings.ToUpper(family[:1]) + family[1:]
|
|
264
|
+
fmt.Fprintf(
|
|
265
|
+
&builder,
|
|
266
|
+
"export const factory_%s_%s = typia.json.%s<%s>();\n",
|
|
267
|
+
family, entry.Key, creator, entry.TS,
|
|
268
|
+
)
|
|
269
|
+
fmt.Fprintf(
|
|
270
|
+
&builder,
|
|
271
|
+
"export const direct_%s_%s = (input: %s) => typia.json.%s<%s>(input);\n",
|
|
272
|
+
family, entry.Key, entry.TS, family, entry.TS,
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
builder.WriteString("\n")
|
|
276
|
+
}
|
|
277
|
+
return builder.String()
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const jsonStringifyContextualUndefinedNumberStub = `module.exports._jsonStringifyNumber = (value) => (Number.isFinite(value) ? value : null);
|
|
281
|
+
`
|
|
282
|
+
|
|
283
|
+
const jsonStringifyContextualUndefinedStringStub = `module.exports._jsonStringifyString = (value) => JSON.stringify(value);
|
|
284
|
+
`
|
|
285
|
+
|
|
286
|
+
// The tail and rest stubs are transcriptions of
|
|
287
|
+
// packages/typia/src/internal/_jsonStringifyTail.ts and _jsonStringifyRest.ts.
|
|
288
|
+
const jsonStringifyContextualUndefinedTailStub = `module.exports._jsonStringifyTail = (str) =>
|
|
289
|
+
str[str.length - 1] === "," ? str.substring(0, str.length - 1) : str;
|
|
290
|
+
`
|
|
291
|
+
|
|
292
|
+
const jsonStringifyContextualUndefinedRestStub = `module.exports._jsonStringifyRest = (str) =>
|
|
293
|
+
str.length === 2 ? "" : "," + str.substring(1, str.length - 1);
|
|
294
|
+
`
|
|
295
|
+
|
|
296
|
+
const jsonStringifyContextualUndefinedThrowStub = `module.exports._throwTypeGuardError = (props) => {
|
|
297
|
+
const error = new Error(props.expected);
|
|
298
|
+
Object.assign(error, props);
|
|
299
|
+
throw error;
|
|
300
|
+
};
|
|
301
|
+
`
|
|
302
|
+
|
|
303
|
+
const jsonStringifyContextualUndefinedRunner = `const mod = require("./main.cjs");
|
|
304
|
+
|
|
305
|
+
// deepEqual compares two JSON.parse results. Objects are compared by key set and
|
|
306
|
+
// value, never by text, because typia is free to order the properties it emits
|
|
307
|
+
// differently from ECMAScript while still producing an equivalent document.
|
|
308
|
+
const deepEqual = (x, y) => {
|
|
309
|
+
if (x === y) return true;
|
|
310
|
+
if (x === null || y === null) return false;
|
|
311
|
+
if (typeof x !== "object" || typeof y !== "object") return false;
|
|
312
|
+
if (Array.isArray(x) !== Array.isArray(y)) return false;
|
|
313
|
+
if (Array.isArray(x)) {
|
|
314
|
+
if (x.length !== y.length) return false;
|
|
315
|
+
return x.every((v, i) => deepEqual(v, y[i]));
|
|
316
|
+
}
|
|
317
|
+
const xk = Object.keys(x).sort();
|
|
318
|
+
const yk = Object.keys(y).sort();
|
|
319
|
+
if (xk.length !== yk.length) return false;
|
|
320
|
+
if (!xk.every((k, i) => k === yk[i])) return false;
|
|
321
|
+
return xk.every((k) => deepEqual(x[k], y[k]));
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const sparse = (assign) => {
|
|
325
|
+
const array = [];
|
|
326
|
+
assign(array);
|
|
327
|
+
return array;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const CASES = [
|
|
331
|
+
// Static any property: every value whose serialization is undefined must
|
|
332
|
+
// vanish exactly the way ECMAScript drops it, and the valid neighbors beside
|
|
333
|
+
// it must survive.
|
|
334
|
+
["anyProperty", "function value", () => ({ keep: 1, value: () => 0 })],
|
|
335
|
+
["anyProperty", "symbol value", () => ({ keep: 1, value: Symbol("s") })],
|
|
336
|
+
["anyProperty", "undefined value", () => ({ keep: 1, value: undefined })],
|
|
337
|
+
["anyProperty", "null value", () => ({ keep: 1, value: null })],
|
|
338
|
+
["anyProperty", "number value", () => ({ keep: 1, value: 2 })],
|
|
339
|
+
["anyProperty", "nested function member", () => ({
|
|
340
|
+
keep: 1,
|
|
341
|
+
value: { a: 1, b: () => 0, c: Symbol("x"), d: undefined },
|
|
342
|
+
})],
|
|
343
|
+
["anyProperty", "nested array member", () => ({
|
|
344
|
+
keep: 1,
|
|
345
|
+
value: [() => 0, 1, Symbol("y"), undefined],
|
|
346
|
+
})],
|
|
347
|
+
["anyProperty", "toJSON returning undefined", () => ({
|
|
348
|
+
keep: 1,
|
|
349
|
+
value: { toJSON: () => undefined },
|
|
350
|
+
})],
|
|
351
|
+
["anyProperty", "toJSON returning a value", () => ({
|
|
352
|
+
keep: 1,
|
|
353
|
+
value: { toJSON: () => 3 },
|
|
354
|
+
})],
|
|
355
|
+
["anyProperty", "invalid keep twin", () => ({ keep: "x", value: 1 }), "reject"],
|
|
356
|
+
|
|
357
|
+
// unknown must behave exactly like any.
|
|
358
|
+
["unknownProperty", "function value", () => ({ keep: 1, value: () => 0 })],
|
|
359
|
+
["unknownProperty", "symbol value", () => ({ keep: 1, value: Symbol("s") })],
|
|
360
|
+
["unknownProperty", "undefined value", () => ({ keep: 1, value: undefined })],
|
|
361
|
+
["unknownProperty", "number value", () => ({ keep: 1, value: 2 })],
|
|
362
|
+
["unknownProperty", "invalid keep twin", () => ({ keep: "x", value: 1 }), "reject"],
|
|
363
|
+
|
|
364
|
+
// A required property typed undefined has nothing to serialize at all.
|
|
365
|
+
["undefinedProperty", "undefined value", () => ({ keep: 1, value: undefined })],
|
|
366
|
+
|
|
367
|
+
// Optional any: absent, explicitly undefined, and present.
|
|
368
|
+
["optionalAny", "absent", () => ({ keep: 1 })],
|
|
369
|
+
["optionalAny", "explicit undefined", () => ({ keep: 1, value: undefined }), "strictReject"],
|
|
370
|
+
["optionalAny", "function value", () => ({ keep: 1, value: () => 0 })],
|
|
371
|
+
["optionalAny", "number value", () => ({ keep: 1, value: 2 })],
|
|
372
|
+
|
|
373
|
+
// Dynamic (index signature) properties.
|
|
374
|
+
["recordAny", "mixed omitted and kept", () => ({
|
|
375
|
+
keep: 1,
|
|
376
|
+
omit: () => 0,
|
|
377
|
+
sym: Symbol("s"),
|
|
378
|
+
nil: null,
|
|
379
|
+
undef: undefined,
|
|
380
|
+
})],
|
|
381
|
+
["recordAny", "every entry omitted", () => ({ omit: () => 0, sym: Symbol("s") })],
|
|
382
|
+
["recordAny", "empty object", () => ({})],
|
|
383
|
+
["recordUnknown", "mixed omitted and kept", () => ({
|
|
384
|
+
keep: 1,
|
|
385
|
+
omit: () => 0,
|
|
386
|
+
sym: Symbol("s"),
|
|
387
|
+
nil: null,
|
|
388
|
+
})],
|
|
389
|
+
["mixedRecord", "static neighbor with omitted dynamic", () => ({
|
|
390
|
+
keep: 1,
|
|
391
|
+
omit: () => 0,
|
|
392
|
+
sym: Symbol("s"),
|
|
393
|
+
nil: null,
|
|
394
|
+
})],
|
|
395
|
+
["mixedRecord", "static neighbor only", () => ({ keep: 1 })],
|
|
396
|
+
|
|
397
|
+
// Arrays: a serialization of undefined is null in array context, and a hole
|
|
398
|
+
// reads as undefined at every element type.
|
|
399
|
+
["anyArray", "leading function", () => [() => 0, 1]],
|
|
400
|
+
["anyArray", "all element kinds", () => [() => 0, 1, Symbol("s"), undefined, null]],
|
|
401
|
+
["anyArray", "single function", () => [() => 0]],
|
|
402
|
+
["anyArray", "empty", () => []],
|
|
403
|
+
["anyArray", "sparse tail", () => sparse((a) => {
|
|
404
|
+
a[0] = 1;
|
|
405
|
+
a[3] = 4;
|
|
406
|
+
})],
|
|
407
|
+
["unknownArray", "leading function", () => [() => 0, 1]],
|
|
408
|
+
["numberArray", "dense control", () => [1, 2, 3]],
|
|
409
|
+
["numberArray", "sparse leading holes", () => sparse((a) => {
|
|
410
|
+
a[2] = 3;
|
|
411
|
+
})],
|
|
412
|
+
|
|
413
|
+
// Tuples and rest tuples.
|
|
414
|
+
["tuple", "function and symbol elements", () => [() => 0, Symbol("s"), 1]],
|
|
415
|
+
["tuple", "undefined elements", () => [undefined, undefined, 1]],
|
|
416
|
+
["tuple", "valid control", () => [1, 2, 3]],
|
|
417
|
+
["tuple", "invalid last element twin", () => [1, 2, "x"], "reject"],
|
|
418
|
+
["restTuple", "function inside rest", () => [1, () => 0, 2]],
|
|
419
|
+
["restTuple", "empty rest", () => [1]],
|
|
420
|
+
|
|
421
|
+
// Nested containers.
|
|
422
|
+
["nested", "function inside nested object", () => ({
|
|
423
|
+
outer: [{ inner: () => 0 }, { inner: 1 }],
|
|
424
|
+
})],
|
|
425
|
+
|
|
426
|
+
// toJSON controls: present, inherited, returning undefined, and non-callable.
|
|
427
|
+
["jsonableProperty", "own toJSON", () => ({ keep: 1, value: { toJSON: () => "x" } })],
|
|
428
|
+
["jsonableProperty", "inherited toJSON", () => {
|
|
429
|
+
class Jsonable {
|
|
430
|
+
toJSON() {
|
|
431
|
+
return "x";
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return { keep: 1, value: new Jsonable() };
|
|
435
|
+
}],
|
|
436
|
+
// A non-callable toJSON twin belongs here by shape but not by cause: the
|
|
437
|
+
// checker fails to reject it and the serializer then throws
|
|
438
|
+
// "input.value.toJSON is not a function", so isStringify and validateStringify
|
|
439
|
+
// throw where their contract is to answer. That is a callability rule, not the
|
|
440
|
+
// contextual "decide omission from the serialized result" rule this regression
|
|
441
|
+
// pins, and fixing it here would mix two root causes into one diff. It is
|
|
442
|
+
// tracked in samchon/typia#2271 and keeps its own twin there.
|
|
443
|
+
["maybeJsonableProperty", "toJSON returning undefined", () => ({
|
|
444
|
+
keep: 1,
|
|
445
|
+
value: { toJSON: () => undefined },
|
|
446
|
+
})],
|
|
447
|
+
["maybeJsonableProperty", "toJSON returning a value", () => ({
|
|
448
|
+
keep: 1,
|
|
449
|
+
value: { toJSON: () => "x" },
|
|
450
|
+
})],
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
let ran = 0;
|
|
454
|
+
const failures = [];
|
|
455
|
+
|
|
456
|
+
const fail = (label, message) => failures.push(label + ": " + message);
|
|
457
|
+
|
|
458
|
+
const parse = (label, text) => {
|
|
459
|
+
try {
|
|
460
|
+
return { ok: true, value: JSON.parse(text) };
|
|
461
|
+
} catch (error) {
|
|
462
|
+
fail(label, "produced text that is not JSON: " + JSON.stringify(text));
|
|
463
|
+
return { ok: false };
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const compare = (label, text, oracle) => {
|
|
468
|
+
if (typeof text !== "string") {
|
|
469
|
+
fail(label, "produced " + String(text) + " instead of JSON text");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const parsed = parse(label, text);
|
|
473
|
+
if (parsed.ok !== true) return;
|
|
474
|
+
if (!deepEqual(parsed.value, oracle.value))
|
|
475
|
+
fail(
|
|
476
|
+
label,
|
|
477
|
+
"produced " + text + " but JSON.stringify produced " + oracle.text,
|
|
478
|
+
);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const run = (fn, argument) => {
|
|
482
|
+
try {
|
|
483
|
+
return { thrown: false, value: fn(argument) };
|
|
484
|
+
} catch (error) {
|
|
485
|
+
return { thrown: true, error };
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
for (const [key, label, make, kind] of CASES) {
|
|
490
|
+
const reject = kind === "reject";
|
|
491
|
+
const strictReject = kind === "strictReject" && STRICT_UNDEFINED === true;
|
|
492
|
+
const oracleText = JSON.stringify(make());
|
|
493
|
+
const oracle = { text: oracleText, value: undefined };
|
|
494
|
+
if (reject !== true) {
|
|
495
|
+
if (typeof oracleText !== "string")
|
|
496
|
+
throw new Error(key + " / " + label + ": oracle produced no text");
|
|
497
|
+
oracle.value = JSON.parse(oracleText);
|
|
498
|
+
}
|
|
499
|
+
for (const form of ["factory", "direct"]) {
|
|
500
|
+
const name = key + " / " + label + " / " + form;
|
|
501
|
+
|
|
502
|
+
// json.stringify never validates, so its text must match the oracle for
|
|
503
|
+
// every statically accepted input, in both option rows.
|
|
504
|
+
if (reject !== true) {
|
|
505
|
+
ran += 1;
|
|
506
|
+
const raw = run(mod[form + "_stringify_" + key], make());
|
|
507
|
+
if (raw.thrown === true)
|
|
508
|
+
fail(name + " / stringify", "threw " + String(raw.error && raw.error.message));
|
|
509
|
+
else compare(name + " / stringify", raw.value, oracle);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// json.isStringify: null means rejected.
|
|
513
|
+
ran += 1;
|
|
514
|
+
const is = run(mod[form + "_isStringify_" + key], make());
|
|
515
|
+
if (is.thrown === true)
|
|
516
|
+
fail(name + " / isStringify", "threw " + String(is.error && is.error.message));
|
|
517
|
+
else if (reject === true) {
|
|
518
|
+
if (is.value !== null)
|
|
519
|
+
fail(name + " / isStringify", "accepted an invalid value: " + String(is.value));
|
|
520
|
+
} else if (is.value === null) {
|
|
521
|
+
if (strictReject !== true)
|
|
522
|
+
fail(name + " / isStringify", "rejected a valid value");
|
|
523
|
+
} else compare(name + " / isStringify", is.value, oracle);
|
|
524
|
+
|
|
525
|
+
// json.assertStringify: a throw means rejected.
|
|
526
|
+
ran += 1;
|
|
527
|
+
const asserted = run(mod[form + "_assertStringify_" + key], make());
|
|
528
|
+
if (reject === true) {
|
|
529
|
+
if (asserted.thrown !== true)
|
|
530
|
+
fail(name + " / assertStringify", "accepted an invalid value: " + String(asserted.value));
|
|
531
|
+
} else if (asserted.thrown === true) {
|
|
532
|
+
if (strictReject !== true)
|
|
533
|
+
fail(name + " / assertStringify", "rejected a valid value: " + String(asserted.error && asserted.error.message));
|
|
534
|
+
} else compare(name + " / assertStringify", asserted.value, oracle);
|
|
535
|
+
|
|
536
|
+
// json.validateStringify: success must never carry malformed text.
|
|
537
|
+
ran += 1;
|
|
538
|
+
const validated = run(mod[form + "_validateStringify_" + key], make());
|
|
539
|
+
if (validated.thrown === true)
|
|
540
|
+
fail(name + " / validateStringify", "threw " + String(validated.error && validated.error.message));
|
|
541
|
+
else if (reject === true) {
|
|
542
|
+
if (validated.value.success !== false)
|
|
543
|
+
fail(name + " / validateStringify", "accepted an invalid value: " + JSON.stringify(validated.value));
|
|
544
|
+
} else if (validated.value.success === false) {
|
|
545
|
+
if (strictReject !== true)
|
|
546
|
+
fail(name + " / validateStringify", "rejected a valid value: " + JSON.stringify(validated.value.errors));
|
|
547
|
+
} else compare(name + " / validateStringify", validated.value.data, oracle);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (failures.length !== 0) {
|
|
552
|
+
for (const line of failures) console.log(line);
|
|
553
|
+
throw new Error(failures.length + " contextual serialization failures");
|
|
554
|
+
}
|
|
555
|
+
console.log("RAN " + ran + " CASES");
|
|
556
|
+
`
|