Specialized pattern matching class for complex regex operations with proper escape sequence handling. The PatternMatcher class is ideal for ESLint rules that need sophisticated string pattern detection and replacement capabilities.
Advanced pattern matching class that handles escape sequences correctly.
/**
* The class to find patterns as considering escape sequences
* @param pattern - The RegExp pattern to match (must include 'g' flag)
* @param options - Optional configuration for pattern matching
*/
class PatternMatcher {
constructor(pattern: RegExp, options?: PatternMatcherOptions);
/**
* Find all matches of the pattern in a given string
* @param str - The string to search in
* @returns Iterator yielding match information for each occurrence
*/
execAll(str: string): IterableIterator<RegExpExecArray>;
/**
* Check whether the pattern is found in a given string
* @param str - The string to check
* @returns True if the pattern was found in the string
*/
test(str: string): boolean;
/**
* Replace matches in a given string
* @param str - The string to be replaced
* @param replacer - The string or function to replace matches with
* @returns The replaced string
*/
[Symbol.replace](str: string, replacer: string | ReplacerFunction): string;
}
interface PatternMatcherOptions {
escaped?: boolean;
}
type ReplacerFunction = (match: string, ...args: any[]) => string;Create pattern matchers for common string patterns.
import { PatternMatcher } from "eslint-utils";
create(context) {
// Match TODO comments
const todoPattern = new PatternMatcher(/TODO:?\s*(.*)/gi);
// Match console methods
const consolePattern = new PatternMatcher(/console\.(log|warn|error|debug)/g);
// Match magic numbers (numbers not 0, 1, -1)
const magicNumberPattern = new PatternMatcher(/\b(?!0\b|1\b|-1\b)\d+\b/g);
return {
Program(node) {
const sourceCode = context.getSourceCode();
const text = sourceCode.getText();
// Find all TODO comments
for (const match of todoPattern.execAll(text)) {
console.log('TODO found:', match[1]); // The captured todo text
}
// Check for console usage
if (consolePattern.test(text)) {
context.report(node, 'Console statements found in code');
}
// Find magic numbers
for (const match of magicNumberPattern.execAll(text)) {
console.log('Magic number:', match[0], 'at position', match.index);
}
}
};
}Handle patterns that need to consider escape sequences.
import { PatternMatcher } from "eslint-utils";
create(context) {
// Pattern for unescaped quotes in strings
const unescapedQuotePattern = new PatternMatcher(/"(?:[^"\\]|\\.)*"/g, {
escaped: false
});
// Pattern for escaped sequences
const escapePattern = new PatternMatcher(/\\./g, {
escaped: true
});
return {
Literal(node) {
if (typeof node.value === 'string') {
const rawValue = node.raw;
// Check for properly escaped quotes
if (unescapedQuotePattern.test(rawValue)) {
console.log('String literal with quotes found');
}
// Analyze escape sequences
for (const match of escapePattern.execAll(rawValue)) {
if (match[0] === '\\\\') {
console.log('Escaped backslash found');
} else if (match[0] === '\\"') {
console.log('Escaped quote found');
}
}
}
}
};
}Use PatternMatcher for string replacements with placeholder support.
import { PatternMatcher } from "eslint-utils";
create(context) {
// Pattern for old-style string formatting
const sprintfPattern = new PatternMatcher(/sprintf\s*\(\s*["']([^"']*)["']/g);
// Pattern for template literal conversion
const templatePattern = new PatternMatcher(/%([sdif])/g);
return {
CallExpression(node) {
if (node.callee.name === 'sprintf' && node.arguments[0]) {
const sourceCode = context.getSourceCode();
const text = sourceCode.getText(node.arguments[0]);
// Convert sprintf-style to template literal
const converted = templatePattern[Symbol.replace](text, (match, type) => {
switch (type) {
case 's': return '${String($&)}';
case 'd':
case 'i': return '${Number($&)}';
case 'f': return '${parseFloat($&)}';
default: return match;
}
});
context.report({
node,
message: 'Consider using template literals instead of sprintf',
data: { converted }
});
}
}
};
}Analyze complex code patterns and structures.
import { PatternMatcher } from "eslint-utils";
create(context) {
// Pattern for React component prop destructuring
const propDestructurePattern = new PatternMatcher(
/const\s*{\s*([^}]+)\s*}\s*=\s*props/g
);
// Pattern for async/await usage
const asyncPattern = new PatternMatcher(/await\s+\w+\s*\(/g);
// Pattern for error handling
const errorHandlePattern = new PatternMatcher(
/(try\s*{[^}]*}\s*catch\s*\([^)]*\)\s*{[^}]*})/gs
);
return {
Program(node) {
const sourceCode = context.getSourceCode();
const text = sourceCode.getText();
// Analyze prop destructuring patterns
for (const match of propDestructurePattern.execAll(text)) {
const props = match[1].split(',').map(prop => prop.trim());
if (props.length > 5) {
context.report(node, `Too many destructured props: ${props.length}`);
}
}
// Check async/await patterns
const asyncMatches = Array.from(asyncPattern.execAll(text));
if (asyncMatches.length > 10) {
context.report(node, 'Consider reducing async operations');
}
// Analyze error handling coverage
const errorBlocks = Array.from(errorHandlePattern.execAll(text));
const asyncCount = asyncMatches.length;
if (asyncCount > 0 && errorBlocks.length === 0) {
context.report(node, 'Async operations without error handling');
}
}
};
}Use function-based replacers for complex transformations.
import { PatternMatcher } from "eslint-utils";
create(context) {
// Pattern for camelCase to kebab-case conversion
const camelCasePattern = new PatternMatcher(/([a-z])([A-Z])/g);
// Pattern for function parameter analysis
const functionParamPattern = new PatternMatcher(
/function\s*\w*\s*\(([^)]*)\)/g
);
return {
Property(node) {
if (node.key.type === 'Identifier') {
const keyName = node.key.name;
// Convert camelCase keys to kebab-case
const kebabCase = camelCasePattern[Symbol.replace](keyName, (match, lower, upper) => {
return lower + '-' + upper.toLowerCase();
});
if (kebabCase !== keyName) {
context.report({
node: node.key,
message: `Consider using kebab-case: '${kebabCase}'`
});
}
}
},
FunctionDeclaration(node) {
const sourceCode = context.getSourceCode();
const text = sourceCode.getText(node);
// Analyze function parameters
for (const match of functionParamPattern.execAll(text)) {
const params = match[1].split(',').map(p => p.trim()).filter(Boolean);
const analysis = params.map((param, index) => {
// Extract parameter name (handle defaults, destructuring)
const cleanParam = param.replace(/\s*=.*$/, '').replace(/^{\s*|\s*}$/g, '');
return {
index,
name: cleanParam,
hasDefault: param.includes('='),
isDestructured: param.includes('{') || param.includes('[')
};
});
const defaultParams = analysis.filter(p => p.hasDefault);
const destructuredParams = analysis.filter(p => p.isDestructured);
if (defaultParams.length > 3) {
context.report(node, 'Too many parameters with defaults');
}
if (destructuredParams.length > 1) {
context.report(node, 'Multiple destructured parameters detected');
}
}
}
};
}Process large texts efficiently using the iterator interface.
import { PatternMatcher } from "eslint-utils";
create(context) {
// Pattern for finding import statements
const importPattern = new PatternMatcher(
/import\s+(?:{[^}]*}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/g
);
return {
Program(node) {
const sourceCode = context.getSourceCode();
const text = sourceCode.getText();
const imports = new Map();
const duplicateModules = new Set();
// Process imports efficiently with iterator
for (const match of importPattern.execAll(text)) {
const [fullMatch, moduleName] = match;
const position = match.index;
if (imports.has(moduleName)) {
duplicateModules.add(moduleName);
} else {
imports.set(moduleName, {
match: fullMatch,
position,
line: sourceCode.getLocFromIndex(position).line
});
}
// Early termination for performance
if (imports.size > 100) {
context.report(node, 'Too many imports detected');
break;
}
}
// Report duplicate imports
for (const module of duplicateModules) {
const importInfo = imports.get(module);
context.report({
node,
loc: sourceCode.getLocFromIndex(importInfo.position),
message: `Duplicate import of module: ${module}`
});
}
}
};
}PatternMatcher supports replacement placeholders:
$$ - Literal dollar sign$& - The matched substring$' - The portion after the match$1, $2, etc. - Captured groupsconst pattern = new PatternMatcher(/(\w+)\s*=\s*(\w+)/g);
const result = pattern[Symbol.replace](
"name = value",
"const $1 = $2;" // Becomes "const name = value;"
);When escaped: true is set, the matcher properly handles escape sequences:
const pattern = new PatternMatcher(/\\n/g, { escaped: true });
// Will correctly match escaped newlines in stringstest() for simple existence checks instead of execAll()execAll() for processing all matches efficiently