Skip to content

Commit 776ef2b

Browse files
authoredApr 12, 2024··
add a RegExp module to packages/effect, closes #2488 (#2493)
1 parent 41c8102 commit 776ef2b

File tree

6 files changed

+66
-12
lines changed

6 files changed

+66
-12
lines changed
 

‎.changeset/gentle-actors-sing.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
add a `RegExp` module to `packages/effect`, closes #2488

‎packages/effect/src/RegExp.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* This module provides utility functions for working with RegExp in TypeScript.
3+
*
4+
* @since 2.0.0
5+
*/
6+
7+
/**
8+
* Escapes special characters in a regular expression pattern.
9+
*
10+
* @example
11+
* import * as RegExp from "effect/RegExp"
12+
*
13+
* assert.deepStrictEqual(RegExp.escape("a*b"), "a\\*b")
14+
*
15+
* @since 2.0.0
16+
*/
17+
export const escape = (string: string): string => string.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&")

‎packages/effect/src/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,13 @@ export * as RedBlackTree from "./RedBlackTree.js"
599599
*/
600600
export * as Ref from "./Ref.js"
601601

602+
/**
603+
* This module provides utility functions for working with RegExp in TypeScript.
604+
*
605+
* @since 2.0.0
606+
*/
607+
export * as RegExp from "./RegExp.js"
608+
602609
/**
603610
* @since 2.0.0
604611
*/

‎packages/effect/src/internal/configProvider.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as number from "../Number.js"
1313
import * as Option from "../Option.js"
1414
import { pipeArguments } from "../Pipeable.js"
1515
import * as ReadonlyArray from "../ReadonlyArray.js"
16+
import * as regexp from "../RegExp.js"
1617
import type * as _config from "./config.js"
1718
import * as configError from "./configError.js"
1819
import * as pathPatch from "./configProvider/pathPatch.js"
@@ -595,7 +596,7 @@ export const within = dual<
595596
})
596597

597598
const splitPathString = (text: string, delim: string): ReadonlyArray<string> => {
598-
const split = text.split(new RegExp(`\\s*${escapeRegex(delim)}\\s*`))
599+
const split = text.split(new RegExp(`\\s*${regexp.escape(delim)}\\s*`))
599600
return split
600601
}
601602

@@ -626,8 +627,6 @@ const transpose = <A>(array: ReadonlyArray<ReadonlyArray<A>>): ReadonlyArray<Rea
626627
return Object.keys(array[0]).map((column) => array.map((row) => row[column as any]))
627628
}
628629

629-
const escapeRegex = (string: string): string => string.replace(/[/\-\\^$*+?.()|[\]{}]/g, "\\$&")
630-
631630
const indicesFrom = (quotedIndices: HashSet.HashSet<string>): Effect.Effect<ReadonlyArray<number>> =>
632631
pipe(
633632
core.forEachSequential(quotedIndices, parseQuotedIndex),

‎packages/effect/test/RegExp.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as RegExp from "effect/RegExp"
2+
import { describe, expect, it } from "vitest"
3+
4+
describe("RegExp", () => {
5+
describe("escape", () => {
6+
it("should escape special characters correctly", () => {
7+
const testCases: Array<[string, string]> = [
8+
["abc", "abc"],
9+
["a*b", "a\\*b"],
10+
["a.b", "a\\.b"],
11+
["a|b", "a\\|b"],
12+
["a?b", "a\\?b"],
13+
["a+b", "a\\+b"],
14+
["a(b", "a\\(b"],
15+
["a)b", "a\\)b"],
16+
["a[b", "a\\[b"],
17+
["a]b", "a\\]b"],
18+
["a{b", "a\\{b"],
19+
["a}b", "a\\}b"],
20+
["a^b", "a\\^b"],
21+
["a$b", "a\\$b"],
22+
["a\\b", "a\\\\b"],
23+
["a/b", "a\\/b"]
24+
]
25+
26+
testCases.forEach(([input, expected]) => {
27+
const result = RegExp.escape(input)
28+
expect(result).toEqual(expected)
29+
})
30+
})
31+
})
32+
})

‎packages/schema/src/AST.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as Option from "effect/Option"
1111
import * as Order from "effect/Order"
1212
import * as Predicate from "effect/Predicate"
1313
import * as ReadonlyArray from "effect/ReadonlyArray"
14+
import * as regexp from "effect/RegExp"
1415
import type { Concurrency } from "effect/Types"
1516
import * as util_ from "./internal/util.js"
1617
import type { ParseIssue } from "./ParseResult.js"
@@ -1834,29 +1835,22 @@ export const annotations = (ast: AST, annotations: Annotations): AST => {
18341835
*/
18351836
export const keyof = (ast: AST): AST => Union.unify(_keyof(ast))
18361837

1837-
const specialCharsRegex = /[.*+?^${}()|[\]\\]/g
1838-
1839-
const escapeSpecialChars = (s: string): string =>
1840-
specialCharsRegex.test(s) ?
1841-
s.replace(specialCharsRegex, "\\$&") // $& means the whole matched string
1842-
: s
1843-
18441838
const STRING_KEYWORD_PATTERN = ".*"
18451839
const NUMBER_KEYWORD_PATTERN = "[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?"
18461840

18471841
/**
18481842
* @since 1.0.0
18491843
*/
18501844
export const getTemplateLiteralRegExp = (ast: TemplateLiteral): RegExp => {
1851-
let pattern = `^${escapeSpecialChars(ast.head)}`
1845+
let pattern = `^${regexp.escape(ast.head)}`
18521846

18531847
for (const span of ast.spans) {
18541848
if (isStringKeyword(span.type)) {
18551849
pattern += STRING_KEYWORD_PATTERN
18561850
} else if (isNumberKeyword(span.type)) {
18571851
pattern += NUMBER_KEYWORD_PATTERN
18581852
}
1859-
pattern += escapeSpecialChars(span.literal)
1853+
pattern += regexp.escape(span.literal)
18601854
}
18611855

18621856
pattern += "$"

0 commit comments

Comments
 (0)
Please sign in to comment.