typia 13.2.0 → 14.0.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/http.d.ts +3 -3
- package/lib/http.js.map +1 -1
- package/lib/http.mjs.map +1 -1
- package/lib/internal/_assertGuard.js +13 -3
- package/lib/internal/_assertGuard.js.map +1 -1
- package/lib/internal/_assertGuard.mjs +4 -2
- package/lib/internal/_assertGuard.mjs.map +1 -1
- package/lib/internal/_isFormatIdnHostname.js +5 -1
- package/lib/internal/_isFormatIdnHostname.js.map +1 -1
- package/lib/internal/_isFormatIdnHostname.mjs +1 -1
- package/lib/internal/_isFormatIdnHostname.mjs.map +1 -1
- package/lib/internal/_isFormatTime.js +19 -2
- package/lib/internal/_isFormatTime.js.map +1 -1
- package/lib/internal/_isFormatTime.mjs +9 -2
- package/lib/internal/_isFormatTime.mjs.map +1 -1
- package/lib/internal/_randomArray.js +37 -4
- package/lib/internal/_randomArray.js.map +1 -1
- package/lib/internal/_randomArray.mjs +16 -3
- package/lib/internal/_randomArray.mjs.map +1 -1
- package/lib/internal/_randomFormatByte.js +15 -12
- package/lib/internal/_randomFormatByte.js.map +1 -1
- package/lib/internal/_randomFormatByte.mjs +11 -10
- package/lib/internal/_randomFormatByte.mjs.map +1 -1
- package/lib/internal/_randomFormatDatetime.mjs +1 -1
- package/lib/internal/_randomFormatHostname.d.ts +2 -2
- package/lib/internal/_randomFormatHostname.js +2 -2
- package/lib/internal/_randomFormatHostname.mjs +2 -2
- package/lib/internal/_randomFormatHostname.mjs.map +1 -1
- package/lib/internal/_randomFormatIdnHostname.js +9 -10
- package/lib/internal/_randomFormatIdnHostname.js.map +1 -1
- package/lib/internal/_randomFormatIdnHostname.mjs +3 -4
- package/lib/internal/_randomFormatIdnHostname.mjs.map +1 -1
- package/lib/internal/_randomFormatRegex.js +4 -4
- package/lib/internal/_randomFormatRegex.js.map +1 -1
- package/lib/internal/_randomFormatRegex.mjs +1 -2
- package/lib/internal/_randomFormatRegex.mjs.map +1 -1
- package/lib/internal/_randomFormatTime.js +15 -10
- package/lib/internal/_randomFormatTime.js.map +1 -1
- package/lib/internal/_randomFormatTime.mjs +6 -3
- package/lib/internal/_randomFormatTime.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/internal/private/__randomComposition.d.ts +8 -0
- package/lib/internal/private/__randomComposition.js +21 -1
- package/lib/internal/private/__randomComposition.js.map +1 -1
- package/lib/internal/private/__randomComposition.mjs +18 -1
- package/lib/internal/private/__randomComposition.mjs.map +1 -1
- package/lib/json.d.ts +2 -2
- package/lib/json.js.map +1 -1
- package/lib/json.mjs.map +1 -1
- package/lib/module.d.ts +28 -13
- package/lib/module.js.map +1 -1
- package/lib/module.mjs.map +1 -1
- package/lib/notations.d.ts +4 -4
- package/lib/notations.js.map +1 -1
- package/lib/notations.mjs.map +1 -1
- package/lib/plain.d.ts +3 -3
- package/lib/plain.js.map +1 -1
- package/lib/plain.mjs.map +1 -1
- package/lib/protobuf.d.ts +2 -2
- package/lib/protobuf.js.map +1 -1
- package/lib/protobuf.mjs.map +1 -1
- package/lib/transform.mjs +1 -1
- package/native/cmd/ttsc-typia/assert_guard_factory_contract_test.go +17 -4
- package/native/cmd/ttsc-typia/create_assert_error_factory_arity_test.go +309 -0
- package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +206 -0
- package/native/cmd/ttsc-typia/local_function_named_like_typia_operation_transform_test.go +185 -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/recursive_conditional_alias_name_transform_test.go +307 -0
- package/native/cmd/ttsc-typia/shadowed_typia_module_declaration_diagnostic_test.go +152 -0
- package/native/cmd/ttsc-typia/transform.go +2 -2
- package/native/cmd/ttsc-typia/transform_helpers_test.go +96 -18
- package/native/core/factories/FormatCheatSheet.go +2 -2
- package/native/core/factories/MetadataCommentTagFactory.go +91 -7
- package/native/core/factories/factory_utility_coverage_test.go +896 -907
- package/native/core/factories/internal/metadata/MetadataHelper.go +5 -28
- 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 -3
- 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/MetadataCollection.go +47 -5
- package/native/transform/CallExpressionTransformer.go +167 -0
- package/package.json +3 -12
- package/src/http.ts +18 -4
- package/src/internal/_assertGuard.ts +13 -2
- package/src/internal/_isFormatIdnHostname.ts +5 -1
- package/src/internal/_isFormatTime.ts +19 -2
- package/src/internal/_randomArray.ts +42 -6
- package/src/internal/_randomFormatByte.ts +17 -12
- package/src/internal/_randomFormatHostname.ts +2 -2
- package/src/internal/_randomFormatIdnHostname.ts +8 -9
- package/src/internal/_randomFormatRegex.ts +4 -6
- package/src/internal/_randomFormatTime.ts +15 -10
- package/src/internal/_randomPattern.ts +18 -3
- package/src/internal/_randomStringLength.ts +9 -2
- package/src/internal/private/__randomComposition.ts +24 -0
- package/src/json.ts +16 -4
- package/src/module.ts +56 -22
- package/src/notations.ts +16 -4
- package/src/plain.ts +12 -3
- package/src/protobuf.ts +16 -4
- 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,307 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"os/exec"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestRecursiveConditionalAliasNameTransform pins the transform over a type
|
|
12
|
+
// whose resolved graph names itself (issue #2331).
|
|
13
|
+
//
|
|
14
|
+
// `Primitive<T>` instantiated at the self-recursive union `type Node = Date |
|
|
15
|
+
// Node[]` resolves to a union `X = string & Format<"date-time"> | X[]`, whose
|
|
16
|
+
// array member's sole type argument is `X` itself. The name builders walked
|
|
17
|
+
// union member -> array reference -> type argument and arrived back at the type
|
|
18
|
+
// whose name was still being composed, so they recursed until the plugin process
|
|
19
|
+
// died of a Go stack overflow: no diagnostic, no emit, and ttsc taken with it.
|
|
20
|
+
// `Primitive` is only one witness -- any self-recursive conditional alias
|
|
21
|
+
// produces the same graph -- so the fixture pins the bare `Rec<T>` form too.
|
|
22
|
+
//
|
|
23
|
+
// The names the fix produces have to stay usable, not merely terminate: the
|
|
24
|
+
// cycle's placeholder is the type's own symbol with its arguments unexpanded, so
|
|
25
|
+
// a JSON schema component keeps a short key instead of being named after a
|
|
26
|
+
// several-hundred-character structural dump of the elided rendering.
|
|
27
|
+
//
|
|
28
|
+
// 1. Transform is / assert / json.schema over `Primitive<Node>`, the bare
|
|
29
|
+
// recursive conditional alias, and the recursive-JsonValue object, beside
|
|
30
|
+
// controls (named recursive union, self-recursive array alias, recursive
|
|
31
|
+
// interface) that already transformed and must keep their names.
|
|
32
|
+
// 2. Require the emit to carry a recursive array validator rather than an
|
|
33
|
+
// inlined expansion.
|
|
34
|
+
// 3. Execute the emitted validators over positive, negative, nested and
|
|
35
|
+
// boundary values, and require the recursive schema to self-reference
|
|
36
|
+
// through a bounded component name.
|
|
37
|
+
func TestRecursiveConditionalAliasNameTransform(t *testing.T) {
|
|
38
|
+
project := recursiveConditionalAliasNameProject(t)
|
|
39
|
+
transform := func() (string, string, int) {
|
|
40
|
+
return ttscTypiaTestCapture(func() int {
|
|
41
|
+
return runTransform([]string{
|
|
42
|
+
"--cwd", project,
|
|
43
|
+
"--tsconfig", "tsconfig.json",
|
|
44
|
+
"--file", "src/main.ts",
|
|
45
|
+
"--output", "js",
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
out, errText, code := transform()
|
|
51
|
+
if code != 0 {
|
|
52
|
+
t.Fatalf("recursive conditional alias transform failed: code=%d stderr=\n%s", code, errText)
|
|
53
|
+
}
|
|
54
|
+
if strings.Contains(out, "_ia0") == false {
|
|
55
|
+
t.Fatalf("expected a recursive array validator in the emit, got:\n%s", out)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The cycle guard is a map, and the name it settles on becomes a helper
|
|
59
|
+
// identifier and a schema component key. Map iteration order must not reach
|
|
60
|
+
// either, so the same source has to emit the same bytes.
|
|
61
|
+
repeat, repeatErr, repeatCode := transform()
|
|
62
|
+
if repeatCode != 0 {
|
|
63
|
+
t.Fatalf("recursive conditional alias repeat transform failed: code=%d stderr=\n%s", repeatCode, repeatErr)
|
|
64
|
+
}
|
|
65
|
+
if repeat != out {
|
|
66
|
+
t.Fatalf("repeat transform emitted different bytes:\nfirst:\n%s\nsecond:\n%s", out, repeat)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
recursiveConditionalAliasNameRunRuntimeCases(t, project, out)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func recursiveConditionalAliasNameProject(t *testing.T) string {
|
|
73
|
+
t.Helper()
|
|
74
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
75
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
76
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
77
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
78
|
+
}
|
|
79
|
+
dir, err := os.MkdirTemp(base, "recursive-conditional-alias-")
|
|
80
|
+
if err != nil {
|
|
81
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
82
|
+
}
|
|
83
|
+
t.Cleanup(func() {
|
|
84
|
+
_ = os.RemoveAll(dir)
|
|
85
|
+
})
|
|
86
|
+
src := filepath.Join(dir, "src")
|
|
87
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
88
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
89
|
+
}
|
|
90
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(recursiveConditionalAliasNameTSConfig), 0o644); err != nil {
|
|
91
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
92
|
+
}
|
|
93
|
+
if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(recursiveConditionalAliasNameSource), 0o644); err != nil {
|
|
94
|
+
t.Fatalf("write source: %v", err)
|
|
95
|
+
}
|
|
96
|
+
return dir
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func recursiveConditionalAliasNameRunRuntimeCases(t *testing.T, project string, js string) {
|
|
100
|
+
t.Helper()
|
|
101
|
+
node, err := exec.LookPath("node")
|
|
102
|
+
if err != nil {
|
|
103
|
+
t.Skip("node executable not found")
|
|
104
|
+
}
|
|
105
|
+
runtimeDir := filepath.Join(project, "runtime")
|
|
106
|
+
if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
|
|
107
|
+
t.Fatalf("mkdir runtime dir: %v", err)
|
|
108
|
+
}
|
|
109
|
+
ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
|
|
110
|
+
if err := os.WriteFile(filepath.Join(runtimeDir, "is-format-date-time-stub.cjs"), []byte(recursiveConditionalAliasNameFormatStub), 0o644); err != nil {
|
|
111
|
+
t.Fatalf("write format stub: %v", err)
|
|
112
|
+
}
|
|
113
|
+
js = strings.ReplaceAll(js, `require("typia/lib/internal/_isFormatDateTime")`, `require("./is-format-date-time-stub.cjs")`)
|
|
114
|
+
runtimeJS := ttscTypiaTestRewriteCommonJS(t, js)
|
|
115
|
+
if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
|
|
116
|
+
t.Fatalf("write runtime module: %v", err)
|
|
117
|
+
}
|
|
118
|
+
runner := filepath.Join(runtimeDir, "run.cjs")
|
|
119
|
+
if err := os.WriteFile(runner, []byte(recursiveConditionalAliasNameRuntimeRunner), 0o644); err != nil {
|
|
120
|
+
t.Fatalf("write runtime runner: %v", err)
|
|
121
|
+
}
|
|
122
|
+
cmd := exec.Command(node, runner)
|
|
123
|
+
cmd.Dir = runtimeDir
|
|
124
|
+
output, err := cmd.CombinedOutput()
|
|
125
|
+
if err != nil {
|
|
126
|
+
t.Fatalf("recursive conditional alias runtime cases failed: %v\n%s", err, output)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const recursiveConditionalAliasNameTSConfig = `{
|
|
131
|
+
"compilerOptions": {
|
|
132
|
+
"target": "ES2022",
|
|
133
|
+
"module": "commonjs",
|
|
134
|
+
"moduleResolution": "bundler",
|
|
135
|
+
"ignoreDeprecations": "6.0",
|
|
136
|
+
"types": ["*"],
|
|
137
|
+
"esModuleInterop": true,
|
|
138
|
+
"strict": true,
|
|
139
|
+
"skipLibCheck": true
|
|
140
|
+
},
|
|
141
|
+
"include": ["src"]
|
|
142
|
+
}
|
|
143
|
+
`
|
|
144
|
+
|
|
145
|
+
// The shipped helper validates the RFC 3339 date-time shape; the runtime cases
|
|
146
|
+
// only need it to separate a date-time string from a plain one, and a stable
|
|
147
|
+
// predicate keeps a failure reproducible.
|
|
148
|
+
const recursiveConditionalAliasNameFormatStub = `module.exports._isFormatDateTime = (value) =>
|
|
149
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/.test(value);
|
|
150
|
+
`
|
|
151
|
+
|
|
152
|
+
const recursiveConditionalAliasNameSource = `import typia, { Primitive, tags } from "typia";
|
|
153
|
+
|
|
154
|
+
// 1. the reported case: Primitive over a self-recursive union carrying Date.
|
|
155
|
+
// Primitive rewrites the Date arm to a date-time string and keeps the array
|
|
156
|
+
// arm pointing back at the whole union.
|
|
157
|
+
type Node = Date | Node[];
|
|
158
|
+
export const isPrimitiveNode = typia.createIs<Primitive<Node>>();
|
|
159
|
+
export const assertPrimitiveNode = typia.createAssert<Primitive<Node>>();
|
|
160
|
+
export const schemaPrimitiveNode = typia.json.schema<Primitive<Node>>();
|
|
161
|
+
|
|
162
|
+
// 2. the same graph without Primitive: any self-recursive conditional alias
|
|
163
|
+
// instantiates to a union whose array member's type argument is that union
|
|
164
|
+
type Rec<T> = T extends Date
|
|
165
|
+
? string
|
|
166
|
+
: T extends (infer U)[]
|
|
167
|
+
? Rec<U>[]
|
|
168
|
+
: never;
|
|
169
|
+
export const isRec = typia.createIs<Rec<Node>>();
|
|
170
|
+
|
|
171
|
+
// 3. the second report: the recursion sits behind an object property
|
|
172
|
+
interface JsonObject {
|
|
173
|
+
[key: string]: JsonValue;
|
|
174
|
+
}
|
|
175
|
+
type JsonValue = string | number | boolean | null | JsonObject | JsonValue[];
|
|
176
|
+
interface Output {
|
|
177
|
+
at: Date;
|
|
178
|
+
payload: JsonValue;
|
|
179
|
+
}
|
|
180
|
+
export const isOutput = typia.createIs<Primitive<Output>>();
|
|
181
|
+
|
|
182
|
+
// controls: recursions that already named themselves through an alias or a
|
|
183
|
+
// declaration must keep transforming and must not take the cycle placeholder
|
|
184
|
+
type Named = (string & tags.Format<"date-time">) | Named[];
|
|
185
|
+
export const isNamed = typia.createIs<Named>();
|
|
186
|
+
export const schemaNamed = typia.json.schema<Named>();
|
|
187
|
+
|
|
188
|
+
type SelfArray = SelfArray[];
|
|
189
|
+
export const isSelfArray = typia.createIs<SelfArray>();
|
|
190
|
+
|
|
191
|
+
interface ICategory {
|
|
192
|
+
name: string;
|
|
193
|
+
children: ICategory[];
|
|
194
|
+
}
|
|
195
|
+
export const isCategory = typia.createIs<ICategory>();
|
|
196
|
+
export const schemaCategory = typia.json.schema<ICategory>();
|
|
197
|
+
`
|
|
198
|
+
|
|
199
|
+
const recursiveConditionalAliasNameRuntimeRunner = `const mod = require("./main.cjs");
|
|
200
|
+
|
|
201
|
+
let failures = 0;
|
|
202
|
+
const expect = (label, actual, expected) => {
|
|
203
|
+
if (actual !== expected) {
|
|
204
|
+
console.log("FAIL " + label + ": expected " + expected + ", got " + actual);
|
|
205
|
+
failures++;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const DATE = "2020-01-01T00:00:00.000Z";
|
|
210
|
+
|
|
211
|
+
// --- 1. Primitive<Node>: date-time string or (recursively) an array of those ---
|
|
212
|
+
expect("primitive top date-time", mod.isPrimitiveNode(DATE), true);
|
|
213
|
+
expect("primitive top plain string", mod.isPrimitiveNode("nope"), false);
|
|
214
|
+
expect("primitive top number", mod.isPrimitiveNode(1), false);
|
|
215
|
+
expect("primitive top null", mod.isPrimitiveNode(null), false);
|
|
216
|
+
expect("primitive top Date instance", mod.isPrimitiveNode(new Date(0)), false);
|
|
217
|
+
expect("primitive empty array", mod.isPrimitiveNode([]), true);
|
|
218
|
+
expect("primitive flat array", mod.isPrimitiveNode([DATE, DATE]), true);
|
|
219
|
+
expect("primitive flat array bad member", mod.isPrimitiveNode([DATE, "nope"]), false);
|
|
220
|
+
expect("primitive nested array", mod.isPrimitiveNode([[[DATE]], []]), true);
|
|
221
|
+
expect("primitive nested bad leaf", mod.isPrimitiveNode([[[1]]]), false);
|
|
222
|
+
expect("primitive nested Date instance", mod.isPrimitiveNode([[new Date(0)]]), false);
|
|
223
|
+
|
|
224
|
+
// depth boundary: the emitted validator recurses through one function, so a
|
|
225
|
+
// deep value must not need a deeper emit
|
|
226
|
+
let deep = [DATE];
|
|
227
|
+
for (let i = 0; i < 64; ++i) deep = [deep];
|
|
228
|
+
expect("primitive deeply nested", mod.isPrimitiveNode(deep), true);
|
|
229
|
+
let deepBad = [1];
|
|
230
|
+
for (let i = 0; i < 64; ++i) deepBad = [deepBad];
|
|
231
|
+
expect("primitive deeply nested bad leaf", mod.isPrimitiveNode(deepBad), false);
|
|
232
|
+
|
|
233
|
+
// the emitted recursive validator must not be fooled by a self-referencing value
|
|
234
|
+
const cyclic = [];
|
|
235
|
+
cyclic.push(cyclic);
|
|
236
|
+
expect("primitive cyclic value terminates", mod.isPrimitiveNode(cyclic), true);
|
|
237
|
+
|
|
238
|
+
// --- 1b. assert reports the arm, and passes what is valid ---
|
|
239
|
+
expect("assert passes", JSON.stringify(mod.assertPrimitiveNode([DATE])), JSON.stringify([DATE]));
|
|
240
|
+
let assertPath = "";
|
|
241
|
+
let assertExpected = "";
|
|
242
|
+
try {
|
|
243
|
+
mod.assertPrimitiveNode(["nope"]);
|
|
244
|
+
failures++;
|
|
245
|
+
console.log("FAIL assert should have thrown");
|
|
246
|
+
} catch (exp) {
|
|
247
|
+
assertPath = String(exp.path);
|
|
248
|
+
assertExpected = String(exp.expected);
|
|
249
|
+
}
|
|
250
|
+
expect("assert names the path", assertPath, "$input[0]");
|
|
251
|
+
// The expected string is the same name the fix has to keep readable: the arm
|
|
252
|
+
// itself, not a structural dump of the cycle it sits in.
|
|
253
|
+
expect("assert names the arm", assertExpected.includes('Format<"date-time">'), true);
|
|
254
|
+
expect("assert expectation is bounded", assertExpected.length <= 64, true);
|
|
255
|
+
|
|
256
|
+
// --- 2. the bare recursive conditional alias: string or (recursively) arrays ---
|
|
257
|
+
expect("rec top string", mod.isRec("anything"), true);
|
|
258
|
+
expect("rec top number", mod.isRec(1), false);
|
|
259
|
+
expect("rec nested array", mod.isRec([["a"], []]), true);
|
|
260
|
+
expect("rec nested bad leaf", mod.isRec([[1]]), false);
|
|
261
|
+
|
|
262
|
+
// --- 3. the recursion behind an object property ---
|
|
263
|
+
expect("output valid", mod.isOutput({ at: DATE, payload: { a: [1, "b", null, { c: true }] } }), true);
|
|
264
|
+
expect("output bad date", mod.isOutput({ at: "nope", payload: 1 }), false);
|
|
265
|
+
expect("output missing key", mod.isOutput({ payload: 1 }), false);
|
|
266
|
+
expect("output bad payload", mod.isOutput({ at: DATE, payload: undefined }), false);
|
|
267
|
+
|
|
268
|
+
// --- controls: the recursions that already worked ---
|
|
269
|
+
expect("named recursive union", mod.isNamed([[DATE]]), true);
|
|
270
|
+
expect("named recursive union bad leaf", mod.isNamed([["nope"]]), false);
|
|
271
|
+
expect("self array alias", mod.isSelfArray([[], [[]]]), true);
|
|
272
|
+
expect("self array alias bad leaf", mod.isSelfArray([1]), false);
|
|
273
|
+
expect("category valid", mod.isCategory({ name: "a", children: [{ name: "b", children: [] }] }), true);
|
|
274
|
+
expect("category bad child", mod.isCategory({ name: "a", children: [{ name: 1, children: [] }] }), false);
|
|
275
|
+
|
|
276
|
+
// --- the schema names the cycle without dumping its structural expansion ---
|
|
277
|
+
const unit = mod.schemaPrimitiveNode;
|
|
278
|
+
const keys = Object.keys(unit.components.schemas || {});
|
|
279
|
+
expect("schema has one component", keys.length, 1);
|
|
280
|
+
const key = keys[0];
|
|
281
|
+
if (key !== undefined) {
|
|
282
|
+
// The pre-fix walk could only have produced a name built from the checker's
|
|
283
|
+
// elided rendering, which runs to several hundred characters; the placeholder
|
|
284
|
+
// keeps it in the same range as an ordinary named component.
|
|
285
|
+
expect("component name is bounded", key.length <= 64, true);
|
|
286
|
+
expect("component is an array", unit.components.schemas[key].type, "array");
|
|
287
|
+
const items = unit.components.schemas[key].items;
|
|
288
|
+
const refs = (items.oneOf || []).filter((elem) => elem.$ref !== undefined);
|
|
289
|
+
expect("array items reference the component itself", refs.length === 1 && refs[0].$ref === "#/components/schemas/" + key, true);
|
|
290
|
+
expect("array items keep the date-time arm", (items.oneOf || []).some((elem) => elem.format === "date-time"), true);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// controls: a recursion reached through a declaration names every component it
|
|
294
|
+
// emits from that declaration, so the placeholder never applies to one
|
|
295
|
+
expect("category component name", Object.keys(mod.schemaCategory.components.schemas || {})[0], "ICategory");
|
|
296
|
+
const namedKeys = Object.keys(mod.schemaNamed.components.schemas || {});
|
|
297
|
+
expect(
|
|
298
|
+
"named recursion names every component from its declaration",
|
|
299
|
+
namedKeys.length !== 0 && namedKeys.every((elem) => elem.includes("Named")),
|
|
300
|
+
true,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
if (failures > 0) {
|
|
304
|
+
throw new Error(failures + " assertion(s) failed");
|
|
305
|
+
}
|
|
306
|
+
console.log("all recursive-conditional-alias cases passed");
|
|
307
|
+
`
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestShadowedTypiaModuleDeclarationDiagnostic verifies a redeclared typia call
|
|
12
|
+
// is reported instead of silently skipped (#2328).
|
|
13
|
+
//
|
|
14
|
+
// The transformer attributes a call by the file its resolved signature is
|
|
15
|
+
// declared in, which is what stops another package from claiming typia's
|
|
16
|
+
// transform. A project-local `declare module "typia"` moves that declaration out
|
|
17
|
+
// of typia's own files, so the identity test misses and the call used to pass
|
|
18
|
+
// through untransformed with no diagnostic — `ttsc.transform()` returned
|
|
19
|
+
// `type: "success"` with an empty diagnostic list, and the failure surfaced only
|
|
20
|
+
// at run time as `no transform has been configured`. The identity test stays;
|
|
21
|
+
// the silence does not.
|
|
22
|
+
//
|
|
23
|
+
// 1. Build a project with typia installed and an ambient `declare module
|
|
24
|
+
// "typia"` that redeclares `createAssert`, `json.createAssertParse`, and a
|
|
25
|
+
// `json.createAssert` that names no typia operation at all.
|
|
26
|
+
// 2. Run the transform over the project.
|
|
27
|
+
// 3. Require one diagnostic for each of the two real operations, at its call
|
|
28
|
+
// site, naming the operation as written and the shadowing file — and none
|
|
29
|
+
// for the third, whose spelling puts a root operation in a namespace.
|
|
30
|
+
func TestShadowedTypiaModuleDeclarationDiagnostic(t *testing.T) {
|
|
31
|
+
project := shadowedTypiaModuleProject(t)
|
|
32
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
33
|
+
return runTransform([]string{
|
|
34
|
+
"--cwd", project,
|
|
35
|
+
"--tsconfig", "tsconfig.json",
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
if code != 3 {
|
|
39
|
+
t.Fatalf("shadowed typia declaration should fail with code 3, got %d\nstdout=%s\nstderr=%s", code, out, errText)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var result transformProjectOutput
|
|
43
|
+
if err := json.Unmarshal([]byte(out), &result); err != nil {
|
|
44
|
+
t.Fatalf("decode transform output: %v\n%s", err, out)
|
|
45
|
+
}
|
|
46
|
+
// The root and namespaced spellings are looked up in different functor
|
|
47
|
+
// tables, so both have to be reported for the gate to be correct, and the
|
|
48
|
+
// code has to carry the namespace the user wrote.
|
|
49
|
+
expected := map[string]bool{
|
|
50
|
+
"typia.createAssert": false,
|
|
51
|
+
"typia.json.createAssertParse": false,
|
|
52
|
+
}
|
|
53
|
+
if len(result.Diagnostics) != len(expected) {
|
|
54
|
+
t.Fatalf("expected %d transform diagnostics, got %+v", len(expected), result.Diagnostics)
|
|
55
|
+
}
|
|
56
|
+
for _, diag := range result.Diagnostics {
|
|
57
|
+
seen, owned := expected[diag.Code]
|
|
58
|
+
if owned == false {
|
|
59
|
+
t.Fatalf("diagnostic code did not name a shadowed operation as written: %+v", diag)
|
|
60
|
+
}
|
|
61
|
+
if seen {
|
|
62
|
+
t.Fatalf("diagnostic code reported twice: %+v", diag)
|
|
63
|
+
}
|
|
64
|
+
expected[diag.Code] = true
|
|
65
|
+
if diag.File == nil || !strings.HasSuffix(filepath.ToSlash(*diag.File), "src/main.ts") {
|
|
66
|
+
t.Fatalf("diagnostic was not reported at the call site: %+v", diag)
|
|
67
|
+
}
|
|
68
|
+
if diag.Line != shadowedTypiaCallLines[diag.Code] {
|
|
69
|
+
t.Fatalf("diagnostic line mismatch for %s: %+v", diag.Code, diag)
|
|
70
|
+
}
|
|
71
|
+
if !strings.Contains(filepath.ToSlash(diag.MessageText), "src/typia.d.ts") {
|
|
72
|
+
t.Fatalf("diagnostic did not name the shadowing declaration: %+v", diag)
|
|
73
|
+
}
|
|
74
|
+
if !strings.Contains(diag.MessageText, "no transform was applied") {
|
|
75
|
+
t.Fatalf("diagnostic did not state the consequence: %+v", diag)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for code, seen := range expected {
|
|
79
|
+
if seen == false {
|
|
80
|
+
t.Fatalf("no diagnostic reported %s", code)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// shadowedTypiaCallLines is the 1-based line of each shadowed call in
|
|
86
|
+
// shadowedTypiaCallSource, so a regression that reports the whole file or the
|
|
87
|
+
// wrong statement cannot pass on the file name alone.
|
|
88
|
+
var shadowedTypiaCallLines = map[string]int{
|
|
89
|
+
"typia.createAssert": 7,
|
|
90
|
+
"typia.json.createAssertParse": 8,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func shadowedTypiaModuleProject(t *testing.T) string {
|
|
94
|
+
t.Helper()
|
|
95
|
+
dir := t.TempDir()
|
|
96
|
+
src := filepath.Join(dir, "src")
|
|
97
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
98
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
99
|
+
}
|
|
100
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(transformDiagnosticTSConfig), 0o644); err != nil {
|
|
101
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
102
|
+
}
|
|
103
|
+
// typia is installed as well as shadowed, matching the reporter's project: an
|
|
104
|
+
// ambient module declaration outranks node resolution, and the fixture is
|
|
105
|
+
// only faithful if there is something for it to outrank.
|
|
106
|
+
ttscTypiaTestWriteFactoryStub(t, dir)
|
|
107
|
+
for name, body := range map[string]string{
|
|
108
|
+
"typia.d.ts": shadowedTypiaAmbientDeclaration,
|
|
109
|
+
"main.ts": shadowedTypiaCallSource,
|
|
110
|
+
} {
|
|
111
|
+
if err := os.WriteFile(filepath.Join(src, name), []byte(body), 0o644); err != nil {
|
|
112
|
+
t.Fatalf("write fixture %s: %v", name, err)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return dir
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const shadowedTypiaAmbientDeclaration = `declare module "typia" {
|
|
119
|
+
type ErrorFactory = (props: { method: string }) => Error;
|
|
120
|
+
export function createAssert<T>(
|
|
121
|
+
errorFactory?: ErrorFactory,
|
|
122
|
+
): (input: unknown, errorFactory?: ErrorFactory) => T;
|
|
123
|
+
export namespace json {
|
|
124
|
+
function createAssertParse<T>(
|
|
125
|
+
errorFactory?: ErrorFactory,
|
|
126
|
+
): (input: string, errorFactory?: ErrorFactory) => T;
|
|
127
|
+
function createAssert<T>(
|
|
128
|
+
errorFactory?: ErrorFactory,
|
|
129
|
+
): (input: unknown, errorFactory?: ErrorFactory) => T;
|
|
130
|
+
}
|
|
131
|
+
const typia: {
|
|
132
|
+
createAssert<T>(
|
|
133
|
+
errorFactory?: ErrorFactory,
|
|
134
|
+
): (input: unknown, errorFactory?: ErrorFactory) => T;
|
|
135
|
+
json: typeof json;
|
|
136
|
+
};
|
|
137
|
+
export default typia;
|
|
138
|
+
}
|
|
139
|
+
`
|
|
140
|
+
|
|
141
|
+
// The third call spells a root operation inside a namespace. typia has no
|
|
142
|
+
// `json.createAssert`, so nothing was shadowed and nothing may be reported.
|
|
143
|
+
const shadowedTypiaCallSource = `import typia from "typia";
|
|
144
|
+
|
|
145
|
+
export interface User {
|
|
146
|
+
id: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const parseUser = typia.createAssert<User>();
|
|
150
|
+
export const parseUserJson = typia.json.createAssertParse<User>();
|
|
151
|
+
export const bogus = typia.json.createAssert<User>();
|
|
152
|
+
`
|
|
@@ -20,8 +20,8 @@ import (
|
|
|
20
20
|
nativetransform "github.com/samchon/typia/packages/typia/native/transform"
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
-
// runTransform is the source-to-source host the ttsc transform
|
|
24
|
-
//
|
|
23
|
+
// runTransform is the source-to-source host that drives the ttsc transform
|
|
24
|
+
// stage. Unlike `build` (which emits .js), it returns
|
|
25
25
|
// transformed TypeScript: typia's per-file node transformer runs in an
|
|
26
26
|
// EmitContext and the result SourceFile is printed back as TS (no JS script
|
|
27
27
|
// transformers), so injected namespace imports stay as ES imports the caller
|
|
@@ -3,7 +3,6 @@ package main
|
|
|
3
3
|
import (
|
|
4
4
|
"bytes"
|
|
5
5
|
"os"
|
|
6
|
-
"os/exec"
|
|
7
6
|
"path/filepath"
|
|
8
7
|
"runtime"
|
|
9
8
|
"sort"
|
|
@@ -11,16 +10,81 @@ import (
|
|
|
11
10
|
"testing"
|
|
12
11
|
)
|
|
13
12
|
|
|
13
|
+
// ttscTypiaTestTypecheck requires a fixture project to be free of TypeScript
|
|
14
|
+
// errors, which is what makes an `@ts-expect-error` in a fixture load-bearing.
|
|
15
|
+
//
|
|
16
|
+
// It runs the build command in no-emit mode with the rewrite disabled, so the
|
|
17
|
+
// only diagnostics it can report are the program's own. It used to shell out to
|
|
18
|
+
// `pnpm exec ttsc --noEmit -p tsconfig.json`, which reported nothing and exited
|
|
19
|
+
// 0 for a fixture under `.tmp-ttsc-typia-tests` — `const value: number = "bad"`
|
|
20
|
+
// passed — so every caller was asserting against an oracle that could not fail.
|
|
21
|
+
// `runBuild` is the same program these tests already drive, and
|
|
22
|
+
// build_reports_semantic_diagnostics_test.go pins that it surfaces semantic
|
|
23
|
+
// diagnostics as status 2.
|
|
14
24
|
func ttscTypiaTestTypecheck(t *testing.T, project string) {
|
|
15
25
|
t.Helper()
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
27
|
+
return runBuild([]string{
|
|
28
|
+
"--cwd", project,
|
|
29
|
+
"--tsconfig", "tsconfig.json",
|
|
30
|
+
"--noEmit",
|
|
31
|
+
"--rewrite-mode", "none",
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
if code != 0 {
|
|
35
|
+
t.Fatalf("fixture typecheck failed: code=%d\nstdout=%s\nstderr=%s", code, out, errText)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ttscTypiaTestWriteFactoryStub installs a typia whose declaration file sits at
|
|
40
|
+
// the path the transformer's identity test accepts, so a fixture that also
|
|
41
|
+
// carries an ambient or augmenting `declare module "typia"` differs from a
|
|
42
|
+
// plain one only in that declaration.
|
|
43
|
+
func ttscTypiaTestWriteFactoryStub(t *testing.T, project string) {
|
|
44
|
+
t.Helper()
|
|
45
|
+
root := filepath.Join(project, "node_modules", "typia")
|
|
46
|
+
lib := filepath.Join(root, "lib")
|
|
47
|
+
if err := os.MkdirAll(lib, 0o755); err != nil {
|
|
48
|
+
t.Fatalf("mkdir typia stub: %v", err)
|
|
49
|
+
}
|
|
50
|
+
files := map[string]string{
|
|
51
|
+
"package.json": `{
|
|
52
|
+
"name": "typia",
|
|
53
|
+
"version": "0.0.0-test",
|
|
54
|
+
"main": "./lib/module.js",
|
|
55
|
+
"types": "./lib/module.d.ts",
|
|
56
|
+
"exports": {
|
|
57
|
+
".": {
|
|
58
|
+
"types": "./lib/module.d.ts",
|
|
59
|
+
"default": "./lib/module.js"
|
|
60
|
+
},
|
|
61
|
+
"./lib/transform": "./lib/transform.js"
|
|
62
|
+
},
|
|
63
|
+
"ttsc": {
|
|
64
|
+
"plugin": { "transform": "typia/lib/transform" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
filepath.Join("lib", "module.d.ts"): `declare namespace typia {
|
|
69
|
+
function createAssert<T>(): (input: unknown) => T;
|
|
70
|
+
function createIs<T>(): (input: unknown) => input is T;
|
|
71
|
+
}
|
|
72
|
+
declare const typia: {
|
|
73
|
+
createAssert: typeof typia.createAssert;
|
|
74
|
+
createIs: typeof typia.createIs;
|
|
75
|
+
};
|
|
76
|
+
export default typia;
|
|
77
|
+
export declare function createAssert<T>(): (input: unknown) => T;
|
|
78
|
+
export declare function createIs<T>(): (input: unknown) => input is T;
|
|
79
|
+
`,
|
|
80
|
+
filepath.Join("lib", "module.js"): "exports.createAssert = () => () => undefined;\nexports.createIs = () => () => false;\n",
|
|
81
|
+
filepath.Join("lib", "transform.js"): "module.exports = {};\n",
|
|
82
|
+
filepath.Join("lib", "transform.d.ts"): "export {};\n",
|
|
19
83
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
84
|
+
for name, body := range files {
|
|
85
|
+
if err := os.WriteFile(filepath.Join(root, name), []byte(body), 0o644); err != nil {
|
|
86
|
+
t.Fatalf("write typia stub %s: %v", name, err)
|
|
87
|
+
}
|
|
24
88
|
}
|
|
25
89
|
}
|
|
26
90
|
|
|
@@ -61,14 +125,14 @@ func ttscTypiaTestCapture(run func() int) (string, string, int) {
|
|
|
61
125
|
func ttscTypiaTestWriteCommonRuntimeStubs(t *testing.T, runtimeDir string) {
|
|
62
126
|
t.Helper()
|
|
63
127
|
files := map[string]string{
|
|
64
|
-
"typia-stub.cjs":
|
|
65
|
-
"validate-report-stub.cjs":
|
|
66
|
-
"standard-schema-stub.cjs":
|
|
67
|
-
"assert-guard-stub.cjs":
|
|
68
|
-
"functional-error-stub.cjs":
|
|
69
|
-
"access-expression-stub.cjs":
|
|
70
|
-
"string-length-stub.cjs":
|
|
71
|
-
"notation-stub.cjs":
|
|
128
|
+
"typia-stub.cjs": "module.exports = {};\n",
|
|
129
|
+
"validate-report-stub.cjs": ttscTypiaTestValidateReportStub,
|
|
130
|
+
"standard-schema-stub.cjs": ttscTypiaTestStandardSchemaStub,
|
|
131
|
+
"assert-guard-stub.cjs": ttscTypiaTestAssertGuardStub,
|
|
132
|
+
"functional-error-stub.cjs": ttscTypiaTestFunctionalErrorStub,
|
|
133
|
+
"access-expression-stub.cjs": ttscTypiaTestAccessExpressionStub,
|
|
134
|
+
"string-length-stub.cjs": ttscTypiaTestStringLengthStub,
|
|
135
|
+
"notation-stub.cjs": ttscTypiaTestNotationStub,
|
|
72
136
|
"json-stringify-array-stub.cjs": ttscTypiaTestJsonStringifyArrayStub,
|
|
73
137
|
"json-stringify-property-stub.cjs": ttscTypiaTestJsonStringifyPropertyStub,
|
|
74
138
|
"json-stringify-element-stub.cjs": ttscTypiaTestJsonStringifyElementStub,
|
|
@@ -160,9 +224,20 @@ const ttscTypiaTestValidateReportStub = `module.exports._validateReport = (array
|
|
|
160
224
|
const ttscTypiaTestStandardSchemaStub = `module.exports._createStandardSchema = (validate) => validate;
|
|
161
225
|
`
|
|
162
226
|
|
|
227
|
+
// A double for packages/typia/src/internal/_assertGuard.ts that takes the same
|
|
228
|
+
// branches: `exceptionable === true` and a callable-factory requirement. The
|
|
229
|
+
// generated function's second parameter is caller-supplied, so a pointwise
|
|
230
|
+
// hand-off (`rows.map(assertUser)`) fills it with the element index; testing
|
|
231
|
+
// truthiness here would let this stub report `factory is not a function` for a
|
|
232
|
+
// case the shipped helper handles. The fallback error is deliberately not a
|
|
233
|
+
// TypeGuardError — a stub cannot import typia — so cases assert `props`
|
|
234
|
+
// members rather than the shipped message format.
|
|
163
235
|
const ttscTypiaTestAssertGuardStub = `module.exports._assertGuard = (exceptionable, props, factory) => {
|
|
164
|
-
if (exceptionable) {
|
|
165
|
-
const error =
|
|
236
|
+
if (exceptionable === true) {
|
|
237
|
+
const error =
|
|
238
|
+
typeof factory === "function"
|
|
239
|
+
? factory(props)
|
|
240
|
+
: Object.assign(new Error(props.expected), props);
|
|
166
241
|
throw error;
|
|
167
242
|
}
|
|
168
243
|
return false;
|
|
@@ -217,6 +292,9 @@ const ttscTypiaTestRandomStringStub = `module.exports._randomString = (props) =>
|
|
|
217
292
|
};
|
|
218
293
|
`
|
|
219
294
|
|
|
295
|
+
// A double for packages/typia/src/internal/_stringLength.ts: the string
|
|
296
|
+
// iterator walks code points, so a surrogate pair counts once, which is what
|
|
297
|
+
// `MinLength` and `MaxLength` now mean.
|
|
220
298
|
const ttscTypiaTestStringLengthStub = `module.exports._stringLength = (value) => {
|
|
221
299
|
let count = 0;
|
|
222
300
|
for (const _ of value) ++count;
|
|
@@ -45,7 +45,7 @@ var FormatCheatSheet = map[string]string{
|
|
|
45
45
|
"email": "/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test($input)",
|
|
46
46
|
"hostname": "/^(?=.{1,253}\\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\\.?$/i.test($input)",
|
|
47
47
|
"idn-email": "/^(([^<>()[\\]\\.,;:\\s@\\\"]+(\\.[^<>()[\\]\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@(([^<>()[\\]\\.,;:\\s@\\\"]+\\.)+[^<>()[\\]\\.,;:\\s@\\\"]{2,})$/i.test($input)",
|
|
48
|
-
"idn-hostname": "/^([a-z0-9\\u00a1-\\
|
|
48
|
+
"idn-hostname": "/^(?=.{1,253}\\.?$)[a-z0-9\\u00a1-\\uffff](?:[a-z0-9\\u00a1-\\uffff-]{0,61}[a-z0-9\\u00a1-\\uffff])?(?:\\.[a-z0-9\\u00a1-\\uffff](?:[a-z0-9\\u00a1-\\uffff-]{0,61}[a-z0-9\\u00a1-\\uffff])?)*\\.?$/i.test($input)",
|
|
49
49
|
"iri": `(() => !/[\u0000-\u0020\u007f-\u009f\ud800-\udfff"<>\\^\x60{|}]/u.test($input) && !/%(?![0-9A-Fa-f]{2})/u.test($input) && /^[A-Za-z][A-Za-z0-9+.-]*:/u.test($input))()`,
|
|
50
50
|
"iri-reference": `(() => { if (/[\u0000-\u0020\u007f-\u009f\ud800-\udfff"<>\\^\x60{|}]/u.test($input) || /%(?![0-9A-Fa-f]{2})/u.test($input)) return false; const colon = $input.indexOf(":"); const boundary = $input.search(/[/?#]/u); return colon === -1 || (boundary !== -1 && boundary < colon) || /^[A-Za-z][A-Za-z0-9+.-]*:/u.test($input); })()`,
|
|
51
51
|
"ipv4": "/^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$/.test($input)",
|
|
@@ -56,7 +56,7 @@ var FormatCheatSheet = map[string]string{
|
|
|
56
56
|
"url": "/^(?:https?|ftp):\\/\\/(?:[^\\s/?#@]+(?::[^\\s/?#@]*)?@)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?=.{1,253}(?::\\d{2,5})?(?:\\/[^\\s]*)?$)(?:[a-z0-9\\u00a1-\\uffff](?:[-a-z0-9\\u00a1-\\uffff]{0,61}[a-z0-9\\u00a1-\\uffff])?\\.)+(?:[a-z\\u00a1-\\uffff](?:[-a-z0-9\\u00a1-\\uffff]{0,61}[a-z0-9\\u00a1-\\uffff])))(?::\\d{2,5})?(?:\\/[^\\s]*)?$/iu.test($input)",
|
|
57
57
|
"date-time": `(() => { const match = /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])[Tt]((?:[01][0-9]|2[0-3])):([0-5][0-9]):([0-5][0-9]|60)(?:\.[0-9]+)?(?:[Zz]|([+-])((?:[01][0-9]|2[0-3])):([0-5][0-9]))$/.exec($input); if (match === null) return false; const year = Number(match[1]); const month = Number(match[2]); const day = Number(match[3]); if (day > [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] + (month === 2 && (year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)) ? 1 : 0)) return false; if (match[6] !== "60") return true; const instant = new Date(0); instant.setUTCFullYear(year, month - 1, day); instant.setUTCHours(Number(match[4]), Number(match[5]), 0, 0); const offset = match[7] === undefined ? 0 : (match[7] === "+" ? 1 : -1) * (Number(match[8]) * 60 + Number(match[9])); instant.setUTCMinutes(instant.getUTCMinutes() - offset); return instant.getUTCHours() === 23 && instant.getUTCMinutes() === 59 && ((instant.getUTCMonth() === 5 && instant.getUTCDate() === 30) || (instant.getUTCMonth() === 11 && instant.getUTCDate() === 31)); })()`,
|
|
58
58
|
"date": `(() => { const match = /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/.exec($input); if (match === null) return false; const year = Number(match[1]); const month = Number(match[2]); const day = Number(match[3]); return day <= [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] + (month === 2 && (year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)) ? 1 : 0); })()`,
|
|
59
|
-
"time":
|
|
59
|
+
"time": `(() => { const match = /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(?:\.[0-9]+)?(?:[Zz]|([+-])((?:[01][0-9]|2[0-3])):([0-5][0-9]))$/.exec($input); if (match === null) return false; if (match[3] !== "60") return true; const offset = match[4] === undefined ? 0 : (match[4] === "+" ? 1 : -1) * (Number(match[5]) * 60 + Number(match[6])); const minutes = Number(match[1]) * 60 + Number(match[2]) - offset; return ((minutes % 1440) + 1440) % 1440 === 1439; })()`,
|
|
60
60
|
"duration": "/^P(?!$)((\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+S)?)?|(\\d+W)?)$/.test($input)",
|
|
61
61
|
"json-pointer": "/^(?:\\/(?:[^~/]|~0|~1)*)*$/.test($input)",
|
|
62
62
|
"relative-json-pointer": "/^(?:0|[1-9][0-9]*)(?:#|(?:\\/(?:[^~/]|~0|~1)*)*)$/.test($input)",
|