or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

ast-utils.mdindex.mdpattern-matching.mdreference-tracking.mdstatic-analysis.mdtoken-predicates.md
tile.json

pattern-matching.mddocs/

Pattern Matching

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.

Capabilities

PatternMatcher Class

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;

Usage Examples

Basic Pattern Matching

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);
      }
    }
  };
}

Advanced Pattern Matching with Escape Handling

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');
          }
        }
      }
    }
  };
}

String Replacement and Transformation

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 }
        });
      }
    }
  };
}

Complex Pattern Analysis

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');
      }
    }
  };
}

Custom Replacer Functions

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');
        }
      }
    }
  };
}

Iterator-based Processing

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}`
        });
      }
    }
  };
}

Advanced Features

Placeholder Replacement

PatternMatcher supports replacement placeholders:

  • $$ - Literal dollar sign
  • $& - The matched substring
  • `$`` - The portion before the match
  • $' - The portion after the match
  • $1, $2, etc. - Captured groups
const pattern = new PatternMatcher(/(\w+)\s*=\s*(\w+)/g);
const result = pattern[Symbol.replace](
  "name = value", 
  "const $1 = $2;" // Becomes "const name = value;"
);

Escape Sequence Handling

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 strings

Performance Considerations

  • Use test() for simple existence checks instead of execAll()
  • Iterate with execAll() for processing all matches efficiently
  • Consider breaking early in loops for large texts
  • Cache PatternMatcher instances for repeated use