typia 13.2.0 → 13.3.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/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/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/local_function_named_like_typia_operation_transform_test.go +185 -0
- 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/template_literal_type_tags_transform_test.go +1 -3
- package/native/cmd/ttsc-typia/transform_helpers_test.go +92 -26
- package/native/core/factories/FormatCheatSheet.go +2 -2
- package/native/core/factories/MetadataCommentTagFactory.go +26 -11
- 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_coverage_test.go +2 -2
- package/native/core/programmers/RandomProgrammer.go +5 -2
- package/native/core/schemas/metadata/MetadataCollection.go +47 -5
- package/native/transform/CallExpressionTransformer.go +167 -0
- package/package.json +3 -3
- 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/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
|
@@ -70,34 +70,11 @@ func metadata_type_full_name(
|
|
|
70
70
|
return result
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
func metadata_type_symbol_name(checker *nativechecker.Checker, typ *nativechecker.Type) string {
|
|
79
|
-
if checker == nil || typ == nil {
|
|
80
|
-
return ""
|
|
81
|
-
}
|
|
82
|
-
symbol := typ.Symbol()
|
|
83
|
-
if symbol == nil {
|
|
84
|
-
return checker.TypeToString(typ)
|
|
85
|
-
}
|
|
86
|
-
name := metadata_symbol_name(symbol)
|
|
87
|
-
generic := metadata_get_type_arguments(checker, typ)
|
|
88
|
-
if len(generic) == 0 {
|
|
89
|
-
return name
|
|
90
|
-
}
|
|
91
|
-
if name == "Promise" {
|
|
92
|
-
return metadata_type_symbol_name(checker, generic[0])
|
|
93
|
-
}
|
|
94
|
-
names := make([]string, 0, len(generic))
|
|
95
|
-
for _, child := range generic {
|
|
96
|
-
names = append(names, metadata_type_symbol_name(checker, child))
|
|
97
|
-
}
|
|
98
|
-
return name + "<" + strings.Join(names, ", ") + ">"
|
|
99
|
-
}
|
|
100
|
-
|
|
73
|
+
// metadata_type_symbol_base_name derives the name from the type's own *symbol*,
|
|
74
|
+
// never from checker.TypeToString. The Map / Set / native guards test this name
|
|
75
|
+
// against a prefix, and an alias chain (e.g. `type MAP = Map<K, V>`) makes
|
|
76
|
+
// TypeToString print the alias name ("MapAlias.MAP<...>") instead of "Map<...>",
|
|
77
|
+
// which would defeat those guards.
|
|
101
78
|
func metadata_type_symbol_base_name(typ *nativechecker.Type) string {
|
|
102
79
|
if typ == nil {
|
|
103
80
|
return ""
|
|
@@ -124,8 +124,8 @@ func TestMetadataCommentTagFactoryCoverage(t *testing.T) {
|
|
|
124
124
|
if len(reports) != decimalReportCount || len(decimal["number"]) != 1 || len(decimal["bigint"]) != 0 {
|
|
125
125
|
t.Fatalf("decimal multipleOf must remain a number-only JSDoc tag: %#v, reports=%#v", decimal, reports[decimalReportCount:])
|
|
126
126
|
}
|
|
127
|
-
if decimal["number"][0].Validate != "$
|
|
128
|
-
t.Fatalf("decimal multipleOf must
|
|
127
|
+
if decimal["number"][0].Validate != "$input % 0.01 === 0" {
|
|
128
|
+
t.Fatalf("decimal multipleOf must spell the same remainder check the MultipleOf type tag declares: %s", decimal["number"][0].Validate)
|
|
129
129
|
}
|
|
130
130
|
invalidBefore := len(reports)
|
|
131
131
|
_ = metadataCommentTagFactory_parse_number(struct {
|
|
@@ -1442,8 +1442,11 @@ func randomProgrammer_is_typed_array(name string) bool {
|
|
|
1442
1442
|
// `maximum` comment tags on a number-typed JSON schema, and `number` cannot
|
|
1443
1443
|
// represent 2**63 - 1 or 2**64 - 1 -- both arrive rounded up to the next power of
|
|
1444
1444
|
// two. That costs nothing here: the generator only picks a value from the range,
|
|
1445
|
-
// and the TypedArray constructor wraps whatever it is handed.
|
|
1446
|
-
// exactness instead, in
|
|
1445
|
+
// and the TypedArray constructor wraps whatever it is handed. On the `number`
|
|
1446
|
+
// path validation owns exactness instead, in _isTypeInt64 and _isTypeUint64. The
|
|
1447
|
+
// `bigint` path owns none: Type<"int64"> declares no check and Type<"uint64">
|
|
1448
|
+
// only a lower bound, so that neither declaration names a runtime helper an
|
|
1449
|
+
// older `typia` cannot resolve (#2330). #2338 owns restoring the bound.
|
|
1447
1450
|
func randomProgrammer_typed_array_range(name string) (string, string, string) {
|
|
1448
1451
|
switch name {
|
|
1449
1452
|
case "Uint8Array", "Uint8ClampedArray":
|
|
@@ -810,11 +810,31 @@ var metadataCollection_replacers = []metadataCollection_replacer{
|
|
|
810
810
|
}
|
|
811
811
|
|
|
812
812
|
func metadataCollection_getFullName(checker *nativechecker.Checker, typ *nativechecker.Type) string {
|
|
813
|
+
return metadataCollection_getFullNameOf(checker, typ, nil)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// metadataCollection_getFullNameOf carries the types whose name is still being
|
|
817
|
+
// composed on the current path. A recursive type graph reaches the same *Type
|
|
818
|
+
// again before its name is finished: `type Rec<T> = T extends Date ? string : T
|
|
819
|
+
// extends (infer U)[] ? Rec<U>[] : never` instantiated at `Date | Node[]`
|
|
820
|
+
// resolves to the union `string | Rec<Node>[]`, whose array member is a
|
|
821
|
+
// reference whose sole type argument is that very union. Walking union member ->
|
|
822
|
+
// array reference -> type argument returns to the union, so the unguarded walk
|
|
823
|
+
// never terminates and overflows the plugin's stack instead of naming the type
|
|
824
|
+
// (#2331). A type reached a second time on one path is therefore named by its
|
|
825
|
+
// own symbol with its arguments left unexpanded -- the recursion's placeholder,
|
|
826
|
+
// `Array<string | Array>` for the example above -- and a symbol-less one by the
|
|
827
|
+
// anonymous marker the collection already allocates unique names from. Expanding
|
|
828
|
+
// the checker's elided rendering there instead would name every such component
|
|
829
|
+
// after a several-hundred-character structural dump.
|
|
830
|
+
func metadataCollection_getFullNameOf(
|
|
831
|
+
checker *nativechecker.Checker,
|
|
832
|
+
typ *nativechecker.Type,
|
|
833
|
+
visiting map[*nativechecker.Type]bool,
|
|
834
|
+
) string {
|
|
813
835
|
if checker == nil || typ == nil {
|
|
814
836
|
return "__type"
|
|
815
837
|
}
|
|
816
|
-
rendered := metadataCollection_sanitizeName(checker.TypeToString(typ))
|
|
817
|
-
|
|
818
838
|
// Mirror TypeFactory.getFullName: prefer the alias symbol, then the type's
|
|
819
839
|
// own symbol. getTypeNameSymbol already returns t.alias.symbol first (the
|
|
820
840
|
// alias symbol), so a non-nil result that differs from typ.Symbol() means the
|
|
@@ -826,6 +846,13 @@ func metadataCollection_getFullName(checker *nativechecker.Checker, typ *nativec
|
|
|
826
846
|
if symbol == nil {
|
|
827
847
|
symbol = rawSymbol
|
|
828
848
|
}
|
|
849
|
+
if visiting[typ] {
|
|
850
|
+
if symbol != nil {
|
|
851
|
+
return metadataCollection_getName(symbol)
|
|
852
|
+
}
|
|
853
|
+
return "__type"
|
|
854
|
+
}
|
|
855
|
+
rendered := metadataCollection_sanitizeName(checker.TypeToString(typ))
|
|
829
856
|
if symbol == nil {
|
|
830
857
|
if typ.IsUnion() || typ.IsIntersection() {
|
|
831
858
|
joiner := " | "
|
|
@@ -834,9 +861,11 @@ func metadataCollection_getFullName(checker *nativechecker.Checker, typ *nativec
|
|
|
834
861
|
}
|
|
835
862
|
children := typ.Types()
|
|
836
863
|
names := make([]string, 0, len(children))
|
|
864
|
+
visiting = metadataCollection_markVisiting(visiting, typ)
|
|
837
865
|
for _, child := range children {
|
|
838
|
-
names = append(names,
|
|
866
|
+
names = append(names, metadataCollection_getFullNameOf(checker, child, visiting))
|
|
839
867
|
}
|
|
868
|
+
delete(visiting, typ)
|
|
840
869
|
return strings.Join(names, joiner)
|
|
841
870
|
}
|
|
842
871
|
if rendered != "" {
|
|
@@ -864,16 +893,29 @@ func metadataCollection_getFullName(checker *nativechecker.Checker, typ *nativec
|
|
|
864
893
|
}
|
|
865
894
|
return name
|
|
866
895
|
}
|
|
896
|
+
visiting = metadataCollection_markVisiting(visiting, typ)
|
|
897
|
+
defer delete(visiting, typ)
|
|
867
898
|
if name == "Promise" {
|
|
868
|
-
return
|
|
899
|
+
return metadataCollection_getFullNameOf(checker, generic[0], visiting)
|
|
869
900
|
}
|
|
870
901
|
names := make([]string, 0, len(generic))
|
|
871
902
|
for _, child := range generic {
|
|
872
|
-
names = append(names,
|
|
903
|
+
names = append(names, metadataCollection_getFullNameOf(checker, child, visiting))
|
|
873
904
|
}
|
|
874
905
|
return name + "<" + strings.Join(names, ", ") + ">"
|
|
875
906
|
}
|
|
876
907
|
|
|
908
|
+
func metadataCollection_markVisiting(
|
|
909
|
+
visiting map[*nativechecker.Type]bool,
|
|
910
|
+
typ *nativechecker.Type,
|
|
911
|
+
) map[*nativechecker.Type]bool {
|
|
912
|
+
if visiting == nil {
|
|
913
|
+
visiting = map[*nativechecker.Type]bool{}
|
|
914
|
+
}
|
|
915
|
+
visiting[typ] = true
|
|
916
|
+
return visiting
|
|
917
|
+
}
|
|
918
|
+
|
|
877
919
|
func metadataCollection_getName(symbol *nativeast.Symbol) string {
|
|
878
920
|
if symbol == nil || len(symbol.Declarations) == 0 || symbol.Declarations[0].Parent == nil {
|
|
879
921
|
return "__type"
|
|
@@ -63,6 +63,7 @@ func (callExpressionTransformerNamespace) Transform(props CallExpressionTransfor
|
|
|
63
63
|
location, _ := filepath.Abs(sourceFile.FileName())
|
|
64
64
|
module, ok := callExpressionTransformer_targetModule(location)
|
|
65
65
|
if ok == false {
|
|
66
|
+
callExpressionTransformer_rejectShadowedTypia(props, location)
|
|
66
67
|
return props.Expression.AsNode()
|
|
67
68
|
}
|
|
68
69
|
typ := props.Context.Checker.GetTypeAtLocation(declaration)
|
|
@@ -127,6 +128,172 @@ func callExpressionTransformer_targetModule(location string) (string, bool) {
|
|
|
127
128
|
return "", false
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
// callExpressionTransformer_rejectShadowedTypia raises a diagnostic when a call
|
|
132
|
+
// the file-path identity test rejected is nevertheless a typia call the author
|
|
133
|
+
// expects to be transformed, and names the file that declared it instead.
|
|
134
|
+
//
|
|
135
|
+
// Attribution by declaration file is deliberate: accepting a callee declared
|
|
136
|
+
// anywhere else would let any package claim typia's transform, which is the
|
|
137
|
+
// boundary default_library_spoof_native_identity_transform_test.go and
|
|
138
|
+
// lib_replacement_native_identity_transform_test.go protect. That check is not
|
|
139
|
+
// relaxed here. Silence is what needs correcting. A project-local `declare
|
|
140
|
+
// module "typia"` moves the resolved signature's declaration out of typia's own
|
|
141
|
+
// files, so the call is left untransformed with no diagnostic at all and the
|
|
142
|
+
// failure surfaces only at run time as `no transform has been configured`
|
|
143
|
+
// (samchon/typia#2328).
|
|
144
|
+
//
|
|
145
|
+
// Three conditions have to hold together, so an unrelated local function that
|
|
146
|
+
// happens to share a name with a typia operation cannot trip this:
|
|
147
|
+
//
|
|
148
|
+
// 1. the callee spells an operation this transformer owns, in the namespace it
|
|
149
|
+
// actually lives in — `typia.json.assertParse`, never `typia.assertParse`;
|
|
150
|
+
// 2. the root of the callee expression resolves to an import binding; and
|
|
151
|
+
// 3. that import's module specifier is typia's own.
|
|
152
|
+
//
|
|
153
|
+
// Known gap: an alias chain is not followed, so a project that re-exports typia
|
|
154
|
+
// through its own barrel and then shadows it is still silent. The specifier of
|
|
155
|
+
// the import the call actually reads is the only evidence this collects.
|
|
156
|
+
func callExpressionTransformer_rejectShadowedTypia(props CallExpressionTransformer_TransformProps, location string) {
|
|
157
|
+
code, ok := callExpressionTransformer_calleeOperation(props.Expression.Expression)
|
|
158
|
+
if ok == false {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
root := callExpressionTransformer_calleeRoot(props.Expression.Expression)
|
|
162
|
+
if root == nil || root.Kind != shimast.KindIdentifier {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
symbol := props.Context.Checker.GetSymbolAtLocation(root)
|
|
166
|
+
if symbol == nil || symbol.Flags&shimast.SymbolFlagsAlias == 0 {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
for _, declaration := range symbol.Declarations {
|
|
170
|
+
if callExpressionTransformer_importsTypia(declaration) == false {
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
// State only what was observed. The declaration file is the fact; a
|
|
174
|
+
// `declare module "typia"` is the usual cause but not the only way to move
|
|
175
|
+
// a declaration, and naming it as the cause would misdirect anyone whose
|
|
176
|
+
// build got here another way.
|
|
177
|
+
panic(nativecontext.NewTransformerError(nativecontext.TransformerError_IProps{
|
|
178
|
+
Code: code,
|
|
179
|
+
Message: "this call was declared in " +
|
|
180
|
+
filepath.ToSlash(location) +
|
|
181
|
+
", not in typia's own module, so no transform was applied. A project-local `declare module \"typia\"` is the usual cause; remove it, or give the redeclaration another module name.",
|
|
182
|
+
}))
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// callExpressionTransformer_calleeOperation answers whether a callee expression
|
|
187
|
+
// spells one of this transformer's operations, and returns the diagnostic code
|
|
188
|
+
// for it — the written spelling, qualified as `typia.json.assertParse`.
|
|
189
|
+
//
|
|
190
|
+
// The namespace is part of the spelling. `typia.json.assertParse` names the
|
|
191
|
+
// `json` table, while `typia.createAssert` and a bare named import name the root
|
|
192
|
+
// `module` table; a root-level `typia.assertParse` matches nothing, because that
|
|
193
|
+
// operation does not live there. Matching a flat union of every table's names
|
|
194
|
+
// instead would report a module augmentation that adds an unrelated `parse` or
|
|
195
|
+
// `schema` to typia, which is a build that works today.
|
|
196
|
+
//
|
|
197
|
+
// Known gap: taking the namespace from the spelling means a renamed binding
|
|
198
|
+
// (`import { json as j } from "typia"; j.assertParse(...)`) is not recognized.
|
|
199
|
+
func callExpressionTransformer_calleeOperation(callee *shimast.Node) (string, bool) {
|
|
200
|
+
method := ""
|
|
201
|
+
first := true
|
|
202
|
+
// Segments to the left of the method, nearest first: `typia.json.assertParse`
|
|
203
|
+
// collects ["json", "typia"].
|
|
204
|
+
parts := []string{}
|
|
205
|
+
for current := callee; current != nil; {
|
|
206
|
+
if len(parts) > 2 {
|
|
207
|
+
return "", false
|
|
208
|
+
}
|
|
209
|
+
segment := ""
|
|
210
|
+
switch current.Kind {
|
|
211
|
+
case shimast.KindPropertyAccessExpression:
|
|
212
|
+
access := current.AsPropertyAccessExpression()
|
|
213
|
+
name := access.Name()
|
|
214
|
+
if name == nil || name.Kind != shimast.KindIdentifier {
|
|
215
|
+
return "", false
|
|
216
|
+
}
|
|
217
|
+
segment = name.Text()
|
|
218
|
+
current = access.Expression
|
|
219
|
+
case shimast.KindIdentifier:
|
|
220
|
+
segment = current.Text()
|
|
221
|
+
current = nil
|
|
222
|
+
default:
|
|
223
|
+
return "", false
|
|
224
|
+
}
|
|
225
|
+
if first {
|
|
226
|
+
method = segment
|
|
227
|
+
first = false
|
|
228
|
+
} else {
|
|
229
|
+
parts = append(parts, segment)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if method == "" || len(parts) > 2 {
|
|
233
|
+
return "", false
|
|
234
|
+
}
|
|
235
|
+
functors := callExpressionTransformer_FUNCTORS()
|
|
236
|
+
// `typia.json.assertParse` and `json.assertParse` both name the `json` table
|
|
237
|
+
// through the segment directly left of the method.
|
|
238
|
+
if len(parts) != 0 {
|
|
239
|
+
if _, found := functors[parts[0]][method]; found {
|
|
240
|
+
return "typia." + parts[0] + "." + method, true
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// `typia.createAssert` and a bare `createAssert` name the root table. The
|
|
244
|
+
// qualifier, when present, is the imported binding rather than a namespace.
|
|
245
|
+
if len(parts) <= 1 {
|
|
246
|
+
if _, found := functors["module"][method]; found {
|
|
247
|
+
return "typia." + method, true
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return "", false
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// callExpressionTransformer_calleeRoot walks a property-access chain down to the
|
|
254
|
+
// expression it starts from, which is the binding that has to come from typia.
|
|
255
|
+
func callExpressionTransformer_calleeRoot(callee *shimast.Node) *shimast.Node {
|
|
256
|
+
current := callee
|
|
257
|
+
for current != nil && current.Kind == shimast.KindPropertyAccessExpression {
|
|
258
|
+
current = current.AsPropertyAccessExpression().Expression
|
|
259
|
+
}
|
|
260
|
+
return current
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// callExpressionTransformer_importsTypia answers whether an alias declaration
|
|
264
|
+
// belongs to an import whose module specifier is typia's own. Both `import ...
|
|
265
|
+
// from "typia"` and `import typia = require("typia")` are matched; the walk
|
|
266
|
+
// stops at the source file so a local declaration can never reach an unrelated
|
|
267
|
+
// import above it.
|
|
268
|
+
func callExpressionTransformer_importsTypia(declaration *shimast.Node) bool {
|
|
269
|
+
for node := declaration; node != nil; node = node.Parent {
|
|
270
|
+
switch node.Kind {
|
|
271
|
+
case shimast.KindImportDeclaration:
|
|
272
|
+
return callExpressionTransformer_isTypiaSpecifier(node.ModuleSpecifier())
|
|
273
|
+
case shimast.KindImportEqualsDeclaration:
|
|
274
|
+
reference := node.AsImportEqualsDeclaration().ModuleReference
|
|
275
|
+
if reference == nil || reference.Kind != shimast.KindExternalModuleReference {
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
278
|
+
return callExpressionTransformer_isTypiaSpecifier(reference.AsExternalModuleReference().Expression)
|
|
279
|
+
case shimast.KindSourceFile:
|
|
280
|
+
return false
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// callExpressionTransformer_isTypiaSpecifier accepts typia's own package name
|
|
287
|
+
// and its subpaths, and nothing that merely begins with those letters: a
|
|
288
|
+
// `typia-codegen` package is a different package.
|
|
289
|
+
func callExpressionTransformer_isTypiaSpecifier(specifier *shimast.Node) bool {
|
|
290
|
+
if specifier == nil || specifier.Kind != shimast.KindStringLiteral {
|
|
291
|
+
return false
|
|
292
|
+
}
|
|
293
|
+
text := specifier.AsStringLiteral().Text
|
|
294
|
+
return text == "typia" || strings.HasPrefix(text, "typia/")
|
|
295
|
+
}
|
|
296
|
+
|
|
130
297
|
func callExpressionTransformer_sourceFile(node *shimast.Node) *shimast.SourceFile {
|
|
131
298
|
for current := node; current != nil; current = current.Parent {
|
|
132
299
|
if current.Kind == shimast.KindSourceFile {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typia",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.3.0",
|
|
4
4
|
"description": "Superfast runtime validators with only one line",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"inquirer": "^8.2.5",
|
|
51
51
|
"randexp": "^0.5.3",
|
|
52
52
|
"tinyglobby": "^0.2.12",
|
|
53
|
-
"@typia/interface": "^13.
|
|
54
|
-
"@typia/utils": "^13.
|
|
53
|
+
"@typia/interface": "^13.3.0",
|
|
54
|
+
"@typia/utils": "^13.3.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"ttsc": ">=0.19.2"
|
package/src/http.ts
CHANGED
|
@@ -550,10 +550,16 @@ export function createAssertFormData(
|
|
|
550
550
|
*/
|
|
551
551
|
export function createAssertFormData<T extends object>(
|
|
552
552
|
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
553
|
-
): (
|
|
553
|
+
): (
|
|
554
|
+
input: FormData,
|
|
555
|
+
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
556
|
+
) => T;
|
|
554
557
|
|
|
555
558
|
/** @internal */
|
|
556
|
-
export function createAssertFormData<T>(): (
|
|
559
|
+
export function createAssertFormData<T>(): (
|
|
560
|
+
input: FormData,
|
|
561
|
+
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
562
|
+
) => T {
|
|
557
563
|
NoTransformConfigurationError("http.createAssertFormData");
|
|
558
564
|
}
|
|
559
565
|
|
|
@@ -652,11 +658,15 @@ export function createAssertQuery(
|
|
|
652
658
|
*/
|
|
653
659
|
export function createAssertQuery<T extends object>(
|
|
654
660
|
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
655
|
-
): (
|
|
661
|
+
): (
|
|
662
|
+
input: string | IReadableURLSearchParams,
|
|
663
|
+
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
664
|
+
) => T;
|
|
656
665
|
|
|
657
666
|
/** @internal */
|
|
658
667
|
export function createAssertQuery<T>(): (
|
|
659
668
|
input: string | IReadableURLSearchParams,
|
|
669
|
+
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
660
670
|
) => T {
|
|
661
671
|
NoTransformConfigurationError("http.createAssertQuery");
|
|
662
672
|
}
|
|
@@ -758,11 +768,15 @@ export function createAssertHeaders(
|
|
|
758
768
|
*/
|
|
759
769
|
export function createAssertHeaders<T extends object>(
|
|
760
770
|
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
761
|
-
): (
|
|
771
|
+
): (
|
|
772
|
+
input: Record<string, string | string[] | undefined>,
|
|
773
|
+
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
774
|
+
) => T;
|
|
762
775
|
|
|
763
776
|
/** @internal */
|
|
764
777
|
export function createAssertHeaders<T>(): (
|
|
765
778
|
input: Record<string, string | string[] | undefined>,
|
|
779
|
+
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
|
|
766
780
|
) => T {
|
|
767
781
|
NoTransformConfigurationError("http.createAssertHeaders");
|
|
768
782
|
}
|
|
@@ -6,8 +6,19 @@ export const _assertGuard = (
|
|
|
6
6
|
factory?: (props: TypeGuardError.IProps) => Error,
|
|
7
7
|
): false => {
|
|
8
8
|
if (exceptionable === true) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// `factory` arrives from the generated function's second parameter, which
|
|
10
|
+
// a pointwise hand-off fills with something that is not a factory at all:
|
|
11
|
+
// `rows.map(assertUser)` passes the element index. A truthiness test then
|
|
12
|
+
// called a number and reported `factory is not a function`, hiding the
|
|
13
|
+
// real assertion failure — and only for indices other than 0. Require a
|
|
14
|
+
// callable, and fall back to TypeGuardError for everything else.
|
|
15
|
+
//
|
|
16
|
+
// A create-time factory is the default of that same parameter, so a
|
|
17
|
+
// non-callable argument has already overridden it by the time this runs
|
|
18
|
+
// and cannot be recovered here. TypeGuardError still names the property
|
|
19
|
+
// that failed, which `factory is not a function` never did.
|
|
20
|
+
if (typeof factory === "function") throw factory(props);
|
|
21
|
+
throw new TypeGuardError(props);
|
|
11
22
|
}
|
|
12
23
|
return false;
|
|
13
24
|
};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export const _isFormatIdnHostname = (str: string): boolean => PATTERN.test(str);
|
|
2
2
|
|
|
3
|
+
// The ASCII `hostname` structure with the label character class extended by the
|
|
4
|
+
// non-ASCII range: every valid host name is a valid IDN host name, so this must
|
|
5
|
+
// accept a single label, a one-to-63-character label in any position, and a
|
|
6
|
+
// name up to 253 characters, exactly as `_isFormatHostname` does (issue #2317).
|
|
3
7
|
const PATTERN =
|
|
4
|
-
/^([a-z0-9\u00a1-\
|
|
8
|
+
/^(?=.{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;
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
export const _isFormatTime = (str: string): boolean =>
|
|
1
|
+
export const _isFormatTime = (str: string): boolean => {
|
|
2
|
+
const match: RegExpExecArray | null = PATTERN.exec(str);
|
|
3
|
+
if (match === null) return false;
|
|
4
|
+
if (match[3] !== "60") return true;
|
|
5
|
+
|
|
6
|
+
// RFC 3339 admits `:60` only where a leap second is inserted, which is
|
|
7
|
+
// always 23:59:60 UTC. `date-time` decides that from the instant it can
|
|
8
|
+
// build; a clock carries no date, so it shifts itself by its own offset and
|
|
9
|
+
// asks the same question, wrapping the result back into the day.
|
|
10
|
+
const offset: number =
|
|
11
|
+
match[4] === undefined
|
|
12
|
+
? 0
|
|
13
|
+
: (match[4] === "+" ? 1 : -1) *
|
|
14
|
+
(Number(match[5]) * 60 + Number(match[6]));
|
|
15
|
+
const minutes: number = Number(match[1]) * 60 + Number(match[2]) - offset;
|
|
16
|
+
return ((minutes % DAY) + DAY) % DAY === DAY - 1;
|
|
17
|
+
};
|
|
2
18
|
|
|
3
19
|
const PATTERN =
|
|
4
|
-
/^([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:\.[0-9]
|
|
20
|
+
/^([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]))$/;
|
|
21
|
+
const DAY = 24 * 60;
|
|
@@ -7,6 +7,16 @@ const DEFAULT_MIN_ITEMS = 1;
|
|
|
7
7
|
const DEFAULT_RANGE = 5;
|
|
8
8
|
const DEFAULT_RECURSIVE_RANGE = 2;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Consecutive duplicates that end a unique draw once its floor is satisfied.
|
|
12
|
+
*
|
|
13
|
+
* The worst case a domain can still hide is one value left among a handful:
|
|
14
|
+
* with one of four remaining, sixty-four misses in a row is a `0.75 ** 64`
|
|
15
|
+
* event, about one in a hundred million. Below the floor this limit does not
|
|
16
|
+
* apply at all, so it can never fail a request the domain could satisfy.
|
|
17
|
+
*/
|
|
18
|
+
const STALE_LIMIT = 64;
|
|
19
|
+
|
|
10
20
|
export const _randomArray = <T>(
|
|
11
21
|
props: Omit<OpenApi.IJsonSchema.IArray, "items"> & {
|
|
12
22
|
element: (index: number, count: number) => T;
|
|
@@ -28,16 +38,42 @@ export const _randomArray = <T>(
|
|
|
28
38
|
});
|
|
29
39
|
if (props.uniqueItems !== true)
|
|
30
40
|
return new Array(count).fill(null).map((_, i) => props.element(i, count));
|
|
41
|
+
// How many distinct values the element can take is not knowable from here --
|
|
42
|
+
// `element` is a closure over whatever the transform emitted, from `boolean`
|
|
43
|
+
// to a recursive object -- so the count above is a preference, not a
|
|
44
|
+
// contract. Draw until the budget says the domain has nothing new left, then
|
|
45
|
+
// keep what was reached.
|
|
46
|
+
//
|
|
47
|
+
// Failing on that budget instead is what made `Array<boolean> &
|
|
48
|
+
// UniqueItems` throw on two draws in three: `[true, false]` satisfies the
|
|
49
|
+
// declaration at every count, and a count of five is unreachable for a
|
|
50
|
+
// domain of two however many times it is redrawn. Only a result below the
|
|
51
|
+
// floor is a real failure, which is still the honest outcome for a window
|
|
52
|
+
// like `MinItems<3>` over a domain of two.
|
|
53
|
+
//
|
|
54
|
+
// Below the floor the whole budget is spent, because stopping early there
|
|
55
|
+
// would fail a request the domain can still satisfy. Above it every further
|
|
56
|
+
// attempt is only buying length, so a run of `STALE_LIMIT` duplicates is
|
|
57
|
+
// taken as the domain saying it has nothing left. Without that, each nesting
|
|
58
|
+
// level of a small-domain unique array multiplies the full budget into the
|
|
59
|
+
// level above it.
|
|
31
60
|
const elements: T[] = [];
|
|
32
61
|
const maximumAttempts: number = count * 100 + 1000;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
let stale: number = 0;
|
|
63
|
+
for (
|
|
64
|
+
let attempts = 0;
|
|
65
|
+
elements.length !== count && attempts !== maximumAttempts;
|
|
66
|
+
attempts++
|
|
67
|
+
) {
|
|
38
68
|
const candidate: T = props.element(elements.length, count);
|
|
39
|
-
if (elements.every((element) => _isUniqueItems([element, candidate])))
|
|
69
|
+
if (elements.every((element) => _isUniqueItems([element, candidate]))) {
|
|
40
70
|
elements.push(candidate);
|
|
71
|
+
stale = 0;
|
|
72
|
+
} else if (elements.length >= minimum && ++stale === STALE_LIMIT) break;
|
|
41
73
|
}
|
|
74
|
+
if (elements.length < minimum)
|
|
75
|
+
throw new Error(
|
|
76
|
+
"Unable to generate enough unique items; the element domain may be too small.",
|
|
77
|
+
);
|
|
42
78
|
return elements;
|
|
43
79
|
};
|
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _randomInteger } from "./_randomInteger";
|
|
2
2
|
import {
|
|
3
3
|
_ILengthProps,
|
|
4
4
|
_RANDOM_LENGTH_ERROR,
|
|
5
5
|
_randomLengthPick,
|
|
6
6
|
_randomLengthWindow,
|
|
7
7
|
} from "./_randomStringLength";
|
|
8
|
+
import { __randomBase64 } from "./private/__randomComposition";
|
|
8
9
|
|
|
9
10
|
export const _randomFormatByte = (props?: _ILengthProps): string => {
|
|
10
|
-
if (props?.minLength === undefined && props?.maxLength === undefined)
|
|
11
|
-
return FIXED;
|
|
12
11
|
// Base64 encodes three bytes as four characters, so a valid length is always
|
|
13
|
-
// a multiple of four;
|
|
14
|
-
// and
|
|
15
|
-
//
|
|
12
|
+
// a multiple of four; an unconstrained draw spends between four and twelve
|
|
13
|
+
// groups, and a constrained one raises the window to the first multiple it
|
|
14
|
+
// contains and steps by four from there.
|
|
15
|
+
if (props?.minLength === undefined && props?.maxLength === undefined)
|
|
16
|
+
return __randomBase64(
|
|
17
|
+
4 *
|
|
18
|
+
_randomInteger({
|
|
19
|
+
type: "integer",
|
|
20
|
+
minimum: DEFAULT_GROUP_MIN,
|
|
21
|
+
maximum: DEFAULT_GROUP_MAX,
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
16
24
|
const window = _randomLengthWindow(props, { minimum: 0, spread: 16 });
|
|
17
25
|
const low: number = Math.ceil(window.low / 4) * 4;
|
|
18
26
|
if (low > window.high) throw new Error(_RANDOM_LENGTH_ERROR);
|
|
@@ -23,11 +31,8 @@ export const _randomFormatByte = (props?: _ILengthProps): string => {
|
|
|
23
31
|
low: 0,
|
|
24
32
|
high: Math.floor((window.high - low) / 4),
|
|
25
33
|
});
|
|
26
|
-
return
|
|
27
|
-
type: "string",
|
|
28
|
-
minLength: length,
|
|
29
|
-
maxLength: length,
|
|
30
|
-
});
|
|
34
|
+
return __randomBase64(length);
|
|
31
35
|
};
|
|
32
36
|
|
|
33
|
-
const
|
|
37
|
+
const DEFAULT_GROUP_MIN = 4;
|
|
38
|
+
const DEFAULT_GROUP_MAX = 12;
|
|
@@ -29,8 +29,8 @@ const pickLength = (props: _ILengthProps): number => {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Builds dot-joined hostname labels totaling exactly `length` characters, each
|
|
32
|
-
* label within 63 chars. Shared with the idn-hostname generator, which
|
|
33
|
-
*
|
|
32
|
+
* label within 63 chars. Shared with the idn-hostname generator, which realizes
|
|
33
|
+
* its length the same way since #2317 gave the two formats one structure.
|
|
34
34
|
*/
|
|
35
35
|
export const _randomHostnameLabels = (length: number): string => {
|
|
36
36
|
const parts: string[] = [];
|
|
@@ -6,20 +6,19 @@ import { _ILengthProps } from "./_randomStringLength";
|
|
|
6
6
|
export const _randomFormatIdnHostname = (props?: _ILengthProps): string => {
|
|
7
7
|
if (props?.minLength === undefined && props?.maxLength === undefined)
|
|
8
8
|
return `${random(10)}.${random(3)}`;
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
return
|
|
9
|
+
// idn-hostname shares hostname's structure (a single label of 1..63 chars is
|
|
10
|
+
// valid, no dot or trailing label required), so the requested length is
|
|
11
|
+
// realized the same way: split it across dot-joined labels. #2317 dropped the
|
|
12
|
+
// mandatory dot and two-character trailing label this generator used to
|
|
13
|
+
// reserve, which is why it now realizes a length below four.
|
|
14
|
+
return _randomHostnameLabels(pickLength(props));
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const MAX_TOTAL = 253;
|
|
18
|
-
const MIN_TOTAL = 4; // the shortest idn hostname, `x.yy`: label(1) + `.` + tld(2)
|
|
19
18
|
|
|
20
19
|
const pickLength = (props: _ILengthProps): number => {
|
|
21
|
-
let low: number = props.minLength === undefined ?
|
|
22
|
-
if (low <
|
|
20
|
+
let low: number = props.minLength === undefined ? 1 : props.minLength;
|
|
21
|
+
if (low < 1) low = 1;
|
|
23
22
|
const high: number =
|
|
24
23
|
props.maxLength === undefined
|
|
25
24
|
? Math.min(MAX_TOTAL, low + 14)
|
|
@@ -6,11 +6,12 @@ import {
|
|
|
6
6
|
} from "./_randomStringLength";
|
|
7
7
|
|
|
8
8
|
export const _randomFormatRegex = (props?: _ILengthProps): string => {
|
|
9
|
-
if (props?.minLength === undefined && props?.maxLength === undefined)
|
|
10
|
-
return FIXED;
|
|
11
9
|
// Every character `_randomString` emits is a literal in regular expression
|
|
12
10
|
// syntax, so a source of any length — including the empty source `new
|
|
13
|
-
// RegExp("")` accepts — compiles.
|
|
11
|
+
// RegExp("")` accepts — compiles. An unconstrained draw is therefore an
|
|
12
|
+
// unconstrained string, and a constrained one fixes the length first.
|
|
13
|
+
if (props?.minLength === undefined && props?.maxLength === undefined)
|
|
14
|
+
return _randomString({ type: "string" });
|
|
14
15
|
const length: number = _randomLengthPick(
|
|
15
16
|
_randomLengthWindow(props, { minimum: 0, spread: 16 }),
|
|
16
17
|
);
|
|
@@ -20,6 +21,3 @@ export const _randomFormatRegex = (props?: _ILengthProps): string => {
|
|
|
20
21
|
maxLength: length,
|
|
21
22
|
});
|
|
22
23
|
};
|
|
23
|
-
|
|
24
|
-
const FIXED =
|
|
25
|
-
"/^(?:(?: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)$/";
|