typia 13.3.0 → 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/internal/_createStandardSchema.d.ts +1 -2
- package/lib/internal/_createStandardSchema.js.map +1 -1
- package/lib/internal/_createStandardSchema.mjs.map +1 -1
- package/lib/internal/_randomPattern.js +20 -3
- package/lib/internal/_randomPattern.js.map +1 -1
- package/lib/internal/_randomPattern.mjs +5 -1
- package/lib/internal/_randomPattern.mjs.map +1 -1
- package/lib/internal/_randomStringLength.js +9 -2
- package/lib/internal/_randomStringLength.js.map +1 -1
- package/lib/internal/_randomStringLength.mjs +3 -1
- package/lib/internal/_randomStringLength.mjs.map +1 -1
- package/lib/module.d.ts +1 -2
- package/lib/module.js.map +1 -1
- package/lib/module.mjs.map +1 -1
- package/lib/re-exports.d.ts +1 -1
- package/lib/re-exports.js.map +1 -1
- package/lib/transform.mjs +1 -1
- package/native/cmd/ttsc-typia/create_assert_error_factory_arity_test.go +5 -2
- package/native/cmd/ttsc-typia/dependency_collector_virtual_scheme_test.go +52 -0
- package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +206 -0
- package/native/cmd/ttsc-typia/notation_kebab_case_transform_test.go +2 -2
- package/native/cmd/ttsc-typia/project_catalog_transform_coverage_test.go +84 -85
- package/native/cmd/ttsc-typia/project_dependencies_callee_argument_transform_test.go +177 -0
- package/native/cmd/ttsc-typia/project_dependencies_callee_barrel_transform_test.go +115 -0
- package/native/cmd/ttsc-typia/project_dependencies_callee_inference_transform_test.go +285 -0
- package/native/cmd/ttsc-typia/project_dependencies_callee_shape_transform_test.go +117 -0
- package/native/cmd/ttsc-typia/project_dependencies_callee_untransformed_barrel_transform_test.go +121 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_diagnostic_transform_test.go +116 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_envelope_transform_test.go +115 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_inferred_type_transform_test.go +127 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_replaced_library_transform_test.go +153 -0
- package/native/cmd/ttsc-typia/project_dependencies_complete_untouched_reprint_transform_test.go +155 -0
- package/native/cmd/ttsc-typia/project_dependencies_computed_key_transform_test.go +218 -0
- package/native/cmd/ttsc-typia/project_dependencies_custom_lib_name_transform_test.go +6 -5
- package/native/cmd/ttsc-typia/project_dependencies_enum_member_value_transform_test.go +131 -0
- package/native/cmd/ttsc-typia/project_dependencies_inferred_declaration_transform_test.go +159 -0
- package/native/cmd/ttsc-typia/project_dependencies_jsdoc_typedef_transform_test.go +134 -0
- package/native/cmd/ttsc-typia/project_dependencies_named_tuple_member_transform_test.go +123 -0
- package/native/cmd/ttsc-typia/project_dependencies_qualified_barrel_transform_test.go +141 -0
- package/native/cmd/ttsc-typia/project_dependencies_type_parameter_default_transform_test.go +127 -0
- package/native/cmd/ttsc-typia/template_literal_type_tags_transform_test.go +3 -1
- package/native/cmd/ttsc-typia/transform.go +195 -21
- package/native/cmd/ttsc-typia/transform_helpers_test.go +12 -0
- package/native/core/factories/MetadataCommentTagFactory.go +96 -27
- package/native/core/factories/metadata_comment_tag_factory_coverage_test.go +2 -2
- package/native/core/factories/metadata_comment_tag_factory_rejects_out_of_range_integer_test.go +124 -0
- package/native/core/programmers/AssertProgrammer.go +20 -6
- package/native/core/programmers/RandomProgrammer.go +78 -6
- package/native/core/programmers/ValidateProgrammer.go +21 -6
- package/native/core/programmers/helpers/RandomJoiner.go +8 -1
- package/native/core/programmers/iterate/check_dynamic_properties.go +177 -3
- package/native/core/programmers/iterate/check_object.go +12 -1
- package/native/core/schemas/metadata/MetadataDependency.go +613 -30
- package/native/transform/CallExpressionTransformer.go +16 -1
- package/package.json +4 -14
- package/src/internal/_createStandardSchema.ts +1 -2
- package/src/internal/_randomPattern.ts +18 -3
- package/src/internal/_randomStringLength.ts +9 -2
- package/src/module.ts +6 -2
- package/src/re-exports.ts +13 -0
- package/lib/executable/FileSystemIdentity.d.ts +0 -44
- package/lib/executable/FileSystemIdentity.js +0 -213
- package/lib/executable/FileSystemIdentity.js.map +0 -1
- package/lib/executable/FileSystemIdentity.mjs +0 -133
- package/lib/executable/FileSystemIdentity.mjs.map +0 -1
- package/lib/executable/TypiaGenerateWizard.d.ts +0 -9
- package/lib/executable/TypiaGenerateWizard.js +0 -852
- package/lib/executable/TypiaGenerateWizard.js.map +0 -1
- package/lib/executable/TypiaGenerateWizard.mjs +0 -618
- package/lib/executable/TypiaGenerateWizard.mjs.map +0 -1
- package/lib/executable/generate/ttsc.d.ts +0 -1
- package/lib/executable/generate/ttsc.js +0 -53
- package/lib/executable/generate/ttsc.js.map +0 -1
- package/lib/executable/generate/ttsc.mjs +0 -37
- package/lib/executable/generate/ttsc.mjs.map +0 -1
- package/lib/executable/typia.d.ts +0 -2
- package/lib/executable/typia.js +0 -84
- package/lib/executable/typia.js.map +0 -1
- package/lib/executable/typia.mjs +0 -60
- package/lib/executable/typia.mjs.map +0 -1
- package/src/executable/FileSystemIdentity.ts +0 -222
- package/src/executable/TypiaGenerateWizard.ts +0 -1133
- package/src/executable/generate/ttsc.ts +0 -70
- package/src/executable/typia.ts +0 -80
|
@@ -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
|
|
@@ -103,7 +103,7 @@ func runTransform(args []string) int {
|
|
|
103
103
|
fmt.Fprintln(stderr, "ttsc-typia transform: --out requires --file")
|
|
104
104
|
return 2
|
|
105
105
|
}
|
|
106
|
-
return runTransformProject(prog, cwd, typiaTransform, &transformDiags)
|
|
106
|
+
return runTransformProject(prog, cwd, typiaTransform, &transformDiags, &extras)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
absFile := *file
|
|
@@ -190,11 +190,25 @@ func runTransformSingle(
|
|
|
190
190
|
// dependencies[F]), so graph is added alongside dependencies, not a replacement.
|
|
191
191
|
// Every section keys through driver.TransformOutputKey, so the consumer joins
|
|
192
192
|
// them by key.
|
|
193
|
+
//
|
|
194
|
+
// A third section, `dependenciesComplete`, names the files whose `dependencies`
|
|
195
|
+
// entry is the whole story, and for those the consumer stops unioning: it
|
|
196
|
+
// derives dependencies[F] ∪ configs ∪ candidates and drops reach(edges, F) and
|
|
197
|
+
// globals. That is the difference between re-validating the reference closure
|
|
198
|
+
// of a file and re-validating the handful of declarations typia read, paid once
|
|
199
|
+
// per delivered module, and for the majority of a project's files -- the ones
|
|
200
|
+
// with no typia call at all -- between the closure and nothing.
|
|
201
|
+
//
|
|
202
|
+
// The declaration transfers responsibility, so it is made per file and only
|
|
203
|
+
// where the collector could bound the file's inputs; the dependency listener
|
|
204
|
+
// and the diagnostic report this function wires are what decide that
|
|
205
|
+
// (samchon/typia#2357). See transformDependencyCollector.Unbound.
|
|
193
206
|
func runTransformProject(
|
|
194
207
|
prog *driver.Program,
|
|
195
208
|
cwd string,
|
|
196
209
|
typiaTransform driver.PluginTransform,
|
|
197
210
|
transformDiags *[]typiaTransformDiagnostic,
|
|
211
|
+
extras *nativecontext.ITypiaContext_Extras,
|
|
198
212
|
) int {
|
|
199
213
|
out := transformProjectOutput{
|
|
200
214
|
Diagnostics: []transformCompilerDiagnostic{},
|
|
@@ -209,8 +223,27 @@ func runTransformProject(
|
|
|
209
223
|
sf := prog.SourceFile(fileName)
|
|
210
224
|
return sf != nil && prog.TSProgram.IsLibFile(sf)
|
|
211
225
|
})
|
|
212
|
-
|
|
226
|
+
// Only this host declares completeness, which is why only this host asks the
|
|
227
|
+
// analysis to admit what it could not bound: every such admission costs one
|
|
228
|
+
// file its declaration and nothing else.
|
|
229
|
+
schemametadata.MetadataDependency_listen(prog.Checker, schemametadata.MetadataDependency_IListener{
|
|
230
|
+
File: collector.Touch,
|
|
231
|
+
Callee: collector.TouchCallee,
|
|
232
|
+
Unbounded: collector.Unbound,
|
|
233
|
+
})
|
|
213
234
|
defer schemametadata.MetadataDependency_release(prog.Checker)
|
|
235
|
+
// A diagnostic means typia could not finish lowering a call in the file being
|
|
236
|
+
// transformed, so what the analysis consulted before giving up is not that
|
|
237
|
+
// file's whole input set: an edit to a declaration it never reached could make
|
|
238
|
+
// the same call succeed and publish different text. The build fails either
|
|
239
|
+
// way, but a false claim is still false, so withhold the declaration rather
|
|
240
|
+
// than reason about how far the analysis got. The transformer reads `extras`
|
|
241
|
+
// on every call, so wrapping it through the pointer reaches the loop below.
|
|
242
|
+
reportDiagnostic := extras.AddDiagnostic
|
|
243
|
+
extras.AddDiagnostic = func(diag *nativecontext.ITypiaDiagnostic) int {
|
|
244
|
+
collector.Unbound()
|
|
245
|
+
return reportDiagnostic(diag)
|
|
246
|
+
}
|
|
214
247
|
for _, sf := range prog.SourceFiles() {
|
|
215
248
|
if sf.IsDeclarationFile {
|
|
216
249
|
continue
|
|
@@ -221,8 +254,16 @@ func runTransformProject(
|
|
|
221
254
|
}
|
|
222
255
|
collector.Begin(key)
|
|
223
256
|
out.TypeScript[key] = transformFileToTypeScript(prog, typiaTransform, sf)
|
|
257
|
+
collector.End()
|
|
224
258
|
}
|
|
225
259
|
out.Dependencies = collector.ToJSON()
|
|
260
|
+
// The config chain is the one input that stays universal for a declared file,
|
|
261
|
+
// and it reaches the consumer through this same envelope's graph. Declaring
|
|
262
|
+
// completeness without a graph would leave nothing universal behind, so a
|
|
263
|
+
// tsconfig edit would stop invalidating the files the declaration narrowed.
|
|
264
|
+
if out.Graph != nil {
|
|
265
|
+
out.DependenciesComplete = collector.ToCompleteJSON(out.TypeScript)
|
|
266
|
+
}
|
|
226
267
|
for _, diag := range *transformDiags {
|
|
227
268
|
out.Diagnostics = append(out.Diagnostics, transformDiagnosticToCompilerDiagnostic(diag))
|
|
228
269
|
}
|
|
@@ -359,24 +400,62 @@ type transformProjectOutput struct {
|
|
|
359
400
|
// files are excluded (toolchain-versioned, not project inputs);
|
|
360
401
|
// `node_modules` declaration files are included.
|
|
361
402
|
Dependencies map[string][]string `json:"dependencies,omitempty"`
|
|
403
|
+
// DependenciesComplete lists the TypeScript keys whose Dependencies entry is
|
|
404
|
+
// exhaustive: nothing outside that entry, the file itself, and the universal
|
|
405
|
+
// config chain can change the file's transformed text. The consumer stops
|
|
406
|
+
// widening those files with the reference closure and the global-scope set.
|
|
407
|
+
//
|
|
408
|
+
// Unlisted is always the safe answer, so the list carries only what the
|
|
409
|
+
// collector could bound. An older ttsc ignores the field; 0.19.3 and newer
|
|
410
|
+
// narrow on it.
|
|
411
|
+
DependenciesComplete []string `json:"dependenciesComplete,omitempty"`
|
|
362
412
|
}
|
|
363
413
|
|
|
364
414
|
// transformDependencyCollector accumulates the consulted-declaration files the
|
|
365
415
|
// metadata dependency listener reports while one project file is transformed,
|
|
366
|
-
// attributing them to that file's envelope key
|
|
416
|
+
// attributing them to that file's envelope key, and decides which of those keys
|
|
417
|
+
// may be declared complete.
|
|
367
418
|
type transformDependencyCollector struct {
|
|
368
419
|
cwd string
|
|
369
420
|
current string
|
|
370
421
|
files map[string]map[string]bool
|
|
422
|
+
// unbounded holds the keys withheld from the completeness declaration. It is
|
|
423
|
+
// a set of exclusions rather than a set of admissions because every path that
|
|
424
|
+
// learns a file cannot be bounded runs while that file is being transformed,
|
|
425
|
+
// whereas nothing ever proves the opposite: a file is declarable exactly when
|
|
426
|
+
// no such path fired for it.
|
|
427
|
+
unbounded map[string]bool
|
|
371
428
|
// isLibraryFile reports the compiler's own classification of a file as a
|
|
372
429
|
// default library. Classification must come from the program, never from a
|
|
373
430
|
// filename pattern: a project's own ambient declaration file may legitimately
|
|
374
431
|
// be named `lib.custom.d.ts`, and dropping it by basename silently loses
|
|
375
432
|
// cache invalidation for the types it declares (samchon/typia#2108).
|
|
376
433
|
isLibraryFile func(fileName string) bool
|
|
377
|
-
// values memoizes fileName -> envelope value
|
|
378
|
-
//
|
|
379
|
-
values map[string]
|
|
434
|
+
// values memoizes fileName -> rendered envelope value: the listener reports
|
|
435
|
+
// the same declaration files repeatedly across call sites.
|
|
436
|
+
values map[string]transformDependencyValue
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// transformDependencyBundledScheme is the prefix tsgo gives its embedded default
|
|
440
|
+
// libraries, and the one `driver.NewTransformGraph` drops from the host-owned
|
|
441
|
+
// reference graph.
|
|
442
|
+
const transformDependencyBundledScheme = "bundled:///"
|
|
443
|
+
|
|
444
|
+
// transformDependencyValue is one reported file rendered for the envelope: its
|
|
445
|
+
// key, or the reason it was dropped. The reasons differ in what they cost a
|
|
446
|
+
// completeness declaration, so they cannot both collapse to an empty value.
|
|
447
|
+
type transformDependencyValue struct {
|
|
448
|
+
// key is the envelope value, empty when the file was dropped.
|
|
449
|
+
key string
|
|
450
|
+
// withhold marks a file this envelope cannot report but the host-owned bound
|
|
451
|
+
// still carries, so narrowing to the reported list would lose it.
|
|
452
|
+
withhold bool
|
|
453
|
+
// libraryOnly marks a withhold whose only cause is the compiler classifying
|
|
454
|
+
// the file as a default library. Such a file can change a consulted type and
|
|
455
|
+
// so a generated validator, but never which declaration a call resolves to,
|
|
456
|
+
// which is why the identity channel drops it without withholding
|
|
457
|
+
// (samchon/typia#2361).
|
|
458
|
+
libraryOnly bool
|
|
380
459
|
}
|
|
381
460
|
|
|
382
461
|
func newTransformDependencyCollector(
|
|
@@ -387,7 +466,8 @@ func newTransformDependencyCollector(
|
|
|
387
466
|
cwd: cwd,
|
|
388
467
|
isLibraryFile: isLibraryFile,
|
|
389
468
|
files: map[string]map[string]bool{},
|
|
390
|
-
|
|
469
|
+
unbounded: map[string]bool{},
|
|
470
|
+
values: map[string]transformDependencyValue{},
|
|
391
471
|
}
|
|
392
472
|
}
|
|
393
473
|
|
|
@@ -396,11 +476,61 @@ func (collector *transformDependencyCollector) Begin(key string) {
|
|
|
396
476
|
collector.current = key
|
|
397
477
|
}
|
|
398
478
|
|
|
479
|
+
// End closes the attribution window Begin opened. Nothing consults the checker
|
|
480
|
+
// between two files today, so this changes no output; it is what keeps that
|
|
481
|
+
// true, because a touch that arrives outside a window is dropped rather than
|
|
482
|
+
// charged -- or worse, withheld -- against whichever file happened to run last.
|
|
483
|
+
func (collector *transformDependencyCollector) End() {
|
|
484
|
+
collector.current = ""
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Unbound withholds the completeness declaration from the file being
|
|
488
|
+
// transformed. The file keeps its reported dependencies and falls back to the
|
|
489
|
+
// host-owned reference-closure bound, which is what an unlisted file gets and
|
|
490
|
+
// is always sound. Its callers name the reasons: an input the analysis could
|
|
491
|
+
// only read from an inferred position, a consulted on-disk default library, and
|
|
492
|
+
// a call typia could not lower.
|
|
493
|
+
func (collector *transformDependencyCollector) Unbound() {
|
|
494
|
+
if collector.current == "" {
|
|
495
|
+
return
|
|
496
|
+
}
|
|
497
|
+
collector.unbounded[collector.current] = true
|
|
498
|
+
}
|
|
499
|
+
|
|
399
500
|
// Touch records one consulted declaration file for the current key. Default
|
|
400
501
|
// library files, virtual URI sources (e.g. tsgo's `bundled:///` libraries),
|
|
401
502
|
// and the transformed file itself are dropped so the envelope carries only
|
|
402
503
|
// actionable project inputs.
|
|
504
|
+
//
|
|
505
|
+
// A drop the host-owned bound does not share also withholds the current file's
|
|
506
|
+
// completeness declaration: the graph still carries that file, so a narrowed
|
|
507
|
+
// file would stop watching an input the default bound watches.
|
|
403
508
|
func (collector *transformDependencyCollector) Touch(fileName string) {
|
|
509
|
+
collector.touch(fileName, true)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// TouchCallee records one file reached while deciding WHICH declaration a call
|
|
513
|
+
// resolves to, and does not let a default library withhold the current file.
|
|
514
|
+
//
|
|
515
|
+
// A default library is dropped from the reported list either way -- that
|
|
516
|
+
// contract is samchon/typia#2108's -- but the withholding beside it belongs to
|
|
517
|
+
// the type channel alone. Under `libReplacement` a replaced library can change
|
|
518
|
+
// a generated validator, so a file that consulted one has to give up its
|
|
519
|
+
// declaration. It can never change which declaration a call resolves to:
|
|
520
|
+
// callExpressionTransformer_targetModule recognizes typia's own calls by a path
|
|
521
|
+
// ending in `/typia/lib/<file>.d.ts` or `/typia/src/<file>.ts`, and a file the
|
|
522
|
+
// compiler classifies as a default library satisfies neither wherever it sits
|
|
523
|
+
// on disk. Withholding there cost a file its declaration for calling
|
|
524
|
+
// `table.get(...)`, and under `noembed` -- where every library is a real file --
|
|
525
|
+
// for calling anything at all (samchon/typia#2361).
|
|
526
|
+
//
|
|
527
|
+
// A virtual non-`bundled:///` scheme still withholds on both channels: that drop
|
|
528
|
+
// is about being unwatchable, not about being a library.
|
|
529
|
+
func (collector *transformDependencyCollector) TouchCallee(fileName string) {
|
|
530
|
+
collector.touch(fileName, false)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
func (collector *transformDependencyCollector) touch(fileName string, library bool) {
|
|
404
534
|
if collector.current == "" {
|
|
405
535
|
return
|
|
406
536
|
}
|
|
@@ -409,7 +539,10 @@ func (collector *transformDependencyCollector) Touch(fileName string) {
|
|
|
409
539
|
value = collector.value(fileName)
|
|
410
540
|
collector.values[fileName] = value
|
|
411
541
|
}
|
|
412
|
-
if value
|
|
542
|
+
if value.withhold && (library || value.libraryOnly == false) {
|
|
543
|
+
collector.Unbound()
|
|
544
|
+
}
|
|
545
|
+
if value.key == "" || value.key == collector.current {
|
|
413
546
|
return
|
|
414
547
|
}
|
|
415
548
|
set := collector.files[collector.current]
|
|
@@ -417,19 +550,36 @@ func (collector *transformDependencyCollector) Touch(fileName string) {
|
|
|
417
550
|
set = map[string]bool{}
|
|
418
551
|
collector.files[collector.current] = set
|
|
419
552
|
}
|
|
420
|
-
set[value] = true
|
|
553
|
+
set[value.key] = true
|
|
421
554
|
}
|
|
422
555
|
|
|
423
|
-
// value renders one reported file as its envelope value, or
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
// libraries
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
556
|
+
// value renders one reported file as its envelope value, or records why it must
|
|
557
|
+
// be dropped.
|
|
558
|
+
//
|
|
559
|
+
// tsgo's embedded libraries under `bundled:///` are nobody's input:
|
|
560
|
+
// `driver.NewTransformGraph` skips that exact prefix, so dropping them leaves
|
|
561
|
+
// the reported list and the host-owned bound in agreement and costs a
|
|
562
|
+
// completeness declaration nothing. Any other URI scheme is unwatchable for us
|
|
563
|
+
// but still reaches the graph, so it is dropped AND withheld -- the asymmetry
|
|
564
|
+
// only runs in the safe direction.
|
|
565
|
+
//
|
|
566
|
+
// A file the compiler classifies as a default library is a toolchain input
|
|
567
|
+
// rather than a project input, which is why the envelope has never reported one.
|
|
568
|
+
// Under `noembed` or `libReplacement` it is nevertheless a real path that
|
|
569
|
+
// `graph.globals` carries, so the default bound watches it while a narrowed file
|
|
570
|
+
// would not. Keep dropping it from the reported list -- that contract is
|
|
571
|
+
// #2108's -- and withhold the declaration from the file that consulted it.
|
|
572
|
+
func (collector *transformDependencyCollector) value(fileName string) transformDependencyValue {
|
|
573
|
+
if strings.HasPrefix(fileName, transformDependencyBundledScheme) {
|
|
574
|
+
return transformDependencyValue{}
|
|
575
|
+
}
|
|
576
|
+
if strings.Contains(fileName, "://") {
|
|
577
|
+
return transformDependencyValue{withhold: true}
|
|
578
|
+
}
|
|
579
|
+
if collector.isLibraryFile(fileName) {
|
|
580
|
+
return transformDependencyValue{withhold: true, libraryOnly: true}
|
|
581
|
+
}
|
|
582
|
+
return transformDependencyValue{key: driver.TransformOutputKey(collector.cwd, fileName)}
|
|
433
583
|
}
|
|
434
584
|
|
|
435
585
|
// ToJSON renders the collected sets as the envelope's `dependencies` map with
|
|
@@ -456,6 +606,30 @@ func (collector *transformDependencyCollector) ToJSON() map[string][]string {
|
|
|
456
606
|
return output
|
|
457
607
|
}
|
|
458
608
|
|
|
609
|
+
// ToCompleteJSON renders the envelope's `dependenciesComplete` list: every key
|
|
610
|
+
// the transform published except the ones the collector could not bound.
|
|
611
|
+
//
|
|
612
|
+
// The published map is the input rather than the entries of ToJSON. A file typia
|
|
613
|
+
// never touched reports no dependency at all, and that empty entry is the
|
|
614
|
+
// strongest claim in the envelope -- nothing outside the file can change a
|
|
615
|
+
// faithful re-print -- so reading the dependency map instead would drop the
|
|
616
|
+
// majority of the narrowing on the floor. Taking the same map the consumer keys
|
|
617
|
+
// on also makes the list a subset of it by construction.
|
|
618
|
+
func (collector *transformDependencyCollector) ToCompleteJSON(published map[string]string) []string {
|
|
619
|
+
output := make([]string, 0, len(published))
|
|
620
|
+
for key := range published {
|
|
621
|
+
if collector.unbounded[key] {
|
|
622
|
+
continue
|
|
623
|
+
}
|
|
624
|
+
output = append(output, key)
|
|
625
|
+
}
|
|
626
|
+
if len(output) == 0 {
|
|
627
|
+
return nil
|
|
628
|
+
}
|
|
629
|
+
sort.Strings(output)
|
|
630
|
+
return output
|
|
631
|
+
}
|
|
632
|
+
|
|
459
633
|
type transformCompilerDiagnostic struct {
|
|
460
634
|
File *string `json:"file"`
|
|
461
635
|
Category string `json:"category"`
|
|
@@ -131,6 +131,7 @@ func ttscTypiaTestWriteCommonRuntimeStubs(t *testing.T, runtimeDir string) {
|
|
|
131
131
|
"assert-guard-stub.cjs": ttscTypiaTestAssertGuardStub,
|
|
132
132
|
"functional-error-stub.cjs": ttscTypiaTestFunctionalErrorStub,
|
|
133
133
|
"access-expression-stub.cjs": ttscTypiaTestAccessExpressionStub,
|
|
134
|
+
"string-length-stub.cjs": ttscTypiaTestStringLengthStub,
|
|
134
135
|
"notation-stub.cjs": ttscTypiaTestNotationStub,
|
|
135
136
|
"json-stringify-array-stub.cjs": ttscTypiaTestJsonStringifyArrayStub,
|
|
136
137
|
"json-stringify-property-stub.cjs": ttscTypiaTestJsonStringifyPropertyStub,
|
|
@@ -153,6 +154,7 @@ func ttscTypiaTestRewriteCommonJS(t *testing.T, js string) string {
|
|
|
153
154
|
runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_assertGuard")`, `require("./assert-guard-stub.cjs")`)
|
|
154
155
|
runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_functionalTypeGuardErrorFactory")`, `require("./functional-error-stub.cjs")`)
|
|
155
156
|
runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_accessExpressionAsString")`, `require("./access-expression-stub.cjs")`)
|
|
157
|
+
runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_stringLength")`, `require("./string-length-stub.cjs")`)
|
|
156
158
|
runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_jsonStringifyArray")`, `require("./json-stringify-array-stub.cjs")`)
|
|
157
159
|
runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_jsonStringifyProperty")`, `require("./json-stringify-property-stub.cjs")`)
|
|
158
160
|
runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_jsonStringifyElement")`, `require("./json-stringify-element-stub.cjs")`)
|
|
@@ -290,6 +292,16 @@ const ttscTypiaTestRandomStringStub = `module.exports._randomString = (props) =>
|
|
|
290
292
|
};
|
|
291
293
|
`
|
|
292
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.
|
|
298
|
+
const ttscTypiaTestStringLengthStub = `module.exports._stringLength = (value) => {
|
|
299
|
+
let count = 0;
|
|
300
|
+
for (const _ of value) ++count;
|
|
301
|
+
return count;
|
|
302
|
+
};
|
|
303
|
+
`
|
|
304
|
+
|
|
293
305
|
const ttscTypiaTestAccessExpressionStub = `const reserved = new Set([
|
|
294
306
|
"break",
|
|
295
307
|
"case",
|
|
@@ -196,6 +196,34 @@ func (metadataCommentTagFactoryNamespace) Get(props struct {
|
|
|
196
196
|
return output
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// metadataCommentTagFactory_PARSER maps a JSDoc tag name to the tag records it
|
|
200
|
+
// produces. Each record's `Validate` is the runtime check the transform splices
|
|
201
|
+
// into the emitted validator, and it must spell the same check as the matching
|
|
202
|
+
// declaration in `packages/interface/src/tags` -- the two are one constraint.
|
|
203
|
+
//
|
|
204
|
+
// # WHEN A RECORD MAY NAME A RUNTIME HELPER
|
|
205
|
+
//
|
|
206
|
+
// A `Validate` may write `$importInternal("x")`, here or in the matching
|
|
207
|
+
// declaration, only under the rule below.
|
|
208
|
+
//
|
|
209
|
+
// That call makes the emitted validator `require("typia/lib/internal/_x")`. The
|
|
210
|
+
// type-tag half of each constraint is declared in `@typia/interface`, which
|
|
211
|
+
// `typia` depends on through a caret range, and a caret only floats upward. So
|
|
212
|
+
// inside one major an older `typia` installs a newer `@typia/interface` and
|
|
213
|
+
// emits an import its own runtime never published; that is #2330, and it is why
|
|
214
|
+
// #2336 and #2339 reverted these very templates to inline expressions.
|
|
215
|
+
//
|
|
216
|
+
// The rule: a template may name only a helper that every `typia` inside the
|
|
217
|
+
// caret range already ships. `isTypeInt8` and its siblings satisfy it --
|
|
218
|
+
// `@typia/interface` 13.0.0 named them and `typia` 13.0.0 shipped them. The
|
|
219
|
+
// four this file and `Type.ts` now name do not: `_stringLength`, `_isMultipleOf`
|
|
220
|
+
// (both first published in 13.1.19), `_isTypeInt64Bigint`, and
|
|
221
|
+
// `_isTypeUint64Bigint` (13.1.19). They are safe only because the release that
|
|
222
|
+
// carries them is a major, where the caret cannot reach a `typia` that predates
|
|
223
|
+
// them. A minor or patch release of this tree reopens #2330.
|
|
224
|
+
//
|
|
225
|
+
// Nothing here can enforce that; the release version is the maintainer's, and a
|
|
226
|
+
// campaign pull request never assigns one. This comment is the record.
|
|
199
227
|
var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_parser{
|
|
200
228
|
"items": func(props struct {
|
|
201
229
|
Report func(msg string) any
|
|
@@ -268,7 +296,13 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
|
|
|
268
296
|
Report func(msg string) any
|
|
269
297
|
Value string
|
|
270
298
|
}) metadataCommentTagFactory_TagRecord {
|
|
271
|
-
|
|
299
|
+
// `@multipleOf` and `MultipleOf<N>` are two spellings of one constraint, so
|
|
300
|
+
// the number arm divides exact decimals through the same helper the type tag
|
|
301
|
+
// names. `%` would divide the binary doubles that are actually stored, which
|
|
302
|
+
// answers a different question than the `multipleOf` keyword this tag emits.
|
|
303
|
+
// The bigint arm needs no helper: a bigint remainder is already exact.
|
|
304
|
+
// `_isMultipleOf` first shipped in 13.1.19; see the doc comment above.
|
|
305
|
+
return metadataCommentTagFactory_numeric(props, "MultipleOf", "multipleOf", "$importInternal(\"_isMultipleOf\")($input, "+props.Value+")", "$input % "+props.Value+"n === 0n")
|
|
272
306
|
},
|
|
273
307
|
"format": func(props struct {
|
|
274
308
|
Report func(msg string) any
|
|
@@ -291,9 +325,16 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
|
|
|
291
325
|
Value string
|
|
292
326
|
}) metadataCommentTagFactory_TagRecord {
|
|
293
327
|
value := metadataCommentTagFactory_parse_number(props)
|
|
328
|
+
// `@length`, `@minLength`, and `@maxLength` measure through the same helper
|
|
329
|
+
// `MinLength<N>` and `MaxLength<N>` name, because the JSDoc spelling and the
|
|
330
|
+
// type-tag spelling of one constraint must never mean different things.
|
|
331
|
+
// `$input.length` counts UTF-16 code units, while the `minLength` and
|
|
332
|
+
// `maxLength` keywords these tags emit count characters, so one astral
|
|
333
|
+
// character would measure 2 against a schema that measures 1.
|
|
334
|
+
// `_stringLength` first shipped in 13.1.19; see the doc comment above.
|
|
294
335
|
return metadataCommentTagFactory_TagRecord{"string": {
|
|
295
|
-
{Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: props.Value + " <= $input
|
|
296
|
-
{Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$input
|
|
336
|
+
{Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: props.Value + " <= $importInternal(\"_stringLength\")($input)", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}},
|
|
337
|
+
{Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLength\")($input) <= " + props.Value, Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}},
|
|
297
338
|
}}
|
|
298
339
|
},
|
|
299
340
|
"minLength": func(props struct {
|
|
@@ -301,14 +342,14 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
|
|
|
301
342
|
Value string
|
|
302
343
|
}) metadataCommentTagFactory_TagRecord {
|
|
303
344
|
value := metadataCommentTagFactory_parse_number(props)
|
|
304
|
-
return metadataCommentTagFactory_TagRecord{"string": {{Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: props.Value + " <= $input
|
|
345
|
+
return metadataCommentTagFactory_TagRecord{"string": {{Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: props.Value + " <= $importInternal(\"_stringLength\")($input)", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}}}}
|
|
305
346
|
},
|
|
306
347
|
"maxLength": func(props struct {
|
|
307
348
|
Report func(msg string) any
|
|
308
349
|
Value string
|
|
309
350
|
}) metadataCommentTagFactory_TagRecord {
|
|
310
351
|
value := metadataCommentTagFactory_parse_number(props)
|
|
311
|
-
return metadataCommentTagFactory_TagRecord{"string": {{Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$input
|
|
352
|
+
return metadataCommentTagFactory_TagRecord{"string": {{Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLength\")($input) <= " + props.Value, Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}}}}
|
|
312
353
|
},
|
|
313
354
|
}
|
|
314
355
|
|
|
@@ -407,28 +448,23 @@ func metadataCommentTagFactory_parse_type(props struct {
|
|
|
407
448
|
"number": {{Name: "Type<" + strconv.Quote(value) + ">", Target: "number", Kind: "type", Value: value, Validate: validate, Exclusive: metadataCommentTagFactory_exclusive("type"), Schema: numberSchema}},
|
|
408
449
|
}
|
|
409
450
|
if value == "int64" || value == "uint64" {
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
451
|
+
// `@type int64` and `bigint & Type<"int64">` are two spellings of one
|
|
452
|
+
// constraint, so both delegate to the helper that holds the exact inclusive
|
|
453
|
+
// bound. The bigint form used to emit `true` for int64 and a lower bound
|
|
454
|
+
// only for uint64, which certified any magnitude at all -- and
|
|
455
|
+
// `typia.protobuf.encode` then truncated the value to 64 bits, so a
|
|
456
|
+
// certified value came back different.
|
|
413
457
|
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
// lives in `@typia/interface`, which `typia` depends on through a caret
|
|
418
|
-
// range that only floats upward -- so an older `typia` installs a newer
|
|
419
|
-
// `@typia/interface` and emits an import its own runtime never shipped
|
|
420
|
-
// (#2330). `isTypeInt64` and `isTypeUint64` are safe there because
|
|
421
|
-
// `@typia/interface` 13.0.0 already named them and `typia` 13.0.0 already
|
|
422
|
-
// shipped them; a helper introduced later is not, and inside one major
|
|
423
|
-
// there is no range that prevents the pairing.
|
|
458
|
+
// The bound has to live in a helper rather than an inline literal here
|
|
459
|
+
// because it is only exact as a string: `BigInt(9223372036854775807)` rounds
|
|
460
|
+
// its `number` literal to 2**63 before BigInt ever parses it.
|
|
424
461
|
//
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
bigintValidate := "BigInt(0) <= $input"
|
|
462
|
+
// See the `metadataCommentTagFactory_PARSER` doc comment: naming a helper
|
|
463
|
+
// here is safe only under a major bump, and `_isTypeInt64Bigint` /
|
|
464
|
+
// `_isTypeUint64Bigint` first shipped in 13.1.19.
|
|
465
|
+
bigintValidate := "$importInternal(\"isTypeUint64Bigint\")($input)"
|
|
430
466
|
if value == "int64" {
|
|
431
|
-
bigintValidate = "
|
|
467
|
+
bigintValidate = "$importInternal(\"isTypeInt64Bigint\")($input)"
|
|
432
468
|
}
|
|
433
469
|
record["bigint"] = []schemametadata.IMetadataTypeTag{
|
|
434
470
|
{Name: "Type<" + strconv.Quote(value) + ">", Target: "bigint", Kind: "type", Value: value, Validate: bigintValidate, Exclusive: metadataCommentTagFactory_exclusive("type"), Schema: bigintSchema},
|
|
@@ -452,7 +488,7 @@ func metadataCommentTagFactory_numeric(props struct {
|
|
|
452
488
|
"number": {{Name: name + "<" + props.Value + ">", Target: "number", Kind: kind, Value: number, Validate: numberValidate, Exclusive: exclusive, Schema: map[string]any{kind: number}}},
|
|
453
489
|
}
|
|
454
490
|
if integer != nil {
|
|
455
|
-
record["bigint"] = []schemametadata.IMetadataTypeTag{{Name: name + "<" + props.Value + "n>", Target: "bigint", Kind: kind, Value:
|
|
491
|
+
record["bigint"] = []schemametadata.IMetadataTypeTag{{Name: name + "<" + props.Value + "n>", Target: "bigint", Kind: kind, Value: *integer, Validate: bigintValidate, Exclusive: exclusive, Schema: map[string]any{kind: number}}}
|
|
456
492
|
}
|
|
457
493
|
return record
|
|
458
494
|
}
|
|
@@ -473,7 +509,7 @@ func metadataCommentTagFactory_parse_integer(props struct {
|
|
|
473
509
|
Report func(msg string) any
|
|
474
510
|
Unsigned bool
|
|
475
511
|
Value string
|
|
476
|
-
}) *
|
|
512
|
+
}) *int64 {
|
|
477
513
|
parsedAny := metadataCommentTagFactory_parse_number(struct {
|
|
478
514
|
Report func(msg string) any
|
|
479
515
|
Value string
|
|
@@ -490,10 +526,43 @@ func metadataCommentTagFactory_parse_integer(props struct {
|
|
|
490
526
|
props.Report("invalid unsigned integer")
|
|
491
527
|
return nil
|
|
492
528
|
}
|
|
493
|
-
|
|
529
|
+
// A float64 outside the destination range converts by an
|
|
530
|
+
// implementation-defined rule, not by wrapping: on amd64 both `1e19` and
|
|
531
|
+
// `-1e19` become -9223372036854775808, so a magnitude and its negation
|
|
532
|
+
// collapse to the same wrong number. Reject instead, because a tag whose value
|
|
533
|
+
// cannot be carried is a tag whose meaning is already lost.
|
|
534
|
+
if parsed < metadataCommentTagFactory_INT64_MINIMUM || parsed >= metadataCommentTagFactory_INT64_EXCLUSIVE_MAXIMUM {
|
|
535
|
+
props.Report("integer out of range")
|
|
536
|
+
return nil
|
|
537
|
+
}
|
|
538
|
+
value := int64(parsed)
|
|
494
539
|
return &value
|
|
495
540
|
}
|
|
496
541
|
|
|
542
|
+
// The int64 range as float64 bounds.
|
|
543
|
+
//
|
|
544
|
+
// int64 rather than int, because `int` is 32 bits on the 32-bit platforms ttsc
|
|
545
|
+
// publishes a binary for (`@ttsc/linux-arm`). Bounding by `int` would make the
|
|
546
|
+
// same `@minimum 3000000000` on a `bigint` compile on x64 and either be rejected
|
|
547
|
+
// or silently converted out of range on arm, and the value is already widened to
|
|
548
|
+
// int64 at its only numeric use. Bounding by int64 is what the tag means on
|
|
549
|
+
// every platform.
|
|
550
|
+
//
|
|
551
|
+
// The maximum is exclusive and spelled 2**63 rather than math.MaxInt64, because
|
|
552
|
+
// the inclusive form does not survive the comparison. An untyped constant
|
|
553
|
+
// 9223372036854775807.0 rounds to 2**63 once it meets a float64 operand, so
|
|
554
|
+
// `parsed > max` compares 2**63 against 2**63 and lets the one value through
|
|
555
|
+
// that int64 cannot hold. A strict `>=` against 2**63 is exact instead: no
|
|
556
|
+
// float64 lies between 2**63 - 1024 and 2**63, so rejecting from 2**63 up
|
|
557
|
+
// rejects exactly the magnitudes int64 cannot represent.
|
|
558
|
+
//
|
|
559
|
+
// The minimum needs no such care -- -2**63 is math.MinInt64 and is exactly
|
|
560
|
+
// representable as a float64.
|
|
561
|
+
const (
|
|
562
|
+
metadataCommentTagFactory_INT64_MINIMUM = -9223372036854775808.0
|
|
563
|
+
metadataCommentTagFactory_INT64_EXCLUSIVE_MAXIMUM = 9223372036854775808.0
|
|
564
|
+
)
|
|
565
|
+
|
|
497
566
|
func metadataCommentTagFactory_includes(values []string, target string) bool {
|
|
498
567
|
for _, value := range values {
|
|
499
568
|
if value == target {
|
|
@@ -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 != "$input
|
|
128
|
-
t.Fatalf("decimal multipleOf must
|
|
127
|
+
if decimal["number"][0].Validate != "$importInternal(\"_isMultipleOf\")($input, 0.01)" {
|
|
128
|
+
t.Fatalf("decimal multipleOf must use the shared exact helper: %s", decimal["number"][0].Validate)
|
|
129
129
|
}
|
|
130
130
|
invalidBefore := len(reports)
|
|
131
131
|
_ = metadataCommentTagFactory_parse_number(struct {
|