diff --git a/.eslintrc.yml b/.eslintrc.yml index 760df32..bc5b78e 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,6 +1,6 @@ root: true extends: - - plugin:@mysticatea/es2018 + - plugin:@eslint-community/mysticatea/es2018 parserOptions: project: tsconfig.eslint.json settings: @@ -11,7 +11,15 @@ rules: # tsc does. "no-redeclare": "off" # https://github.com/typescript-eslint/typescript-eslint/issues/743 - "@mysticatea/ts/unbound-method": "off" + "@eslint-community/mysticatea/ts/unbound-method": "off" + + # Temporary disabled rules + "@eslint-community/mysticatea/ts/naming-convention": "off" + "@eslint-community/mysticatea/ts/prefer-readonly-parameter-types": "off" + # Should be fixed by `@eslint-community/eslint-plugin-mysticatea` + "no-duplicate-imports": "off" + "@eslint-community/mysticatea/ts/no-duplicate-imports": + ["error", { includeExports: true }] overrides: - files: "./src/unicode/ids.ts" @@ -20,4 +28,4 @@ overrides: no-misleading-character-class: "off" - files: "./src/unicode/property-data.ts" rules: - "@mysticatea/ts/camelcase": "off" + "@eslint-community/mysticatea/ts/camelcase": "off" diff --git a/package.json b/package.json index 7964417..26e9b87 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "dependencies": {}, "devDependencies": { - "@mysticatea/eslint-plugin": "^13.0.0", + "@eslint-community/eslint-plugin-mysticatea": "^15.3.0", "@rollup/plugin-node-resolve": "^14.1.0", "@types/eslint": "^6.8.1", "@types/jsdom": "^16.2.15", diff --git a/scripts/clone-without-circular.ts b/scripts/clone-without-circular.ts index b5397e8..aaa6bd7 100644 --- a/scripts/clone-without-circular.ts +++ b/scripts/clone-without-circular.ts @@ -1,3 +1,12 @@ +/* Temporarily disable these rules until we fix the `any` usage */ +/* eslint + "@eslint-community/mysticatea/eslint-comments/no-use": "off", + "@eslint-community/mysticatea/ts/no-unsafe-argument": "off", + "@eslint-community/mysticatea/ts/no-unsafe-assignment": "off", + "@eslint-community/mysticatea/ts/no-unsafe-member-access": "off", + "@eslint-community/mysticatea/ts/no-unsafe-return": "off", +*/ + import { posix } from "path" function resolveLocation( @@ -35,7 +44,7 @@ function cloneWithoutCircularRec(x: any, pathMap: Map): any { return x } if (Array.isArray(x)) { - return x.map(el => cloneWithoutCircularRec(el, pathMap)) + return x.map((el) => cloneWithoutCircularRec(el, pathMap)) } const y = {} as any @@ -58,7 +67,7 @@ function getRelativePath( return to } if (Array.isArray(to)) { - return to.map(el => getRelativePath(from, el, pathMap)) + return to.map((el) => getRelativePath(from, el, pathMap)) } const fromPath = pathMap.get(from)! @@ -66,14 +75,15 @@ function getRelativePath( try { return `♻️${posix.relative(fromPath, toPath).replace(/\/$/u, "")}` } catch (err) { - console.error(fromPath, toPath, err.stack) + const error = err as Error + console.error(fromPath, toPath, error.stack) return "💥💥💥💥💥💥💥💥" } } export function cloneWithoutCircular(obj: object): object { const path: string[] = [] - const pathMap: Map = new Map() + const pathMap = new Map() resolveLocation(obj, path, pathMap) return cloneWithoutCircularRec(obj, pathMap) diff --git a/scripts/update-fixtures.ts b/scripts/update-fixtures.ts index d497a1a..facb697 100644 --- a/scripts/update-fixtures.ts +++ b/scripts/update-fixtures.ts @@ -1,4 +1,6 @@ -import { AST, parseRegExpLiteral, visitRegExpAST } from "../src/index" +import type { AST } from "../src/index" +import { parseRegExpLiteral, visitRegExpAST } from "../src/index" +import type { RegExpSyntaxError } from "../src/regexp-syntax-error" import * as Parser from "../test/fixtures/parser/literal" import * as Visitor from "../test/fixtures/visitor" import { cloneWithoutCircular } from "./clone-without-circular" @@ -12,8 +14,9 @@ for (const filename of Object.keys(Parser.Fixtures)) { const ast = parseRegExpLiteral(pattern, options) fixture.patterns[pattern] = { ast: cloneWithoutCircular(ast) } } catch (err) { + const error = err as RegExpSyntaxError fixture.patterns[pattern] = { - error: { message: err.message, index: err.index }, + error: { message: error.message, index: error.index }, } } } diff --git a/scripts/update-unicode-ids.ts b/scripts/update-unicode-ids.ts index 50c3b7b..447008c 100644 --- a/scripts/update-unicode-ids.ts +++ b/scripts/update-unicode-ids.ts @@ -12,14 +12,14 @@ const logger = console // Main const main = async () => { let banner = "" - const idStartSet: Set = new Set() + const idStartSet = new Set() const idStartSmall: [number, number][] = [] const idStartLarge: [number, number][] = [] const idContinueSmall: [number, number][] = [] const idContinueLarge: [number, number][] = [] logger.log("Fetching data... (%s)", DB_URL) - await processEachLine(line => { + await processEachLine((line) => { let m: RegExpExecArray | null = null if (banner === "") { logger.log("Processing data... (%s)", line.slice(2)) @@ -113,7 +113,7 @@ function restoreRanges(data: string): number[] { rules: { curly: "off" }, }) const result = engine.executeOnText(code, "ids.ts").results[0] - code = result.output || code + code = result.output ?? code logger.log("Writing '%s'...", FILE_PATH) await save(code) @@ -121,30 +121,31 @@ function restoreRanges(data: string): number[] { logger.log("Completed!") } -main().catch(error => { +main().catch((err) => { + const error = err as Error logger.error(error.stack) process.exitCode = 1 }) -function processEachLine(cb: (line: string) => void): Promise { +function processEachLine(processLine: (line: string) => void): Promise { return new Promise((resolve, reject) => { - http.get(DB_URL, res => { + http.get(DB_URL, (res) => { let buffer = "" res.setEncoding("utf8") - res.on("data", chunk => { + res.on("data", (chunk) => { const lines = (buffer + String(chunk)).split("\n") if (lines.length === 1) { buffer = lines[0] } else { buffer = lines.pop()! for (const line of lines) { - cb(line) + processLine(line) } } }) res.on("end", () => { if (buffer) { - cb(buffer) + processLine(buffer) } resolve() }) @@ -189,8 +190,12 @@ function makeInitLargeIdRanges(ranges: [number, number][]): string { function save(content: string): Promise { return new Promise((resolve, reject) => { - fs.writeFile(FILE_PATH, content, error => - error ? reject(error) : resolve(), - ) + fs.writeFile(FILE_PATH, content, (error) => { + if (error) { + reject(error) + } else { + resolve() + } + }) }) } diff --git a/scripts/update-unicode-properties.ts b/scripts/update-unicode-properties.ts index fb70a85..f14ad29 100644 --- a/scripts/update-unicode-properties.ts +++ b/scripts/update-unicode-properties.ts @@ -1,5 +1,6 @@ import fs from "fs" -import { JSDOM, DOMWindow } from "jsdom" +import type { DOMWindow } from "jsdom" +import { JSDOM } from "jsdom" import { CLIEngine } from "eslint" const DataSources = [ @@ -43,7 +44,7 @@ type Datum = { // Main ;(async () => { - const data: Record = Object.create(null) + const data: Record = {} const existing = { binProperties: new Set(), gcValues: new Set(), @@ -70,12 +71,13 @@ type Datum = { try { logger.log("Fetching data from %o", url) ;({ window } = await JSDOM.fromURL(url)) - } catch (error) { + } catch (err) { + const error = err as Error if (!error || error.message !== "Error: socket hang up") { throw error } logger.log(error.message, "then retry.") - await new Promise(resolve => setTimeout(resolve, 2000)) + await new Promise((resolve) => setTimeout(resolve, 2000)) } } while (window == null) @@ -99,13 +101,13 @@ ${makeClassDeclarationCode(Object.keys(data))} const gcNameSet = new Set(["General_Category", "gc"]) const scNameSet = new Set(["Script", "Script_Extensions", "sc", "scx"]) const gcValueSets = new DataSet(${Object.values(data) - .map(d => makeDataCode(d.gcValues)) + .map((d) => makeDataCode(d.gcValues)) .join(",")}) const scValueSets = new DataSet(${Object.values(data) - .map(d => makeDataCode(d.scValues)) + .map((d) => makeDataCode(d.scValues)) .join(",")}) const binPropertySets = new DataSet(${Object.values(data) - .map(d => makeDataCode(d.binProperties)) + .map((d) => makeDataCode(d.binProperties)) .join(",")}) export function isValidUnicodeProperty(version: number, name: string, value: string): boolean { @@ -141,26 +143,27 @@ export function isValidLoneUnicodeProperty(version: number, value: string): bool logger.log("Formatting code...") const engine = new CLIEngine({ fix: true }) const result = engine.executeOnText(code, "properties.ts").results[0] - code = result.output || code + code = result.output ?? code logger.log("Writing '%s'...", FILE_PATH) await save(code) logger.log("Completed!") -})().catch(error => { +})().catch((err) => { + const error = err as Error logger.error(error.stack) process.exitCode = 1 }) function collectValues( - window: Window, + window: DOMWindow, id: string, existingSet: Set, ): string[] { const selector = `${id} td:nth-child(1) code` const nodes = window.document.querySelectorAll(selector) - const values = Array.from(nodes, node => node.textContent || "") - .filter(value => { + const values = Array.from(nodes, (node) => node.textContent ?? "") + .filter((value) => { if (existingSet.has(value)) { return false } @@ -183,15 +186,15 @@ function collectValues( function makeClassDeclarationCode(versions: string[]): string { const fields = versions .map( - v => + (v) => `private _raw${v}: string\nprivate _set${v}: Set | undefined`, ) .join("\n") - const parameters = versions.map(v => `raw${v}: string`).join(", ") - const init = versions.map(v => `this._raw${v} = raw${v}`).join("\n") + const parameters = versions.map((v) => `raw${v}: string`).join(", ") + const init = versions.map((v) => `this._raw${v} = raw${v}`).join("\n") const getters = versions .map( - v => + (v) => `public get es${v}(): Set { return this._set${v} || (this._set${v} = new Set(this._raw${v}.split(" "))) }`, ) .join("\n") @@ -209,7 +212,7 @@ function makeClassDeclarationCode(versions: string[]): string { function makeDataCode(values: string[]): string { return `"${values - .map(value => JSON.stringify(value).slice(1, -1)) + .map((value) => JSON.stringify(value).slice(1, -1)) .join(" ")}"` } @@ -227,8 +230,12 @@ function makeVerificationCode( function save(content: string): Promise { return new Promise((resolve, reject) => { - fs.writeFile(FILE_PATH, content, error => - error ? reject(error) : resolve(), - ) + fs.writeFile(FILE_PATH, content, (error) => { + if (error) { + reject(error) + } else { + resolve() + } + }) }) } diff --git a/src/ast.ts b/src/ast.ts index 5ba597c..cabdcf2 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -7,52 +7,51 @@ export type Node = BranchNode | LeafNode * The type which includes all branch nodes. */ export type BranchNode = - | RegExpLiteral - | Pattern | Alternative - | Group | CapturingGroup - | Quantifier | CharacterClass - | LookaroundAssertion | CharacterClassRange + | Group + | LookaroundAssertion + | Pattern + | Quantifier + | RegExpLiteral /** * The type which includes all leaf nodes. */ export type LeafNode = + | Backreference | BoundaryAssertion - | CharacterSet | Character - | Backreference + | CharacterSet | Flags /** * The type which includes all atom nodes. */ -export type Element = Assertion | Quantifier | QuantifiableElement +export type Element = Assertion | QuantifiableElement | Quantifier /** * The type which includes all atom nodes that Quantifier node can have as children. */ export type QuantifiableElement = - | Group + | Backreference | CapturingGroup + | Character | CharacterClass | CharacterSet - | Character - | Backreference - // Lookahead assertions is quantifiable in Annex-B. + | Group | LookaheadAssertion /** * The type which includes all character class atom nodes. */ export type CharacterClassElement = - | EscapeCharacterSet - | UnicodePropertyCharacterSet | Character | CharacterClassRange + | EscapeCharacterSet + | UnicodePropertyCharacterSet /** * The type which defines common properties for all node types. @@ -95,7 +94,7 @@ export interface Pattern extends NodeBase { */ export interface Alternative extends NodeBase { type: "Alternative" - parent: Pattern | Group | CapturingGroup | LookaroundAssertion + parent: CapturingGroup | Group | LookaroundAssertion | Pattern elements: Element[] } @@ -202,7 +201,7 @@ export type BoundaryAssertion = EdgeAssertion | WordBoundaryAssertion export interface EdgeAssertion extends NodeBase { type: "Assertion" parent: Alternative | Quantifier - kind: "start" | "end" + kind: "end" | "start" } /** @@ -240,7 +239,7 @@ export interface AnyCharacterSet extends NodeBase { */ export interface EscapeCharacterSet extends NodeBase { type: "CharacterSet" - parent: Alternative | Quantifier | CharacterClass + parent: Alternative | CharacterClass | Quantifier kind: "digit" | "space" | "word" negate: boolean } @@ -251,7 +250,7 @@ export interface EscapeCharacterSet extends NodeBase { */ export interface UnicodePropertyCharacterSet extends NodeBase { type: "CharacterSet" - parent: Alternative | Quantifier | CharacterClass + parent: Alternative | CharacterClass | Quantifier kind: "property" key: string value: string | null @@ -265,7 +264,7 @@ export interface UnicodePropertyCharacterSet extends NodeBase { */ export interface Character extends NodeBase { type: "Character" - parent: Alternative | Quantifier | CharacterClass | CharacterClassRange + parent: Alternative | CharacterClass | CharacterClassRange | Quantifier value: number // a code point. } diff --git a/src/index.ts b/src/index.ts index 0b6bac5..235452a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ export { AST, RegExpParser, RegExpValidator } * @returns The AST of the regular expression. */ export function parseRegExpLiteral( - source: string | RegExp, + source: RegExp | string, options?: RegExpParser.Options, ): AST.RegExpLiteral { return new RegExpParser(options).parseLiteral(String(source)) @@ -27,7 +27,7 @@ export function validateRegExpLiteral( source: string, options?: RegExpValidator.Options, ): void { - return new RegExpValidator(options).validateLiteral(source) + new RegExpValidator(options).validateLiteral(source) } export function visitRegExpAST( diff --git a/src/parser.ts b/src/parser.ts index 3bc795f..c1afed4 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,4 +1,4 @@ -import { +import type { Alternative, Backreference, CapturingGroup, @@ -12,35 +12,40 @@ import { Pattern, Quantifier, } from "./ast" -import { EcmaVersion } from "./ecma-versions" +import type { EcmaVersion } from "./ecma-versions" import { HyphenMinus } from "./unicode" import { RegExpValidator } from "./validator" type AppendableNode = - | Pattern | Alternative - | Group | CapturingGroup | CharacterClass + | Group | LookaroundAssertion + | Pattern -const DummyPattern: Pattern = {} as any -const DummyFlags: Flags = {} as any -const DummyCapturingGroup: CapturingGroup = {} as any +const DummyPattern: Pattern = {} as Pattern +const DummyFlags: Flags = {} as Flags +const DummyCapturingGroup: CapturingGroup = {} as CapturingGroup class RegExpParserState { public readonly strict: boolean + public readonly ecmaVersion: EcmaVersion + private _node: AppendableNode = DummyPattern + private _flags: Flags = DummyFlags + private _backreferences: Backreference[] = [] + private _capturingGroups: CapturingGroup[] = [] public source = "" public constructor(options?: RegExpParser.Options) { - this.strict = Boolean(options && options.strict) - this.ecmaVersion = (options && options.ecmaVersion) || 2022 + this.strict = Boolean(options?.strict) + this.ecmaVersion = options?.ecmaVersion ?? 2022 } public get pattern(): Pattern { @@ -106,7 +111,7 @@ class RegExpParserState { const group = typeof ref === "number" ? this._capturingGroups[ref - 1] - : this._capturingGroups.find(g => g.name === ref)! + : this._capturingGroups.find((g) => g.name === ref)! reference.resolved = group group.references.push(reference) } @@ -281,7 +286,7 @@ class RegExpParserState { public onEdgeAssertion( start: number, end: number, - kind: "start" | "end", + kind: "end" | "start", ): void { const parent = this._node if (parent.type !== "Alternative") { @@ -517,6 +522,7 @@ export namespace RegExpParser { export class RegExpParser { private _state: RegExpParserState + private _validator: RegExpValidator /** diff --git a/src/reader.ts b/src/reader.ts index 2ec08c8..e087616 100644 --- a/src/reader.ts +++ b/src/reader.ts @@ -17,15 +17,25 @@ const unicodeImpl = { export class Reader { private _impl = legacyImpl + private _s = "" + private _i = 0 + private _end = 0 + private _cp1 = -1 + private _w1 = 1 + private _cp2 = -1 + private _w2 = 1 + private _cp3 = -1 + private _w3 = 1 + private _cp4 = -1 public get source(): string { diff --git a/src/regexp-syntax-error.ts b/src/regexp-syntax-error.ts index 2e7b88e..121e12b 100644 --- a/src/regexp-syntax-error.ts +++ b/src/regexp-syntax-error.ts @@ -1,5 +1,6 @@ export class RegExpSyntaxError extends SyntaxError { public index: number + public constructor( source: string, uFlag: boolean, diff --git a/src/unicode/ids.ts b/src/unicode/ids.ts index fae967d..a82931a 100644 --- a/src/unicode/ids.ts +++ b/src/unicode/ids.ts @@ -27,14 +27,14 @@ export function isIdContinue(cp: number): boolean { function isLargeIdStart(cp: number): boolean { return isInRange( cp, - largeIdStartRanges || (largeIdStartRanges = initLargeIdStartRanges()), + largeIdStartRanges ?? (largeIdStartRanges = initLargeIdStartRanges()), ) } function isLargeIdContinue(cp: number): boolean { return isInRange( cp, - largeIdContinueRanges || + largeIdContinueRanges ?? (largeIdContinueRanges = initLargeIdContinueRanges()), ) } @@ -74,5 +74,5 @@ function isInRange(cp: number, ranges: number[]): boolean { function restoreRanges(data: string): number[] { let last = 0 - return data.split(" ").map(s => (last += parseInt(s, 36) | 0)) + return data.split(" ").map((s) => (last += parseInt(s, 36) | 0)) } diff --git a/src/unicode/properties.ts b/src/unicode/properties.ts index 55b6601..83279a4 100644 --- a/src/unicode/properties.ts +++ b/src/unicode/properties.ts @@ -2,13 +2,21 @@ class DataSet { private _raw2018: string + private _set2018: Set | undefined + private _raw2019: string + private _set2019: Set | undefined + private _raw2020: string + private _set2020: Set | undefined + private _raw2021: string + private _set2021: Set | undefined + public constructor( raw2018: string, raw2019: string, @@ -20,24 +28,28 @@ class DataSet { this._raw2020 = raw2020 this._raw2021 = raw2021 } + public get es2018(): Set { return ( - this._set2018 || (this._set2018 = new Set(this._raw2018.split(" "))) + this._set2018 ?? (this._set2018 = new Set(this._raw2018.split(" "))) ) } + public get es2019(): Set { return ( - this._set2019 || (this._set2019 = new Set(this._raw2019.split(" "))) + this._set2019 ?? (this._set2019 = new Set(this._raw2019.split(" "))) ) } + public get es2020(): Set { return ( - this._set2020 || (this._set2020 = new Set(this._raw2020.split(" "))) + this._set2020 ?? (this._set2020 = new Set(this._raw2020.split(" "))) ) } + public get es2021(): Set { return ( - this._set2021 || (this._set2021 = new Set(this._raw2021.split(" "))) + this._set2021 ?? (this._set2021 = new Set(this._raw2021.split(" "))) ) } } diff --git a/src/validator.ts b/src/validator.ts index 29b5740..84491b9 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -1,4 +1,4 @@ -import { EcmaVersion } from "./ecma-versions" +import type { EcmaVersion } from "./ecma-versions" import { Reader } from "./reader" import { RegExpSyntaxError } from "./regexp-syntax-error" import { @@ -140,14 +140,14 @@ export namespace RegExpValidator { * A function that is called when the validator entered a RegExp literal. * @param start The 0-based index of the first character. */ - onLiteralEnter?(start: number): void + onLiteralEnter?: (start: number) => void /** * A function that is called when the validator left a RegExp literal. * @param start The 0-based index of the first character. * @param end The next 0-based index of the last character. */ - onLiteralLeave?(start: number, end: number): void + onLiteralLeave?: (start: number, end: number) => void /** * A function that is called when the validator found flags. @@ -161,7 +161,7 @@ export namespace RegExpValidator { * @param dotAll `s` flag. * @param hasIndices `d` flag. */ - onFlags?( + onFlags?: ( start: number, end: number, global: boolean, @@ -171,40 +171,40 @@ export namespace RegExpValidator { sticky: boolean, dotAll: boolean, hasIndices: boolean, - ): void + ) => void /** * A function that is called when the validator entered a pattern. * @param start The 0-based index of the first character. */ - onPatternEnter?(start: number): void + onPatternEnter?: (start: number) => void /** * A function that is called when the validator left a pattern. * @param start The 0-based index of the first character. * @param end The next 0-based index of the last character. */ - onPatternLeave?(start: number, end: number): void + onPatternLeave?: (start: number, end: number) => void /** * A function that is called when the validator entered a disjunction. * @param start The 0-based index of the first character. */ - onDisjunctionEnter?(start: number): void + onDisjunctionEnter?: (start: number) => void /** * A function that is called when the validator left a disjunction. * @param start The 0-based index of the first character. * @param end The next 0-based index of the last character. */ - onDisjunctionLeave?(start: number, end: number): void + onDisjunctionLeave?: (start: number, end: number) => void /** * A function that is called when the validator entered an alternative. * @param start The 0-based index of the first character. * @param index The 0-based index of alternatives in a disjunction. */ - onAlternativeEnter?(start: number, index: number): void + onAlternativeEnter?: (start: number, index: number) => void /** * A function that is called when the validator left an alternative. @@ -212,27 +212,27 @@ export namespace RegExpValidator { * @param end The next 0-based index of the last character. * @param index The 0-based index of alternatives in a disjunction. */ - onAlternativeLeave?(start: number, end: number, index: number): void + onAlternativeLeave?: (start: number, end: number, index: number) => void /** * A function that is called when the validator entered an uncapturing group. * @param start The 0-based index of the first character. */ - onGroupEnter?(start: number): void + onGroupEnter?: (start: number) => void /** * A function that is called when the validator left an uncapturing group. * @param start The 0-based index of the first character. * @param end The next 0-based index of the last character. */ - onGroupLeave?(start: number, end: number): void + onGroupLeave?: (start: number, end: number) => void /** * A function that is called when the validator entered a capturing group. * @param start The 0-based index of the first character. * @param name The group name. */ - onCapturingGroupEnter?(start: number, name: string | null): void + onCapturingGroupEnter?: (start: number, name: string | null) => void /** * A function that is called when the validator left a capturing group. @@ -240,11 +240,11 @@ export namespace RegExpValidator { * @param end The next 0-based index of the last character. * @param name The group name. */ - onCapturingGroupLeave?( + onCapturingGroupLeave?: ( start: number, end: number, name: string | null, - ): void + ) => void /** * A function that is called when the validator found a quantifier. @@ -254,13 +254,13 @@ export namespace RegExpValidator { * @param max The maximum number of repeating. * @param greedy The flag to choose the longest matching. */ - onQuantifier?( + onQuantifier?: ( start: number, end: number, min: number, max: number, greedy: boolean, - ): void + ) => void /** * A function that is called when the validator entered a lookahead/lookbehind assertion. @@ -268,11 +268,11 @@ export namespace RegExpValidator { * @param kind The kind of the assertion. * @param negate The flag which represents that the assertion is negative. */ - onLookaroundAssertionEnter?( + onLookaroundAssertionEnter?: ( start: number, kind: "lookahead" | "lookbehind", negate: boolean, - ): void + ) => void /** * A function that is called when the validator left a lookahead/lookbehind assertion. @@ -281,12 +281,12 @@ export namespace RegExpValidator { * @param kind The kind of the assertion. * @param negate The flag which represents that the assertion is negative. */ - onLookaroundAssertionLeave?( + onLookaroundAssertionLeave?: ( start: number, end: number, kind: "lookahead" | "lookbehind", negate: boolean, - ): void + ) => void /** * A function that is called when the validator found an edge boundary assertion. @@ -294,11 +294,11 @@ export namespace RegExpValidator { * @param end The next 0-based index of the last character. * @param kind The kind of the assertion. */ - onEdgeAssertion?( + onEdgeAssertion?: ( start: number, end: number, - kind: "start" | "end", - ): void + kind: "end" | "start", + ) => void /** * A function that is called when the validator found a word boundary assertion. @@ -307,12 +307,12 @@ export namespace RegExpValidator { * @param kind The kind of the assertion. * @param negate The flag which represents that the assertion is negative. */ - onWordBoundaryAssertion?( + onWordBoundaryAssertion?: ( start: number, end: number, kind: "word", negate: boolean, - ): void + ) => void /** * A function that is called when the validator found a dot. @@ -320,7 +320,7 @@ export namespace RegExpValidator { * @param end The next 0-based index of the last character. * @param kind The kind of the character set. */ - onAnyCharacterSet?(start: number, end: number, kind: "any"): void + onAnyCharacterSet?: (start: number, end: number, kind: "any") => void /** * A function that is called when the validator found a character set escape. @@ -329,12 +329,12 @@ export namespace RegExpValidator { * @param kind The kind of the character set. * @param negate The flag which represents that the character set is negative. */ - onEscapeCharacterSet?( + onEscapeCharacterSet?: ( start: number, end: number, kind: "digit" | "space" | "word", negate: boolean, - ): void + ) => void /** * A function that is called when the validator found a Unicode proerty escape. @@ -345,14 +345,14 @@ export namespace RegExpValidator { * @param value The property value. * @param negate The flag which represents that the character set is negative. */ - onUnicodePropertyCharacterSet?( + onUnicodePropertyCharacterSet?: ( start: number, end: number, kind: "property", key: string, value: string | null, negate: boolean, - ): void + ) => void /** * A function that is called when the validator found a character. @@ -360,7 +360,7 @@ export namespace RegExpValidator { * @param end The next 0-based index of the last character. * @param value The code point of the character. */ - onCharacter?(start: number, end: number, value: number): void + onCharacter?: (start: number, end: number, value: number) => void /** * A function that is called when the validator found a backreference. @@ -368,14 +368,18 @@ export namespace RegExpValidator { * @param end The next 0-based index of the last character. * @param ref The key of the referred capturing group. */ - onBackreference?(start: number, end: number, ref: number | string): void + onBackreference?: ( + start: number, + end: number, + ref: number | string, + ) => void /** * A function that is called when the validator entered a character class. * @param start The 0-based index of the first character. * @param negate The flag which represents that the character class is negative. */ - onCharacterClassEnter?(start: number, negate: boolean): void + onCharacterClassEnter?: (start: number, negate: boolean) => void /** * A function that is called when the validator left a character class. @@ -383,11 +387,11 @@ export namespace RegExpValidator { * @param end The next 0-based index of the last character. * @param negate The flag which represents that the character class is negative. */ - onCharacterClassLeave?( + onCharacterClassLeave?: ( start: number, end: number, negate: boolean, - ): void + ) => void /** * A function that is called when the validator found a character class range. @@ -396,12 +400,12 @@ export namespace RegExpValidator { * @param min The minimum code point of the range. * @param max The maximum code point of the range. */ - onCharacterClassRange?( + onCharacterClassRange?: ( start: number, end: number, min: number, max: number, - ): void + ) => void } } @@ -410,18 +414,31 @@ export namespace RegExpValidator { */ export class RegExpValidator { private readonly _options: RegExpValidator.Options + private readonly _reader = new Reader() + private _uFlag = false + private _nFlag = false + private _lastIntValue = 0 + private _lastMinValue = 0 + private _lastMaxValue = 0 + private _lastStrValue = "" + private _lastKeyValue = "" + private _lastValValue = "" + private _lastAssertionIsQuantifiable = false + private _numCapturingParens = 0 + private _groupNames = new Set() + private _backreferenceNames = new Set() /** @@ -429,7 +446,7 @@ export class RegExpValidator { * @param options The options of validator. */ public constructor(options?: RegExpValidator.Options) { - this._options = options || {} + this._options = options ?? ({} as RegExpValidator.Options) } /** @@ -551,11 +568,11 @@ export class RegExpValidator { // #region Delegate for Options private get strict() { - return Boolean(this._options.strict || this._uFlag) + return Boolean(this._options.strict) || this._uFlag } private get ecmaVersion() { - return this._options.ecmaVersion || 2022 + return this._options.ecmaVersion ?? 2022 } private onLiteralEnter(start: number): void { @@ -700,7 +717,7 @@ export class RegExpValidator { private onEdgeAssertion( start: number, end: number, - kind: "start" | "end", + kind: "end" | "start", ): void { if (this._options.onEdgeAssertion) { this._options.onEdgeAssertion(start, end, kind) @@ -1043,6 +1060,7 @@ export class RegExpValidator { (this.consumeExtendedAtom() && this.consumeOptionalQuantifier()) ) } + private consumeOptionalQuantifier(): boolean { this.consumeQuantifier() return true @@ -2167,6 +2185,7 @@ export class RegExpValidator { } return false } + private isValidIdentityEscape(cp: number): boolean { if (cp === -1) { return false diff --git a/src/visitor.ts b/src/visitor.ts index 9a72ba9..83d23a6 100644 --- a/src/visitor.ts +++ b/src/visitor.ts @@ -1,4 +1,4 @@ -import { +import type { Alternative, Assertion, Backreference, @@ -75,7 +75,9 @@ export class RegExpVisitor { this.visitRegExpLiteral(node) break default: - throw new Error(`Unknown type: ${(node as any).type}`) + throw new Error( + `Unknown type: ${(node as Pick).type}`, + ) } } @@ -88,6 +90,7 @@ export class RegExpVisitor { this._handlers.onAlternativeLeave(node) } } + private visitAssertion(node: Assertion): void { if (this._handlers.onAssertionEnter) { this._handlers.onAssertionEnter(node) @@ -99,6 +102,7 @@ export class RegExpVisitor { this._handlers.onAssertionLeave(node) } } + private visitBackreference(node: Backreference): void { if (this._handlers.onBackreferenceEnter) { this._handlers.onBackreferenceEnter(node) @@ -107,6 +111,7 @@ export class RegExpVisitor { this._handlers.onBackreferenceLeave(node) } } + private visitCapturingGroup(node: CapturingGroup): void { if (this._handlers.onCapturingGroupEnter) { this._handlers.onCapturingGroupEnter(node) @@ -116,6 +121,7 @@ export class RegExpVisitor { this._handlers.onCapturingGroupLeave(node) } } + private visitCharacter(node: Character): void { if (this._handlers.onCharacterEnter) { this._handlers.onCharacterEnter(node) @@ -124,6 +130,7 @@ export class RegExpVisitor { this._handlers.onCharacterLeave(node) } } + private visitCharacterClass(node: CharacterClass): void { if (this._handlers.onCharacterClassEnter) { this._handlers.onCharacterClassEnter(node) @@ -133,6 +140,7 @@ export class RegExpVisitor { this._handlers.onCharacterClassLeave(node) } } + private visitCharacterClassRange(node: CharacterClassRange): void { if (this._handlers.onCharacterClassRangeEnter) { this._handlers.onCharacterClassRangeEnter(node) @@ -143,6 +151,7 @@ export class RegExpVisitor { this._handlers.onCharacterClassRangeLeave(node) } } + private visitCharacterSet(node: CharacterSet): void { if (this._handlers.onCharacterSetEnter) { this._handlers.onCharacterSetEnter(node) @@ -151,6 +160,7 @@ export class RegExpVisitor { this._handlers.onCharacterSetLeave(node) } } + private visitFlags(node: Flags): void { if (this._handlers.onFlagsEnter) { this._handlers.onFlagsEnter(node) @@ -159,6 +169,7 @@ export class RegExpVisitor { this._handlers.onFlagsLeave(node) } } + private visitGroup(node: Group): void { if (this._handlers.onGroupEnter) { this._handlers.onGroupEnter(node) @@ -168,6 +179,7 @@ export class RegExpVisitor { this._handlers.onGroupLeave(node) } } + private visitPattern(node: Pattern): void { if (this._handlers.onPatternEnter) { this._handlers.onPatternEnter(node) @@ -177,6 +189,7 @@ export class RegExpVisitor { this._handlers.onPatternLeave(node) } } + private visitQuantifier(node: Quantifier): void { if (this._handlers.onQuantifierEnter) { this._handlers.onQuantifierEnter(node) @@ -186,6 +199,7 @@ export class RegExpVisitor { this._handlers.onQuantifierLeave(node) } } + private visitRegExpLiteral(node: RegExpLiteral): void { if (this._handlers.onRegExpLiteralEnter) { this._handlers.onRegExpLiteralEnter(node) @@ -200,31 +214,31 @@ export class RegExpVisitor { export namespace RegExpVisitor { export interface Handlers { - onAlternativeEnter?(node: Alternative): void - onAlternativeLeave?(node: Alternative): void - onAssertionEnter?(node: Assertion): void - onAssertionLeave?(node: Assertion): void - onBackreferenceEnter?(node: Backreference): void - onBackreferenceLeave?(node: Backreference): void - onCapturingGroupEnter?(node: CapturingGroup): void - onCapturingGroupLeave?(node: CapturingGroup): void - onCharacterEnter?(node: Character): void - onCharacterLeave?(node: Character): void - onCharacterClassEnter?(node: CharacterClass): void - onCharacterClassLeave?(node: CharacterClass): void - onCharacterClassRangeEnter?(node: CharacterClassRange): void - onCharacterClassRangeLeave?(node: CharacterClassRange): void - onCharacterSetEnter?(node: CharacterSet): void - onCharacterSetLeave?(node: CharacterSet): void - onFlagsEnter?(node: Flags): void - onFlagsLeave?(node: Flags): void - onGroupEnter?(node: Group): void - onGroupLeave?(node: Group): void - onPatternEnter?(node: Pattern): void - onPatternLeave?(node: Pattern): void - onQuantifierEnter?(node: Quantifier): void - onQuantifierLeave?(node: Quantifier): void - onRegExpLiteralEnter?(node: RegExpLiteral): void - onRegExpLiteralLeave?(node: RegExpLiteral): void + onAlternativeEnter?: (node: Alternative) => void + onAlternativeLeave?: (node: Alternative) => void + onAssertionEnter?: (node: Assertion) => void + onAssertionLeave?: (node: Assertion) => void + onBackreferenceEnter?: (node: Backreference) => void + onBackreferenceLeave?: (node: Backreference) => void + onCapturingGroupEnter?: (node: CapturingGroup) => void + onCapturingGroupLeave?: (node: CapturingGroup) => void + onCharacterEnter?: (node: Character) => void + onCharacterLeave?: (node: Character) => void + onCharacterClassEnter?: (node: CharacterClass) => void + onCharacterClassLeave?: (node: CharacterClass) => void + onCharacterClassRangeEnter?: (node: CharacterClassRange) => void + onCharacterClassRangeLeave?: (node: CharacterClassRange) => void + onCharacterSetEnter?: (node: CharacterSet) => void + onCharacterSetLeave?: (node: CharacterSet) => void + onFlagsEnter?: (node: Flags) => void + onFlagsLeave?: (node: Flags) => void + onGroupEnter?: (node: Group) => void + onGroupLeave?: (node: Group) => void + onPatternEnter?: (node: Pattern) => void + onPatternLeave?: (node: Pattern) => void + onQuantifierEnter?: (node: Quantifier) => void + onQuantifierLeave?: (node: Quantifier) => void + onRegExpLiteralEnter?: (node: RegExpLiteral) => void + onRegExpLiteralLeave?: (node: RegExpLiteral) => void } } diff --git a/test/fixtures/parser/literal.ts b/test/fixtures/parser/literal.ts index df61562..13ed9b6 100644 --- a/test/fixtures/parser/literal.ts +++ b/test/fixtures/parser/literal.ts @@ -1,8 +1,9 @@ import fs from "fs" import path from "path" -type FixtureData = { - [filename: string]: { +type FixtureData = Record< + string, + { options: { strict: boolean ecmaVersion: @@ -16,21 +17,20 @@ type FixtureData = { | 2021 | 2022 } - patterns: { - [source: string]: - | { ast: object } - | { error: { message: string; index: number } } - } + patterns: Record< + string, + { ast: object } | { error: { message: string; index: number } } + > } -} +> -const Fixtures: FixtureData = {} +export const Fixtures: FixtureData = {} const fixturesRoot = path.join(__dirname, "literal") for (const filename of fs.readdirSync(fixturesRoot)) { Fixtures[filename] = JSON.parse( fs.readFileSync(path.join(fixturesRoot, filename), "utf8"), - (_, v) => (v === "$$Infinity" ? Infinity : v), - ) + (_, v: unknown) => (v === "$$Infinity" ? Infinity : v), + ) as FixtureData[string] } export function save(): void { @@ -39,11 +39,9 @@ export function save(): void { path.join(fixturesRoot, filename), JSON.stringify( Fixtures[filename], - (_, v) => (v === Infinity ? "$$Infinity" : v), + (_, v: unknown) => (v === Infinity ? "$$Infinity" : v), 2, ), ) } } - -export { Fixtures } diff --git a/test/fixtures/visitor/index.ts b/test/fixtures/visitor/index.ts index c9317ca..0efcf63 100644 --- a/test/fixtures/visitor/index.ts +++ b/test/fixtures/visitor/index.ts @@ -1,8 +1,9 @@ import fs from "fs" import path from "path" -type FixtureData = { - [filename: string]: { +type FixtureData = Record< + string, + { options: { strict?: boolean ecmaVersion?: @@ -16,21 +17,19 @@ type FixtureData = { | 2021 | 2022 } - patterns: { - [source: string]: string[] - } + patterns: Record } -} +> const fixturesRoot = __dirname export const Fixtures: FixtureData = fs .readdirSync(fixturesRoot) - .filter(filename => path.extname(filename) === ".json") + .filter((filename) => path.extname(filename) === ".json") .reduce((fixtures, filename) => { fixtures[filename] = JSON.parse( fs.readFileSync(path.join(fixturesRoot, filename), "utf8"), - (_, v) => (v === "$$Infinity" ? Infinity : v), - ) + (_, v: unknown) => (v === "$$Infinity" ? Infinity : v), + ) as FixtureData[string] return fixtures }, {}) export function save(): void { @@ -39,7 +38,7 @@ export function save(): void { path.join(fixturesRoot, filename), JSON.stringify( Fixtures[filename], - (_, v) => (v === Infinity ? "$$Infinity" : v), + (_, v: unknown) => (v === Infinity ? "$$Infinity" : v), 2, ), ) diff --git a/test/parser.ts b/test/parser.ts index 7a6d914..dcf586b 100644 --- a/test/parser.ts +++ b/test/parser.ts @@ -1,5 +1,6 @@ import assert from "assert" import { parseRegExpLiteral, RegExpParser } from "../src/index" +import type { RegExpSyntaxError } from "../src/regexp-syntax-error" import { cloneWithoutCircular } from "../scripts/clone-without-circular" import { Fixtures } from "./fixtures/parser/literal" @@ -48,8 +49,9 @@ describe("parseRegExpLiteral function:", () => { try { parseRegExpLiteral(source, options) } catch (err) { - assert.strictEqual(err.message, expected.message) - assert.strictEqual(err.index, expected.index) + const error = err as RegExpSyntaxError + assert.strictEqual(error.message, expected.message) + assert.strictEqual(error.index, expected.index) return } assert.fail("Should fail, but succeeded.") diff --git a/test/visitor.ts b/test/visitor.ts index d827540..4ff40d0 100644 --- a/test/visitor.ts +++ b/test/visitor.ts @@ -1,15 +1,11 @@ import assert from "assert" -import { - AST, - RegExpParser, - parseRegExpLiteral, - visitRegExpAST, -} from "../src/index" +import type { AST, RegExpParser } from "../src/index" +import { parseRegExpLiteral, visitRegExpAST } from "../src/index" import { cloneWithoutCircular } from "../scripts/clone-without-circular" import { Fixtures } from "./fixtures/visitor" -function generateAST(source: string, options: RegExpParser.Options): any { - return cloneWithoutCircular(parseRegExpLiteral(source, options)) +function generateAST(source: string, options: RegExpParser.Options): AST.Node { + return cloneWithoutCircular(parseRegExpLiteral(source, options)) as AST.Node } describe("visitRegExpAST function:", () => {