Skip to content

Commit 85ec09d

Browse files
committed
Close #1699: support regex flags in tags.Pattern
1 parent 0e0f262 commit 85ec09d

5 files changed

Lines changed: 169 additions & 25 deletions

File tree

src/factories/MetadataCommentTagFactory.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -524,21 +524,56 @@ const PARSER: Record<
524524
],
525525
};
526526
},
527-
pattern: ({ value }) => ({
528-
string: [
529-
{
530-
name: `Pattern<${JSON.stringify(value)}>`,
531-
target: "string",
532-
kind: "pattern",
533-
value: value,
534-
validate: `RegExp(${JSON.stringify(value)}).test($input)`,
535-
exclusive: ["format"],
536-
schema: {
537-
pattern: value,
527+
pattern: ({ value }) => {
528+
// Support regex literal syntax: /pattern/flags
529+
const match = value.match(/^\/(.*)\/([dgimsuy]*)$/);
530+
if (match !== null) {
531+
const [, pattern, flags] = match;
532+
return {
533+
string: [
534+
{
535+
name:
536+
flags === ""
537+
? `Pattern<${JSON.stringify(pattern)}>`
538+
: `Pattern<${JSON.stringify(pattern)}, ${JSON.stringify(flags)}>`,
539+
target: "string",
540+
kind: "pattern",
541+
value: pattern,
542+
validate:
543+
flags === ""
544+
? `RegExp(${JSON.stringify(pattern)}).test($input)`
545+
: `RegExp(${JSON.stringify(pattern)}, ${JSON.stringify(flags)}).test($input)`,
546+
exclusive: ["format"],
547+
schema:
548+
flags === ""
549+
? {
550+
pattern: pattern,
551+
}
552+
: {
553+
pattern: pattern,
554+
"x-pattern-flags": flags,
555+
},
556+
},
557+
],
558+
};
559+
}
560+
// Legacy format: just the pattern (no flags)
561+
return {
562+
string: [
563+
{
564+
name: `Pattern<${JSON.stringify(value)}>`,
565+
target: "string",
566+
kind: "pattern",
567+
value: value,
568+
validate: `RegExp(${JSON.stringify(value)}).test($input)`,
569+
exclusive: ["format"],
570+
schema: {
571+
pattern: value,
572+
},
538573
},
539-
},
540-
],
541-
}),
574+
],
575+
};
576+
},
542577
length: (props) => ({
543578
string: [
544579
{

src/programmers/RandomProgrammer.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -504,22 +504,35 @@ export namespace RandomProgrammer {
504504
.join("")}`,
505505
arguments: [],
506506
};
507-
} else if (string.pattern !== undefined)
507+
} else if (string.pattern !== undefined) {
508+
const flags: string | undefined = (
509+
schema as OpenApi.IJsonSchema.IString & {
510+
"x-pattern-flags"?: string;
511+
}
512+
)["x-pattern-flags"];
508513
return {
509514
method: "pattern",
510515
internal: "randomPattern",
511516
arguments: [
512517
ts.factory.createNewExpression(
513518
ts.factory.createIdentifier("RegExp"),
514519
undefined,
515-
[
516-
ts.factory.createStringLiteral(
517-
(schema as OpenApi.IJsonSchema.IString).pattern!,
518-
),
519-
],
520+
flags !== undefined
521+
? [
522+
ts.factory.createStringLiteral(
523+
(schema as OpenApi.IJsonSchema.IString).pattern!,
524+
),
525+
ts.factory.createStringLiteral(flags),
526+
]
527+
: [
528+
ts.factory.createStringLiteral(
529+
(schema as OpenApi.IJsonSchema.IString).pattern!,
530+
),
531+
],
520532
),
521533
],
522534
};
535+
}
523536
} else if (props.atomic.type === "number") {
524537
const number:
525538
| OpenApi.IJsonSchema.INumber

src/tags/Pattern.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,42 @@ import { TagBase } from "./TagBase";
1313
* type HexColor = string & Pattern<"^#[0-9A-Fa-f]{6}$">; // #FF5733
1414
* ```
1515
*
16+
* You can also specify regex flags as a second parameter:
17+
*
18+
* ```ts
19+
* // Case-insensitive matching
20+
* type CaseInsensitive = string & Pattern<"^hello$", "i">;
21+
*
22+
* // Unicode property escapes (requires 'u' flag)
23+
* type Identifier = string & Pattern<"^[\\p{ID_Start}_$][\\p{ID_Continue}_$]*$", "u">;
24+
* ```
25+
*
26+
* Supported flags: `d`, `g`, `i`, `m`, `s`, `u`, `v`, `y` (can be combined)
27+
*
1628
* Note: This tag is mutually exclusive with the Format tag. You cannot use both
1729
* Pattern and Format on the same type.
1830
*
1931
* @author Jeongho Nam - https://github.com/samchon
2032
*/
21-
export type Pattern<Value extends string> = TagBase<{
33+
export type Pattern<
34+
Value extends string,
35+
Flags extends string = "",
36+
> = TagBase<{
2237
target: "string";
2338
kind: "pattern";
2439
value: Value;
25-
validate: `RegExp("${Serialize<Value>}").test($input)`;
40+
validate: Flags extends ""
41+
? `RegExp("${Serialize<Value>}").test($input)`
42+
: `RegExp("${Serialize<Value>}", "${Flags}").test($input)`;
2643
exclusive: ["format", "pattern"];
27-
schema: {
28-
pattern: Value;
29-
};
44+
schema: Flags extends ""
45+
? {
46+
pattern: Value;
47+
}
48+
: {
49+
pattern: Value;
50+
"x-pattern-flags": Flags;
51+
};
3052
}>;
3153

3254
/// reference: https://github.com/type-challenges/type-challenges/issues/22394#issuecomment-1397158205
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Spoiler } from "../helpers/Spoiler";
2+
3+
export interface CommentTagPatternFlags {
4+
/** @pattern /^hello$/i */
5+
caseInsensitive: string;
6+
7+
/** @pattern /^hello$/m */
8+
multiline: string;
9+
10+
/** @pattern /^[a-z]+$/im */
11+
multipleFlags: string;
12+
}
13+
export namespace CommentTagPatternFlags {
14+
export function generate(): CommentTagPatternFlags {
15+
return {
16+
caseInsensitive: "HELLO",
17+
multiline: "hello",
18+
multipleFlags: "hello",
19+
};
20+
}
21+
22+
export const SPOILERS: Spoiler<CommentTagPatternFlags>[] = [
23+
(input) => {
24+
input.caseInsensitive = "world";
25+
return ["$input.caseInsensitive"];
26+
},
27+
(input) => {
28+
input.multiline = "world";
29+
return ["$input.multiline"];
30+
},
31+
(input) => {
32+
input.multipleFlags = "123";
33+
return ["$input.multipleFlags"];
34+
},
35+
];
36+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import typia from "typia";
2+
3+
import { Spoiler } from "../helpers/Spoiler";
4+
5+
export interface TypeTagPatternFlags {
6+
// Case-insensitive matching
7+
caseInsensitive: string & typia.tags.Pattern<"^hello$", "i">;
8+
9+
// Multiline flag
10+
multiline: string & typia.tags.Pattern<"^hello$", "m">;
11+
12+
// Multiple flags combined
13+
multipleFlags: string & typia.tags.Pattern<"^[a-z]+$", "im">;
14+
}
15+
export namespace TypeTagPatternFlags {
16+
export function generate(): TypeTagPatternFlags {
17+
return {
18+
caseInsensitive: "HELLO",
19+
multiline: "hello",
20+
multipleFlags: "hello",
21+
};
22+
}
23+
24+
export const SPOILERS: Spoiler<TypeTagPatternFlags>[] = [
25+
(input) => {
26+
input.caseInsensitive = "world";
27+
return ["$input.caseInsensitive"];
28+
},
29+
(input) => {
30+
input.multiline = "world";
31+
return ["$input.multiline"];
32+
},
33+
(input) => {
34+
input.multipleFlags = "123";
35+
return ["$input.multipleFlags"];
36+
},
37+
];
38+
}

0 commit comments

Comments
 (0)