diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index d360fda192a00..b59849d296bb9 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -128,8 +128,6 @@ const defaultConfig: ParserConfig = { regexParsingWithErrorRecovery: true }; -class ParseError extends Error { } - export type ParsingError = { message: string; offset: number; @@ -167,6 +165,8 @@ export class Parser { // Note: this doesn't produce an exact syntax tree but a normalized one // ContextKeyExpression's that we use as AST nodes do not expose constructors that do not normalize + private static _parseError = new Error(); + // lifetime note: `_scanner` lives as long as the parser does, i.e., is not reset between calls to `parse` private readonly _scanner = new Scanner(); @@ -211,11 +211,11 @@ export class Parser { const peek = this._peek(); const additionalInfo = peek.type === TokenType.Str ? hintUnexpectedToken : undefined; this._parsingErrors.push({ message: errorUnexpectedToken, offset: peek.offset, lexeme: Scanner.getLexeme(peek), additionalInfo }); - throw new ParseError(); + throw Parser._parseError; } return expr; } catch (e) { - if (!(e instanceof ParseError)) { + if (!(e === Parser._parseError)) { throw e; } return undefined; @@ -311,7 +311,7 @@ export class Parser { } const regexLexeme = expr.lexeme; const closingSlashIndex = regexLexeme.lastIndexOf('/'); - const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : regexLexeme.substring(closingSlashIndex + 1); + const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : this._removeFlagsGY(regexLexeme.substring(closingSlashIndex + 1)); let regexp: RegExp | null; try { regexp = new RegExp(regexLexeme.substring(1, closingSlashIndex), flags); @@ -365,7 +365,7 @@ export class Parser { const regexLexeme = lexemeReconstruction.join(''); const closingSlashIndex = regexLexeme.lastIndexOf('/'); - const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : regexLexeme.substring(closingSlashIndex + 1); + const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : this._removeFlagsGY(regexLexeme.substring(closingSlashIndex + 1)); let regexp: RegExp | null; try { regexp = new RegExp(regexLexeme.substring(1, closingSlashIndex), flags); @@ -481,7 +481,7 @@ export class Parser { case TokenType.EOF: this._parsingErrors.push({ message: errorUnexpectedEOF, offset: peek.offset, lexeme: '', additionalInfo: hintUnexpectedEOF }); - throw new ParseError(); + throw Parser._parseError; default: throw this._errExpectedButGot(`true | false | KEY \n\t| KEY '=~' REGEX \n\t| KEY ('==' | '!=' | '<' | '<=' | '>' | '>=' | 'in' | 'not' 'in') value`, this._peek()); @@ -512,6 +512,11 @@ export class Parser { } } + private _flagsGYRe = /g|y/g; + private _removeFlagsGY(flags: string): string { + return flags.replaceAll(this._flagsGYRe, ''); + } + // careful: this can throw if current token is the initial one (ie index = 0) private _previous() { return this._tokens[this._current - 1]; @@ -546,7 +551,7 @@ export class Parser { const offset = got.offset; const lexeme = Scanner.getLexeme(got); this._parsingErrors.push({ message, offset, lexeme, additionalInfo }); - return new ParseError(); + return Parser._parseError; } private _check(type: TokenType) { @@ -1558,7 +1563,7 @@ function eliminateConstantsInArray(arr: ContextKeyExpression[]): (ContextKeyExpr return newArr; } -class ContextKeyAndExpr implements IContextKeyExpression { +export class ContextKeyAndExpr implements IContextKeyExpression { public static create(_expr: ReadonlyArray, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined { return ContextKeyAndExpr._normalizeArr(_expr, negated, extraRedundantCheck); @@ -1757,7 +1762,7 @@ class ContextKeyAndExpr implements IContextKeyExpression { } } -class ContextKeyOrExpr implements IContextKeyExpression { +export class ContextKeyOrExpr implements IContextKeyExpression { public static create(_expr: ReadonlyArray, negated: ContextKeyExpression | null, extraRedundantCheck: boolean): ContextKeyExpression | undefined { return ContextKeyOrExpr._normalizeArr(_expr, negated, extraRedundantCheck); diff --git a/src/vs/platform/contextkey/test/common/parser.test.ts b/src/vs/platform/contextkey/test/common/parser.test.ts index 0c462e54d84d9..6d08961dae8c9 100644 --- a/src/vs/platform/contextkey/test/common/parser.test.ts +++ b/src/vs/platform/contextkey/test/common/parser.test.ts @@ -176,9 +176,9 @@ suite('Context Key Parser', () => { assert.deepStrictEqual(parseToStr(input), "resource =~ /((\\/scratch\\/(?!update)(.*)\\/)|((\\/src\\/).*\\/)).*$/"); }); - test(`resourcePath =~ /\.md(\.yml|\.txt)*$/gim`, () => { - const input = `resourcePath =~ /\.md(\.yml|\.txt)*$/gim`; - assert.deepStrictEqual(parseToStr(input), "resourcePath =~ /.md(.yml|.txt)*$/gim"); + test(`resourcePath =~ /\.md(\.yml|\.txt)*$/giym`, () => { + const input = `resourcePath =~ /\.md(\.yml|\.txt)*$/giym`; + assert.deepStrictEqual(parseToStr(input), "resourcePath =~ /.md(.yml|.txt)*$/im"); }); });