Skip to content

Commit

Permalink
Avoid wrapping multiple layers of non-capture groups
Browse files Browse the repository at this point in the history
  • Loading branch information
hlysine committed Aug 20, 2023
1 parent bdc6537 commit 058a236
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 32 deletions.
48 changes: 30 additions & 18 deletions src/helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
import { IncompleteToken, RegExpLiteral } from './types';

export function escapeRegExp(text: string): string {
return text.replace(/[-[/\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}

export const hexNumber = /^[0-9a-fA-F]+$/;

export const octalNumber = /^[0-7]+$/;

// octal escape sequences are not matched here because they should be wrapped in a character class
export const negatableCharLiteral = /^(?:\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2})$/;

// last option refers to octal character or capture group backreference
export const charLiteral = /^(?:\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}|\\\d{1,3})$/;

export const captureName = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

export const negatableTokens = /^\\[sdwb]$/;

export const flagString = /^[gmiyusd]*$/;

/**
* Check whether a given value is a template strings array.
* @param arg - The argument to check.
Expand Down Expand Up @@ -145,22 +165,14 @@ export function isBracketGroup(regExp: string): boolean {
return false;
}

export function escapeRegExp(text: string): string {
return text.replace(/[-[/\]{}()*+?.,\\^$|#\s]/g, '\\$&');
export function wrapIfNeeded(regExp: string): string {
if (regExp.length === 1) return regExp;
if (regExp.length === 2 && regExp.startsWith('\\')) return regExp;
if (charLiteral.test(regExp)) return regExp;
if (isCharacterClass(regExp)) return regExp;
if (isBracketGroup(regExp)) {
// need to wrap lookarounds because they are not directly quantifiable
if (!/^\((?:\?=|\?!|\?<=|\?<!)/.test(regExp)) return regExp;
}
return `(?:${regExp})`;
}

export const hexNumber = /^[0-9a-fA-F]+$/;

export const octalNumber = /^[0-7]+$/;

// octal escape sequences are not matched here because they should be wrapped in a character class
export const negatableCharLiteral = /^(?:\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2})$/;

// last option refers to octal character or capture group backreference
export const charLiteral = /^(?:\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}|\\\d{1,3})$/;

export const captureName = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

export const negatableTokens = /^\\[sdwb]$/;

export const flagString = /^[gmiyusd]*$/;
4 changes: 3 additions & 1 deletion src/modifiers/GroupModifier.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RegExpModifier } from '../types';
import { isBracketGroup } from '../helper';

export enum GroupType {
NonCapture = 'nonCapture',
Expand All @@ -18,7 +19,8 @@ export default class GroupModifier implements RegExpModifier {
public modify(regExp: string): [string, string?] {
switch (this.groupType) {
case GroupType.NonCapture:
return [`(?:${regExp})`];
// do not wrap multiple layers of non-capturing groups
return [isBracketGroup(regExp) && regExp.startsWith('(?:') ? regExp : `(?:${regExp})`];
case GroupType.PositiveLookahead:
return [`(?=${regExp})`];
case GroupType.NegativeLookahead:
Expand Down
14 changes: 1 addition & 13 deletions src/modifiers/QuantityModifier.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import { RegExpModifier } from '../types';
import { charLiteral, isBracketGroup, isCharacterClass } from '../helper';
import { wrapIfNeeded } from '../helper';

const NOT_QUANTIFIABLE = new Set(['^', '$', '\\b', '\\B']);

function wrapIfNeeded(regExp: string): string {
if (regExp.length === 1) return regExp;
if (regExp.length === 2 && regExp.startsWith('\\')) return regExp;
if (charLiteral.test(regExp)) return regExp;
if (isCharacterClass(regExp)) return regExp;
if (isBracketGroup(regExp)) {
// need to wrap lookarounds because they are not directly quantifiable
if (!/^\((?:\?=|\?!|\?<=|\?<!)/.test(regExp)) return regExp;
}
return `(?:${regExp})`;
}

export default abstract class QuantityModifier implements RegExpModifier {
public lazy: boolean;

Expand Down
4 changes: 4 additions & 0 deletions test/functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,10 @@ describe('group', () => {
expect(() => not.group`foo`.toString()).toThrow();
expect(() => not(group`foo`).toString()).toThrow();
});
it('does not wrap multiple times', () => {
expect(group.group.exactly`foo`.toString()).toBe('(?:foo)');
expect(group.oneOf`foo``bar`.toString()).toBe('(?:foo|bar)');
});
it('throws for invalid argument', () => {
expect(group().toString()).toBe('(?:)');
// @ts-expect-error - testing invalid arguments
Expand Down

0 comments on commit 058a236

Please sign in to comment.