typia 13.3.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/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/transform.mjs +1 -1
- 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/template_literal_type_tags_transform_test.go +3 -1
- package/native/cmd/ttsc-typia/transform.go +2 -2
- 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/package.json +3 -12
- package/src/internal/_randomPattern.ts +18 -3
- package/src/internal/_randomStringLength.ts +9 -2
- 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
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports._randomPattern = void 0;
|
|
7
7
|
const randexp_1 = __importDefault(require("randexp"));
|
|
8
|
+
const _stringLength_1 = require("./_stringLength");
|
|
8
9
|
const _randomPattern = (regex, props) => {
|
|
9
10
|
const r = new randexp_1.default(regex);
|
|
10
11
|
const min = props === null || props === void 0 ? void 0 : props.minLength;
|
|
@@ -16,13 +17,29 @@ const _randomPattern = (regex, props) => {
|
|
|
16
17
|
r.max = max;
|
|
17
18
|
else if (min !== undefined && min * 2 > r.max)
|
|
18
19
|
r.max = min * 2;
|
|
20
|
+
// The verification counts characters, not UTF-16 code units, because that is
|
|
21
|
+
// what `MinLength` and `MaxLength` compare against. `Pattern` compiles to a
|
|
22
|
+
// flagless `RegExp`, so a quantifier binds one code unit wherever its atom is
|
|
23
|
+
// a bare astral character: under `^😀+$` the `+` repeats the trailing low
|
|
24
|
+
// surrogate alone, and RandExp draws `\ud83d` + k * `\ude00` -- k characters
|
|
25
|
+
// in k + 1 code units. Measuring in code units accepted a two-character draw
|
|
26
|
+
// for `MinLength<3>` and handed back a value the generated validator rejects.
|
|
27
|
+
// Group the atom (`^(?:😀)+$`) and the arithmetic changes to 2k units for k
|
|
28
|
+
// characters; the two counts still differ, which is the point.
|
|
29
|
+
//
|
|
30
|
+
// The count is taken only after the pattern matches, and only when a bound
|
|
31
|
+
// exists, so an unbounded draw still walks nothing.
|
|
19
32
|
const bounded = min !== undefined || max !== undefined;
|
|
20
33
|
const limit = bounded ? 1024 : 10;
|
|
21
34
|
for (let i = 0; i < limit; ++i) {
|
|
22
35
|
const str = r.gen();
|
|
23
|
-
if (regex.test(str)
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
if (regex.test(str) === false)
|
|
37
|
+
continue;
|
|
38
|
+
if (bounded === false)
|
|
39
|
+
return str;
|
|
40
|
+
const length = (0, _stringLength_1._stringLength)(str);
|
|
41
|
+
if ((min === undefined || length >= min) &&
|
|
42
|
+
(max === undefined || length <= max))
|
|
26
43
|
return str;
|
|
27
44
|
}
|
|
28
45
|
throw new Error(bounded
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_randomPattern.js","sourceRoot":"","sources":["../../src/internal/_randomPattern.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA8B;
|
|
1
|
+
{"version":3,"file":"_randomPattern.js","sourceRoot":"","sources":["../../src/internal/_randomPattern.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA8B;AAG9B,mDAAgD;AAEzC,MAAM,cAAc,GAAG,CAC5B,KAAa,EACb,KAAqB,EACb,EAAE;IACV,MAAM,CAAC,GAAY,IAAI,iBAAO,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,GAAG,GAAuB,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,CAAC;IACjD,MAAM,GAAG,GAAuB,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,CAAC;IAEjD,6EAA6E;IAC7E,8EAA8E;IAC9E,6EAA6E;IAC7E,IAAI,GAAG,KAAK,SAAS;QAAE,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;SAC9B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG;QAAE,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;IAE/D,6EAA6E;IAC7E,4EAA4E;IAC5E,8EAA8E;IAC9E,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,+DAA+D;IAC/D,EAAE;IACF,2EAA2E;IAC3E,oDAAoD;IACpD,MAAM,OAAO,GAAY,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,CAAC;IAChE,MAAM,KAAK,GAAW,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC;QACvC,MAAM,GAAG,GAAW,CAAC,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK;YAAE,SAAS;QACxC,IAAI,OAAO,KAAK,KAAK;YAAE,OAAO,GAAG,CAAC;QAClC,MAAM,MAAM,GAAW,IAAA,6BAAa,EAAC,GAAG,CAAC,CAAC;QAC1C,IACE,CAAC,GAAG,KAAK,SAAS,IAAI,MAAM,IAAI,GAAG,CAAC;YACpC,CAAC,GAAG,KAAK,SAAS,IAAI,MAAM,IAAI,GAAG,CAAC;YAEpC,OAAO,GAAG,CAAC;IACf,CAAC;IACD,MAAM,IAAI,KAAK,CACb,OAAO;QACL,CAAC,CAAC,kFAAkF;QACpF,CAAC,CAAC,0DAA0D,CAC/D,CAAC;AACJ,CAAC,CAAC;AA5CW,QAAA,cAAc,GAAd,cAAc,CA4CzB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _stringLength } from "./_stringLength.mjs";
|
|
1
2
|
import RandExp from "randexp";
|
|
2
3
|
//#region src/internal/_randomPattern.ts
|
|
3
4
|
const _randomPattern = (regex, props) => {
|
|
@@ -10,7 +11,10 @@ const _randomPattern = (regex, props) => {
|
|
|
10
11
|
const limit = bounded ? 1024 : 10;
|
|
11
12
|
for (let i = 0; i < limit; ++i) {
|
|
12
13
|
const str = r.gen();
|
|
13
|
-
if (regex.test(str)
|
|
14
|
+
if (regex.test(str) === false) continue;
|
|
15
|
+
if (bounded === false) return str;
|
|
16
|
+
const length = _stringLength(str);
|
|
17
|
+
if ((min === void 0 || length >= min) && (max === void 0 || length <= max)) return str;
|
|
14
18
|
}
|
|
15
19
|
throw new Error(bounded ? "unable to generate a random string matching the pattern within the length range." : "unable to generate a random string matching the pattern.");
|
|
16
20
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_randomPattern.mjs","names":[],"sources":["../../src/internal/_randomPattern.ts"],"sourcesContent":["import RandExp from \"randexp\";\n\nimport { _ILengthProps } from \"./_randomStringLength\";\n\nexport const _randomPattern = (\n regex: RegExp,\n props?: _ILengthProps,\n): string => {\n const r: RandExp = new RandExp(regex);\n const min: number | undefined = props?.minLength;\n const max: number | undefined = props?.maxLength;\n\n // RandExp expands an unbounded quantifier to `randInt(token.min, token.min +\n // r.max)`, so steering `r.max` biases the generated length toward the window.\n // There is no matching floor control, so the length is still verified below.\n if (max !== undefined) r.max = max;\n else if (min !== undefined && min * 2 > r.max) r.max = min * 2;\n\n const bounded: boolean = min !== undefined || max !== undefined;\n const limit: number = bounded ? 1024 : 10;\n for (let i: number = 0; i < limit; ++i) {\n const str: string = r.gen();\n if (
|
|
1
|
+
{"version":3,"file":"_randomPattern.mjs","names":[],"sources":["../../src/internal/_randomPattern.ts"],"sourcesContent":["import RandExp from \"randexp\";\n\nimport { _ILengthProps } from \"./_randomStringLength\";\nimport { _stringLength } from \"./_stringLength\";\n\nexport const _randomPattern = (\n regex: RegExp,\n props?: _ILengthProps,\n): string => {\n const r: RandExp = new RandExp(regex);\n const min: number | undefined = props?.minLength;\n const max: number | undefined = props?.maxLength;\n\n // RandExp expands an unbounded quantifier to `randInt(token.min, token.min +\n // r.max)`, so steering `r.max` biases the generated length toward the window.\n // There is no matching floor control, so the length is still verified below.\n if (max !== undefined) r.max = max;\n else if (min !== undefined && min * 2 > r.max) r.max = min * 2;\n\n // The verification counts characters, not UTF-16 code units, because that is\n // what `MinLength` and `MaxLength` compare against. `Pattern` compiles to a\n // flagless `RegExp`, so a quantifier binds one code unit wherever its atom is\n // a bare astral character: under `^😀+$` the `+` repeats the trailing low\n // surrogate alone, and RandExp draws `\\ud83d` + k * `\\ude00` -- k characters\n // in k + 1 code units. Measuring in code units accepted a two-character draw\n // for `MinLength<3>` and handed back a value the generated validator rejects.\n // Group the atom (`^(?:😀)+$`) and the arithmetic changes to 2k units for k\n // characters; the two counts still differ, which is the point.\n //\n // The count is taken only after the pattern matches, and only when a bound\n // exists, so an unbounded draw still walks nothing.\n const bounded: boolean = min !== undefined || max !== undefined;\n const limit: number = bounded ? 1024 : 10;\n for (let i: number = 0; i < limit; ++i) {\n const str: string = r.gen();\n if (regex.test(str) === false) continue;\n if (bounded === false) return str;\n const length: number = _stringLength(str);\n if (\n (min === undefined || length >= min) &&\n (max === undefined || length <= max)\n )\n return str;\n }\n throw new Error(\n bounded\n ? \"unable to generate a random string matching the pattern within the length range.\"\n : \"unable to generate a random string matching the pattern.\",\n );\n};\n"],"mappings":";;;AAKA,MAAa,kBACX,OACA,UACW;CACX,MAAM,IAAa,IAAI,QAAQ,KAAK;CACpC,MAAM,MAA0B,OAAO;CACvC,MAAM,MAA0B,OAAO;CAKvC,IAAI,QAAQ,KAAA,GAAW,EAAE,MAAM;MAC1B,IAAI,QAAQ,KAAA,KAAa,MAAM,IAAI,EAAE,KAAK,EAAE,MAAM,MAAM;CAc7D,MAAM,UAAmB,QAAQ,KAAA,KAAa,QAAQ,KAAA;CACtD,MAAM,QAAgB,UAAU,OAAO;CACvC,KAAK,IAAI,IAAY,GAAG,IAAI,OAAO,EAAE,GAAG;EACtC,MAAM,MAAc,EAAE,IAAI;EAC1B,IAAI,MAAM,KAAK,GAAG,MAAM,OAAO;EAC/B,IAAI,YAAY,OAAO,OAAO;EAC9B,MAAM,SAAiB,cAAc,GAAG;EACxC,KACG,QAAQ,KAAA,KAAa,UAAU,SAC/B,QAAQ,KAAA,KAAa,UAAU,MAEhC,OAAO;CACX;CACA,MAAM,IAAI,MACR,UACI,qFACA,0DACN;AACF"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports._randomFormatLength = exports._randomLengthPick = exports._randomLengthWindow = exports._randomSegmentLength = exports._RANDOM_LENGTH_ERROR = void 0;
|
|
4
4
|
const _randomInteger_1 = require("./_randomInteger");
|
|
5
5
|
const _randomString_1 = require("./_randomString");
|
|
6
|
+
const _stringLength_1 = require("./_stringLength");
|
|
6
7
|
exports._RANDOM_LENGTH_ERROR = "unable to generate a random string satisfying both the format and the length constraints.";
|
|
7
8
|
/**
|
|
8
9
|
* Builds the variable segment of a fixed-shape format string so that the total
|
|
@@ -74,8 +75,14 @@ const _randomFormatLength = (props, generate) => {
|
|
|
74
75
|
return generate();
|
|
75
76
|
for (let i = 0; i < 256; ++i) {
|
|
76
77
|
const value = generate();
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
// Characters, not UTF-16 code units: `minLength` and `maxLength` are the
|
|
79
|
+
// bounds the generated validator compares in characters. `uuid` and `date`,
|
|
80
|
+
// the only two shapes routed through here, are ASCII, so the two counts
|
|
81
|
+
// agree for them today -- this keeps one measure so a later non-ASCII
|
|
82
|
+
// format cannot reintroduce the divergence unnoticed.
|
|
83
|
+
const length = (0, _stringLength_1._stringLength)(value);
|
|
84
|
+
if ((props.minLength === undefined || length >= props.minLength) &&
|
|
85
|
+
(props.maxLength === undefined || length <= props.maxLength))
|
|
79
86
|
return value;
|
|
80
87
|
}
|
|
81
88
|
throw new Error(exports._RANDOM_LENGTH_ERROR);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_randomStringLength.js","sourceRoot":"","sources":["../../src/internal/_randomStringLength.ts"],"names":[],"mappings":";;;AAAA,qDAAkD;AAClD,mDAAgD;AAEnC,QAAA,oBAAoB,GAC/B,2FAA2F,CAAC;AAe9F;;;;;;;;;GASG;AACI,MAAM,oBAAoB,GAAG,CAClC,KAAgC,EAChC,KAAa,EACb,QAAgB,EAChB,OAAe,EACP,EAAE;IACV,IAAI,GAAG,GACL,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,MAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;IACrE,IAAI,GAAG,GAAG,OAAO;QAAE,GAAG,GAAG,OAAO,CAAC;IACjC,sEAAsE;IACtE,wEAAwE;IACxE,MAAM,IAAI,GACR,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,MAAK,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;IAC5E,IAAI,IAAI,GAAG,GAAG,IAAI,IAAI,GAAG,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,QAAA,oBAAoB,CAAC,CAAC;IACxE,OAAO,IAAA,6BAAa,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5E,CAAC,CAAC;AAfW,QAAA,oBAAoB,GAApB,oBAAoB,CAe/B;AAEF;;;;;;;;;;;;;;;GAeG;AACI,MAAM,mBAAmB,GAAG,CACjC,KAAgC,EAChC,MAA8D,EAC/B,EAAE;;IACjC,MAAM,OAAO,SAAW,MAAM,CAAC,OAAO,mCAAI,MAAM,CAAC,gBAAgB,CAAC;IAClE,MAAM,GAAG,GAAW,IAAI,CAAC,GAAG,OAC1B,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,mCAAI,MAAM,CAAC,OAAO,EAClC,MAAM,CAAC,OAAO,CACf,CAAC;IACF,MAAM,IAAI,GAAW,IAAI,CAAC,GAAG,OAC3B,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,mCAAI,GAAG,GAAG,OAAC,MAAM,CAAC,MAAM,mCAAI,CAAC,CAAC,EAC9C,OAAO,CACR,CAAC;IACF,IAAI,GAAG,GAAG,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,QAAA,oBAAoB,CAAC,CAAC;IACtD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACvB,CAAC,CAAC;AAfW,QAAA,mBAAmB,GAAnB,mBAAmB,CAe9B;AAEF,8EAA8E;AACvE,MAAM,iBAAiB,GAAG,CAAC,MAGjC,EAAU,EAAE,CACX,IAAA,+BAAc,EAAC;IACb,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,MAAM,CAAC,GAAG;IACnB,OAAO,EAAE,MAAM,CAAC,IAAI;CACrB,CAAC,CAAC;AARQ,QAAA,iBAAiB,GAAjB,iBAAiB,CAQzB;AAEL;;;;;;;;;GASG;AACI,MAAM,mBAAmB,GAAG,CACjC,KAAgC,EAChC,QAAsB,EACd,EAAE;IACV,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,MAAK,SAAS,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,MAAK,SAAS;QAClE,OAAO,QAAQ,EAAE,CAAC;IACpB,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;QACrC,MAAM,KAAK,GAAW,QAAQ,EAAE,CAAC;QACjC,IACE,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,
|
|
1
|
+
{"version":3,"file":"_randomStringLength.js","sourceRoot":"","sources":["../../src/internal/_randomStringLength.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _randomInteger } from "./_randomInteger.mjs";
|
|
2
2
|
import { _randomString } from "./_randomString.mjs";
|
|
3
|
+
import { _stringLength } from "./_stringLength.mjs";
|
|
3
4
|
//#region src/internal/_randomStringLength.ts
|
|
4
5
|
const _RANDOM_LENGTH_ERROR = "unable to generate a random string satisfying both the format and the length constraints.";
|
|
5
6
|
/**
|
|
@@ -69,7 +70,8 @@ const _randomFormatLength = (props, generate) => {
|
|
|
69
70
|
if (props?.minLength === void 0 && props?.maxLength === void 0) return generate();
|
|
70
71
|
for (let i = 0; i < 256; ++i) {
|
|
71
72
|
const value = generate();
|
|
72
|
-
|
|
73
|
+
const length = _stringLength(value);
|
|
74
|
+
if ((props.minLength === void 0 || length >= props.minLength) && (props.maxLength === void 0 || length <= props.maxLength)) return value;
|
|
73
75
|
}
|
|
74
76
|
throw new Error(_RANDOM_LENGTH_ERROR);
|
|
75
77
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_randomStringLength.mjs","names":[],"sources":["../../src/internal/_randomStringLength.ts"],"sourcesContent":["import { _randomInteger } from \"./_randomInteger\";\nimport { _randomString } from \"./_randomString\";\n\nexport const _RANDOM_LENGTH_ERROR =\n \"unable to generate a random string satisfying both the format and the length constraints.\";\n\n/**\n * Length bounds forwarded to a string format or pattern generator.\n *\n * `typia.random<T>()` passes this object to the `_randomFormat*` and\n * `_randomPattern` helpers whenever the string leaf also carries a `MinLength`\n * or `MaxLength` tag, so a constrained format or pattern is generated at a\n * length its own validator accepts (issue #2189).\n */\nexport interface _ILengthProps {\n minLength?: number;\n maxLength?: number;\n}\n\n/**\n * Builds the variable segment of a fixed-shape format string so that the total\n * length `fixed + segment.length` lands inside the requested window.\n *\n * `fixed` is the number of characters the format always contributes around the\n * segment, `fallback` is the segment length used when the leaf is unconstrained\n * on that side, and `minimum` is the smallest segment the format still\n * validates with. Throws when the window cannot be satisfied by this format's\n * shape (e.g. `Format<\"email\"> & MaxLength<3>`).\n */\nexport const _randomSegmentLength = (\n props: _ILengthProps | undefined,\n fixed: number,\n fallback: number,\n minimum: number,\n): string => {\n let low: number =\n props?.minLength === undefined ? minimum : props.minLength - fixed;\n if (low < minimum) low = minimum;\n // With only a lower bound, spread the segment across `fallback` extra\n // characters so the generated length varies, mirroring `_randomString`.\n const high: number =\n props?.maxLength === undefined ? low + fallback : props.maxLength - fixed;\n if (high < low || high < minimum) throw new Error(_RANDOM_LENGTH_ERROR);\n return _randomString({ type: \"string\", minLength: low, maxLength: high });\n};\n\n/**\n * Intersects the requested length window with the lengths a format's grammar\n * can express, and draws one of them.\n *\n * `minimum` and `maximum` are the shortest and longest values the format admits\n * — omit `maximum` when the grammar is open above — and `spread` is how far\n * past the floor an unbounded request may reach, so an open side still varies\n * the way `_randomString` does. The caller draws from the returned window and\n * applies whatever step its grammar has (base64 lengths are multiples of four,\n * a fractional second needs at least one digit).\n *\n * Throws only when the window and the grammar do not overlap at all. That is\n * the distinction the retry driver below cannot make: it redraws one shape, so\n * it gives up on every length that shape never emits even when the format\n * itself accepts one (issue #2284).\n */\nexport const _randomLengthWindow = (\n props: _ILengthProps | undefined,\n format: { minimum: number; maximum?: number; spread?: number },\n): { low: number; high: number } => {\n const ceiling: number = format.maximum ?? Number.MAX_SAFE_INTEGER;\n const low: number = Math.max(\n props?.minLength ?? format.minimum,\n format.minimum,\n );\n const high: number = Math.min(\n props?.maxLength ?? low + (format.spread ?? 0),\n ceiling,\n );\n if (low > high) throw new Error(_RANDOM_LENGTH_ERROR);\n return { low, high };\n};\n\n/** Draws a length inside a window produced by {@link _randomLengthWindow}. */\nexport const _randomLengthPick = (window: {\n low: number;\n high: number;\n}): number =>\n _randomInteger({\n type: \"integer\",\n minimum: window.low,\n maximum: window.high,\n });\n\n/**\n * Wraps a fixed-length format generator (uuid, date) with the requested length\n * window.\n *\n * Use it only where the grammar admits exactly one length, so a window that\n * excludes it is genuinely unsatisfiable. A format that can express more than\n * one length must build its value at a length drawn from\n * {@link _randomLengthWindow} instead; redrawing one shape cannot reach a length\n * that shape never emits.\n */\nexport const _randomFormatLength = (\n props: _ILengthProps | undefined,\n generate: () => string,\n): string => {\n if (props?.minLength === undefined && props?.maxLength === undefined)\n return generate();\n for (let i: number = 0; i < 256; ++i) {\n const value: string = generate();\n if (\n (props.minLength === undefined ||
|
|
1
|
+
{"version":3,"file":"_randomStringLength.mjs","names":[],"sources":["../../src/internal/_randomStringLength.ts"],"sourcesContent":["import { _randomInteger } from \"./_randomInteger\";\nimport { _randomString } from \"./_randomString\";\nimport { _stringLength } from \"./_stringLength\";\n\nexport const _RANDOM_LENGTH_ERROR =\n \"unable to generate a random string satisfying both the format and the length constraints.\";\n\n/**\n * Length bounds forwarded to a string format or pattern generator.\n *\n * `typia.random<T>()` passes this object to the `_randomFormat*` and\n * `_randomPattern` helpers whenever the string leaf also carries a `MinLength`\n * or `MaxLength` tag, so a constrained format or pattern is generated at a\n * length its own validator accepts (issue #2189).\n */\nexport interface _ILengthProps {\n minLength?: number;\n maxLength?: number;\n}\n\n/**\n * Builds the variable segment of a fixed-shape format string so that the total\n * length `fixed + segment.length` lands inside the requested window.\n *\n * `fixed` is the number of characters the format always contributes around the\n * segment, `fallback` is the segment length used when the leaf is unconstrained\n * on that side, and `minimum` is the smallest segment the format still\n * validates with. Throws when the window cannot be satisfied by this format's\n * shape (e.g. `Format<\"email\"> & MaxLength<3>`).\n */\nexport const _randomSegmentLength = (\n props: _ILengthProps | undefined,\n fixed: number,\n fallback: number,\n minimum: number,\n): string => {\n let low: number =\n props?.minLength === undefined ? minimum : props.minLength - fixed;\n if (low < minimum) low = minimum;\n // With only a lower bound, spread the segment across `fallback` extra\n // characters so the generated length varies, mirroring `_randomString`.\n const high: number =\n props?.maxLength === undefined ? low + fallback : props.maxLength - fixed;\n if (high < low || high < minimum) throw new Error(_RANDOM_LENGTH_ERROR);\n return _randomString({ type: \"string\", minLength: low, maxLength: high });\n};\n\n/**\n * Intersects the requested length window with the lengths a format's grammar\n * can express, and draws one of them.\n *\n * `minimum` and `maximum` are the shortest and longest values the format admits\n * — omit `maximum` when the grammar is open above — and `spread` is how far\n * past the floor an unbounded request may reach, so an open side still varies\n * the way `_randomString` does. The caller draws from the returned window and\n * applies whatever step its grammar has (base64 lengths are multiples of four,\n * a fractional second needs at least one digit).\n *\n * Throws only when the window and the grammar do not overlap at all. That is\n * the distinction the retry driver below cannot make: it redraws one shape, so\n * it gives up on every length that shape never emits even when the format\n * itself accepts one (issue #2284).\n */\nexport const _randomLengthWindow = (\n props: _ILengthProps | undefined,\n format: { minimum: number; maximum?: number; spread?: number },\n): { low: number; high: number } => {\n const ceiling: number = format.maximum ?? Number.MAX_SAFE_INTEGER;\n const low: number = Math.max(\n props?.minLength ?? format.minimum,\n format.minimum,\n );\n const high: number = Math.min(\n props?.maxLength ?? low + (format.spread ?? 0),\n ceiling,\n );\n if (low > high) throw new Error(_RANDOM_LENGTH_ERROR);\n return { low, high };\n};\n\n/** Draws a length inside a window produced by {@link _randomLengthWindow}. */\nexport const _randomLengthPick = (window: {\n low: number;\n high: number;\n}): number =>\n _randomInteger({\n type: \"integer\",\n minimum: window.low,\n maximum: window.high,\n });\n\n/**\n * Wraps a fixed-length format generator (uuid, date) with the requested length\n * window.\n *\n * Use it only where the grammar admits exactly one length, so a window that\n * excludes it is genuinely unsatisfiable. A format that can express more than\n * one length must build its value at a length drawn from\n * {@link _randomLengthWindow} instead; redrawing one shape cannot reach a length\n * that shape never emits.\n */\nexport const _randomFormatLength = (\n props: _ILengthProps | undefined,\n generate: () => string,\n): string => {\n if (props?.minLength === undefined && props?.maxLength === undefined)\n return generate();\n for (let i: number = 0; i < 256; ++i) {\n const value: string = generate();\n // Characters, not UTF-16 code units: `minLength` and `maxLength` are the\n // bounds the generated validator compares in characters. `uuid` and `date`,\n // the only two shapes routed through here, are ASCII, so the two counts\n // agree for them today -- this keeps one measure so a later non-ASCII\n // format cannot reintroduce the divergence unnoticed.\n const length: number = _stringLength(value);\n if (\n (props.minLength === undefined || length >= props.minLength) &&\n (props.maxLength === undefined || length <= props.maxLength)\n )\n return value;\n }\n throw new Error(_RANDOM_LENGTH_ERROR);\n};\n"],"mappings":";;;;AAIA,MAAa,uBACX;;;;;;;;;;;AAyBF,MAAa,wBACX,OACA,OACA,UACA,YACW;CACX,IAAI,MACF,OAAO,cAAc,KAAA,IAAY,UAAU,MAAM,YAAY;CAC/D,IAAI,MAAM,SAAS,MAAM;CAGzB,MAAM,OACJ,OAAO,cAAc,KAAA,IAAY,MAAM,WAAW,MAAM,YAAY;CACtE,IAAI,OAAO,OAAO,OAAO,SAAS,MAAM,IAAI,MAAM,oBAAoB;CACtE,OAAO,cAAc;EAAE,MAAM;EAAU,WAAW;EAAK,WAAW;CAAK,CAAC;AAC1E;;;;;;;;;;;;;;;;;AAkBA,MAAa,uBACX,OACA,WACkC;CAClC,MAAM,UAAkB,OAAO,WAAW,OAAO;CACjD,MAAM,MAAc,KAAK,IACvB,OAAO,aAAa,OAAO,SAC3B,OAAO,OACT;CACA,MAAM,OAAe,KAAK,IACxB,OAAO,aAAa,OAAO,OAAO,UAAU,IAC5C,OACF;CACA,IAAI,MAAM,MAAM,MAAM,IAAI,MAAM,oBAAoB;CACpD,OAAO;EAAE;EAAK;CAAK;AACrB;;AAGA,MAAa,qBAAqB,WAIhC,eAAe;CACb,MAAM;CACN,SAAS,OAAO;CAChB,SAAS,OAAO;AAClB,CAAC;;;;;;;;;;;AAYH,MAAa,uBACX,OACA,aACW;CACX,IAAI,OAAO,cAAc,KAAA,KAAa,OAAO,cAAc,KAAA,GACzD,OAAO,SAAS;CAClB,KAAK,IAAI,IAAY,GAAG,IAAI,KAAK,EAAE,GAAG;EACpC,MAAM,QAAgB,SAAS;EAM/B,MAAM,SAAiB,cAAc,KAAK;EAC1C,KACG,MAAM,cAAc,KAAA,KAAa,UAAU,MAAM,eACjD,MAAM,cAAc,KAAA,KAAa,UAAU,MAAM,YAElD,OAAO;CACX;CACA,MAAM,IAAI,MAAM,oBAAoB;AACtC"}
|
package/lib/transform.mjs
CHANGED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"os/exec"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestDynamicKeyTagsTransform verifies a type tag on a dynamic key is enforced.
|
|
12
|
+
//
|
|
13
|
+
// `Check_dynamic_key` has always held a complete implementation: it filters the
|
|
14
|
+
// key's tag rows through `check_dynamic_key_fully_validated_tag_rows` and routes
|
|
15
|
+
// a tagged string atomic into `Check_string`, a tagged numeric one into
|
|
16
|
+
// `Check_number`. Nothing reached it, so `[key: string & tags.MinLength<3>]`
|
|
17
|
+
// accepted a two-character key and every other key tag was equally inert
|
|
18
|
+
// (#2347).
|
|
19
|
+
//
|
|
20
|
+
// Values were always checked, which is what made this invisible: the
|
|
21
|
+
// `@typia/template` `DynamicTag` fixture declares exactly this shape and every
|
|
22
|
+
// one of its spoilers corrupts a value, never a key.
|
|
23
|
+
//
|
|
24
|
+
// 1. Transform a fixture whose index signatures carry a pattern, a length, and
|
|
25
|
+
// a numeric range on the key.
|
|
26
|
+
// 2. Require the emitted checker to contain a key predicate at all.
|
|
27
|
+
// 3. Execute runtime cases: a key violating its tag must be rejected, a key
|
|
28
|
+
// satisfying it accepted, and neither a plain `[key: string]` signature nor
|
|
29
|
+
// a template literal one may become constrained.
|
|
30
|
+
//
|
|
31
|
+
// The two negative controls carry the boundary. What the fix makes binding is a
|
|
32
|
+
// broken *tag* on a key the declaration covers; a key no signature's *type*
|
|
33
|
+
// covers is declared nowhere, which is what a surplus property is, and stays
|
|
34
|
+
// accepted. `test_validate_dynamic_key_surplus` pins that distinction against
|
|
35
|
+
// both spellings of the same predicate.
|
|
36
|
+
//
|
|
37
|
+
// The report such a key produces is its own concern. `Superfluous`, the report
|
|
38
|
+
// an extra property already used, says the property is not defined in the object
|
|
39
|
+
// type and advises removing it -- false and unhelpful when the property is
|
|
40
|
+
// declared and only its key broke a constraint. `InvalidKey` names the key type
|
|
41
|
+
// instead, and `test_validate_dynamic_key_report` covers the wording.
|
|
42
|
+
func TestDynamicKeyTagsTransform(t *testing.T) {
|
|
43
|
+
project := dynamicKeyTagsProject(t)
|
|
44
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
45
|
+
return runTransform([]string{
|
|
46
|
+
"--cwd", project,
|
|
47
|
+
"--tsconfig", "tsconfig.json",
|
|
48
|
+
"--file", "src/main.ts",
|
|
49
|
+
"--output", "js",
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
if code != 0 {
|
|
53
|
+
t.Fatalf("dynamic key tags transform failed: code=%d stderr=\n%s", code, errText)
|
|
54
|
+
}
|
|
55
|
+
// The key is read into a local before it is tested, so its predicate is the
|
|
56
|
+
// one place the emitted text can show the check exists at all.
|
|
57
|
+
if !strings.Contains(out, "RegExp(") || !strings.Contains(out, "_stringLength") {
|
|
58
|
+
t.Fatalf("emitted checker should test the dynamic key, not only its value:\n%s", out)
|
|
59
|
+
}
|
|
60
|
+
dynamicKeyTagsRunRuntimeCases(t, project, out)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func dynamicKeyTagsProject(t *testing.T) string {
|
|
64
|
+
t.Helper()
|
|
65
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
66
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
67
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
68
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
69
|
+
}
|
|
70
|
+
dir, err := os.MkdirTemp(base, "dynamic-key-tags-")
|
|
71
|
+
if err != nil {
|
|
72
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
73
|
+
}
|
|
74
|
+
t.Cleanup(func() {
|
|
75
|
+
_ = os.RemoveAll(dir)
|
|
76
|
+
})
|
|
77
|
+
src := filepath.Join(dir, "src")
|
|
78
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
79
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
80
|
+
}
|
|
81
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(dynamicKeyTagsTSConfig), 0o644); err != nil {
|
|
82
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
83
|
+
}
|
|
84
|
+
if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(dynamicKeyTagsSource), 0o644); err != nil {
|
|
85
|
+
t.Fatalf("write source: %v", err)
|
|
86
|
+
}
|
|
87
|
+
return dir
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
func dynamicKeyTagsRunRuntimeCases(t *testing.T, project string, js string) {
|
|
91
|
+
t.Helper()
|
|
92
|
+
node, err := exec.LookPath("node")
|
|
93
|
+
if err != nil {
|
|
94
|
+
t.Skip("node executable not found")
|
|
95
|
+
}
|
|
96
|
+
runtimeDir := filepath.Join(project, "runtime")
|
|
97
|
+
if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
|
|
98
|
+
t.Fatalf("mkdir runtime dir: %v", err)
|
|
99
|
+
}
|
|
100
|
+
ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
|
|
101
|
+
runtimeJS := ttscTypiaTestRewriteCommonJS(t, js)
|
|
102
|
+
if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
|
|
103
|
+
t.Fatalf("write runtime module: %v", err)
|
|
104
|
+
}
|
|
105
|
+
runner := filepath.Join(runtimeDir, "run.cjs")
|
|
106
|
+
if err := os.WriteFile(runner, []byte(dynamicKeyTagsRuntimeRunner), 0o644); err != nil {
|
|
107
|
+
t.Fatalf("write runtime runner: %v", err)
|
|
108
|
+
}
|
|
109
|
+
cmd := exec.Command(node, runner)
|
|
110
|
+
cmd.Dir = runtimeDir
|
|
111
|
+
output, err := cmd.CombinedOutput()
|
|
112
|
+
if err != nil {
|
|
113
|
+
t.Fatalf("dynamic key tags runtime cases failed: %v\n%s", err, output)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const dynamicKeyTagsTSConfig = `{
|
|
118
|
+
"compilerOptions": {
|
|
119
|
+
"target": "ES2022",
|
|
120
|
+
"module": "commonjs",
|
|
121
|
+
"moduleResolution": "bundler",
|
|
122
|
+
"ignoreDeprecations": "6.0",
|
|
123
|
+
"types": ["*"],
|
|
124
|
+
"esModuleInterop": true,
|
|
125
|
+
"strict": true,
|
|
126
|
+
"skipLibCheck": true
|
|
127
|
+
},
|
|
128
|
+
"include": ["src"]
|
|
129
|
+
}
|
|
130
|
+
`
|
|
131
|
+
|
|
132
|
+
const dynamicKeyTagsSource = `import typia, { tags } from "typia";
|
|
133
|
+
|
|
134
|
+
interface IPatternKey {
|
|
135
|
+
[key: string & tags.Pattern<"^ab+$">]: string;
|
|
136
|
+
}
|
|
137
|
+
interface ILengthKey {
|
|
138
|
+
[key: string & tags.MinLength<3>]: string;
|
|
139
|
+
}
|
|
140
|
+
interface INumericKey {
|
|
141
|
+
[key: number & tags.Minimum<0> & tags.Maximum<9>]: string;
|
|
142
|
+
}
|
|
143
|
+
interface IPlainKey {
|
|
144
|
+
[key: string]: string;
|
|
145
|
+
}
|
|
146
|
+
interface ITemplateKey {
|
|
147
|
+
[key: ` + "`" + `prefix_${string}` + "`" + `]: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const isPatternKey = typia.createIs<IPatternKey>();
|
|
151
|
+
export const isLengthKey = typia.createIs<ILengthKey>();
|
|
152
|
+
export const isNumericKey = typia.createIs<INumericKey>();
|
|
153
|
+
export const isPlainKey = typia.createIs<IPlainKey>();
|
|
154
|
+
export const isTemplateKey = typia.createIs<ITemplateKey>();
|
|
155
|
+
`
|
|
156
|
+
|
|
157
|
+
const dynamicKeyTagsRuntimeRunner = `const assert = require("assert");
|
|
158
|
+
const main = require("./main.cjs");
|
|
159
|
+
|
|
160
|
+
// A key that satisfies its tag is accepted; one that violates it is not. Both
|
|
161
|
+
// directions matter: before the fix every one of these returned true.
|
|
162
|
+
assert.strictEqual(main.isPatternKey({ abb: "x" }), true, "pattern key accepted");
|
|
163
|
+
assert.strictEqual(main.isPatternKey({ zzz: "x" }), false, "non-pattern key rejected");
|
|
164
|
+
|
|
165
|
+
assert.strictEqual(main.isLengthKey({ abc: "x" }), true, "long enough key accepted");
|
|
166
|
+
assert.strictEqual(main.isLengthKey({ ab: "x" }), false, "too short key rejected");
|
|
167
|
+
|
|
168
|
+
// The length is counted in characters, as MinLength means everywhere else: two
|
|
169
|
+
// astral characters are four code units but two characters, so they are short.
|
|
170
|
+
assert.strictEqual(
|
|
171
|
+
main.isLengthKey({ "\u{1f600}\u{1f600}\u{1f600}": "x" }),
|
|
172
|
+
true,
|
|
173
|
+
"three astral characters accepted",
|
|
174
|
+
);
|
|
175
|
+
assert.strictEqual(
|
|
176
|
+
main.isLengthKey({ "\u{1f600}\u{1f600}": "x" }),
|
|
177
|
+
false,
|
|
178
|
+
"two astral characters rejected",
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
assert.strictEqual(main.isNumericKey({ 5: "x" }), true, "in-range numeric key accepted");
|
|
182
|
+
assert.strictEqual(main.isNumericKey({ 10: "x" }), false, "out-of-range numeric key rejected");
|
|
183
|
+
assert.strictEqual(main.isNumericKey({ "-1": "x" }), false, "negative numeric key rejected");
|
|
184
|
+
|
|
185
|
+
// NEGATIVE CONTROL: an untagged signature stays unconstrained, so the fix
|
|
186
|
+
// constrains only what was declared.
|
|
187
|
+
assert.strictEqual(main.isPlainKey({ "anything at all": "x" }), true, "plain key accepted");
|
|
188
|
+
assert.strictEqual(main.isPlainKey({ "": "x" }), true, "empty plain key accepted");
|
|
189
|
+
|
|
190
|
+
// NEGATIVE CONTROL: a template literal constrains the key's *type*, not a tag on
|
|
191
|
+
// it, so a key outside it is declared nowhere -- a surplus property, which "is"
|
|
192
|
+
// accepts as it accepts any surplus property. Rejecting this one alongside a
|
|
193
|
+
// broken tag is what the first attempt at the fix did, and it made the
|
|
194
|
+
// equals-mode surplus report unreachable for the DynamicTemplate, DynamicUnion,
|
|
195
|
+
// and DynamicComposite shapes.
|
|
196
|
+
assert.strictEqual(main.isTemplateKey({ prefix_a: "x" }), true, "matching template key accepted");
|
|
197
|
+
assert.strictEqual(main.isTemplateKey({ prefix_a: 1 }), false, "matching template key with a wrong value rejected");
|
|
198
|
+
assert.strictEqual(
|
|
199
|
+
main.isTemplateKey({ prefix_a: "x", wrong: "y" }),
|
|
200
|
+
true,
|
|
201
|
+
"a key outside the template is surplus, not a violation",
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// The value is still checked, which it always was.
|
|
205
|
+
assert.strictEqual(main.isLengthKey({ abc: 1 }), false, "wrong value type rejected");
|
|
206
|
+
`
|
|
@@ -18,8 +18,8 @@ import (
|
|
|
18
18
|
// 1. Transform a fixture covering camelCase, snake_case, leading-underscore,
|
|
19
19
|
// consecutive-uppercase (the acronym run collapses, so `XMLParser` becomes
|
|
20
20
|
// `xmlparser`), and nested property names. The matching compile-time
|
|
21
|
-
// `KebabCase<T>` assertions live in the test-
|
|
22
|
-
//
|
|
21
|
+
// `KebabCase<T>` assertions live in the test-interface fixture, where ttsc
|
|
22
|
+
// enforces type diagnostics.
|
|
23
23
|
// 2. Require the emitted converter to reference the kebab-case keys.
|
|
24
24
|
// 3. Execute kebab, isKebab, assertKebab, and validateKebab runtime cases
|
|
25
25
|
// over valid and invalid inputs.
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
package main
|
|
5
5
|
|
|
6
6
|
import (
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"strings"
|
|
10
|
+
"testing"
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
// TestProjectCatalogTransformCoverage reuses broad TypeScript test projects.
|
|
14
14
|
//
|
|
15
15
|
// The focused ttsc fixtures are intentionally small, but the repository already
|
|
16
|
-
// has
|
|
16
|
+
// has feature projects that exercise many more typia API shapes.
|
|
17
17
|
// Running them through the same project transform mode gives Go coverage for
|
|
18
18
|
// those native branches without duplicating that TypeScript corpus here.
|
|
19
19
|
//
|
|
@@ -31,89 +31,88 @@ import (
|
|
|
31
31
|
// 3. Require JSON project output to include a TypeScript map.
|
|
32
32
|
// 4. Hold each project to its own purpose's diagnostic contract.
|
|
33
33
|
func TestProjectCatalogTransformCoverage(t *testing.T) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
})
|
|
34
|
+
root := transformCoverageRepoRoot(t)
|
|
35
|
+
for _, project := range []string{
|
|
36
|
+
"template",
|
|
37
|
+
"test-langchain",
|
|
38
|
+
"test-mcp",
|
|
39
|
+
"test-typia-automated",
|
|
40
|
+
"test-typia-schema",
|
|
41
|
+
"test-utils",
|
|
42
|
+
"test-utils-automated",
|
|
43
|
+
"test-vercel",
|
|
44
|
+
} {
|
|
45
|
+
project := project
|
|
46
|
+
t.Run(project, func(t *testing.T) {
|
|
47
|
+
out, errText, code := transformCatalogProject(root, project)
|
|
48
|
+
if code != 0 {
|
|
49
|
+
t.Fatalf("%s transform failed: code=%d stderr=\n%s", project, code, errText)
|
|
50
|
+
}
|
|
51
|
+
if !strings.Contains(out, `"typescript"`) {
|
|
52
|
+
t.Fatalf("%s project output should include TypeScript map:\n%s", project, out)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
// tests/test-error compiles deliberately invalid call sites, and reporting them
|
|
57
|
+
// is that workspace's entire reason to exist. Its diagnostics are a contract,
|
|
58
|
+
// not an accident of what it currently holds.
|
|
59
|
+
t.Run("test-error", func(t *testing.T) {
|
|
60
|
+
out, _, code := transformCatalogProject(root, "test-error")
|
|
61
|
+
if code != 3 {
|
|
62
|
+
t.Fatalf("test-error transform should report diagnostics with code 3, got %d", code)
|
|
63
|
+
}
|
|
64
|
+
if !strings.Contains(out, `"diagnostics"`) || !strings.Contains(out, `"typescript"`) {
|
|
65
|
+
t.Fatalf("test-error output should include diagnostics and TypeScript map:\n%s", out)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
// tests/debug is a one-off repro scratchpad, so nothing about its sources is
|
|
69
|
+
// promised: it holds valid code between investigations and invalid code during
|
|
70
|
+
// one. What survives both states is the project transform's own envelope
|
|
71
|
+
// contract, so that is what this pins. The project must resolve and reach a
|
|
72
|
+
// transform decision rather than fail to load (exit 2), it must still
|
|
73
|
+
// contribute transformed sources, and its exit code must agree with the
|
|
74
|
+
// envelope it published.
|
|
75
|
+
//
|
|
76
|
+
// Accepting either 0 or 3 is derived, not observed: transform mode never
|
|
77
|
+
// typechecks, so exit 2 is reachable only from a flag, cwd, or tsconfig-load
|
|
78
|
+
// failure (semantic diagnostics belong to the build route). No source content a
|
|
79
|
+
// repro leaves behind can produce it -- even an outright type error still exits
|
|
80
|
+
// 0 here -- which is what makes the band content-agnostic.
|
|
81
|
+
//
|
|
82
|
+
// The envelope is decoded rather than substring-matched because `"typescript"`
|
|
83
|
+
// carries no `omitempty` and runTransformProject initializes the map before its
|
|
84
|
+
// source loop: the key is present even when the workspace resolved no source at
|
|
85
|
+
// all. That empty envelope is the shape a `rootDir` or `include` rot leaves
|
|
86
|
+
// behind, and this workspace is the one that already shipped such a rot
|
|
87
|
+
// (samchon/typia#2132), so a key-presence check would pass over the one failure
|
|
88
|
+
// worth catching here.
|
|
89
|
+
t.Run("debug", func(t *testing.T) {
|
|
90
|
+
out, errText, code := transformCatalogProject(root, "debug")
|
|
91
|
+
if code != 0 && code != 3 {
|
|
92
|
+
t.Fatalf("debug transform should load and transform the project, got code=%d stderr=\n%s", code, errText)
|
|
93
|
+
}
|
|
94
|
+
var envelope transformProjectOutput
|
|
95
|
+
if err := json.Unmarshal([]byte(out), &envelope); err != nil {
|
|
96
|
+
t.Fatalf("debug project output should be a transform envelope: %v\n%s", err, out)
|
|
97
|
+
}
|
|
98
|
+
if len(envelope.TypeScript) == 0 {
|
|
99
|
+
t.Fatalf("debug project should contribute transformed sources, got an empty TypeScript map:\n%s", out)
|
|
100
|
+
}
|
|
101
|
+
if diagnosed := len(envelope.Diagnostics) > 0; diagnosed != (code == 3) {
|
|
102
|
+
t.Fatalf("debug exit code %d disagrees with its envelope (diagnostics present: %v):\n%s", code, diagnosed, out)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
// transformCatalogProject runs project transform mode over one repository test
|
|
109
108
|
// project and returns its captured stdout, stderr, and exit code.
|
|
110
109
|
func transformCatalogProject(root string, project string) (string, string, int) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
cwd := filepath.Join(root, "tests", project)
|
|
111
|
+
return transformCoverageCapture(func() int {
|
|
112
|
+
return runTransform([]string{
|
|
113
|
+
"--cwd", cwd,
|
|
114
|
+
"--tsconfig", "tsconfig.json",
|
|
115
|
+
"--output", "ts",
|
|
116
|
+
})
|
|
117
|
+
})
|
|
119
118
|
}
|
|
@@ -34,7 +34,9 @@ func TestTemplateLiteralTypeTagsTransform(t *testing.T) {
|
|
|
34
34
|
if code != 0 {
|
|
35
35
|
t.Fatalf("template literal tags transform failed: code=%d stderr=\n%s", code, errText)
|
|
36
36
|
}
|
|
37
|
-
if !strings.Contains(out, "
|
|
37
|
+
if !strings.Contains(out, `require("typia/lib/internal/_stringLength")`) ||
|
|
38
|
+
!strings.Contains(out, "._stringLength(input) <= 30") ||
|
|
39
|
+
!strings.Contains(out, "RegExp(/^prefix(.*)postfix$/)") {
|
|
38
40
|
t.Fatalf("emitted checker should combine the template pattern with its tags:\n%s", out)
|
|
39
41
|
}
|
|
40
42
|
templateLiteralTypeTagsRunRuntimeCases(t, project, out)
|
|
@@ -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
|