Skip to content

feat(preset-basic): enhance findMatchingBrace to handle strings, comments, and template literals #33

@aridyckovsky

Description

@aridyckovsky

Summary

Enhance findMatchingBrace helper to correctly skip braces inside strings, comments, and template literals to avoid false positives when parsing code blocks.

Problem

Current implementation (in packages/preset-basic/src/helpers.ts) naively counts braces without considering context:

const findMatchingBrace = (content: string, startIndex: number): number => {
  let depth = 1;
  for (let i = startIndex + 1; i < content.length; i++) {
    if (content[i] === '{') depth++;
    if (content[i] === '}') depth--;
    if (depth === 0) return i;
  }
  return -1;
};

Edge cases that fail:

Effect.gen(function* () {
  const message = "hello { world }"; // Braces in string
  const template = `value: ${count}`;  // Braces in template literal
  // { This brace in a comment }
  yield* doSomething();
});

Proposed Solution

Enhance the function to:

  1. Skip string literals (single and double quoted)
  2. Skip template literals (backtick strings)
  3. Skip single-line comments (//)
  4. Skip multi-line comments (/* */)

Alternative approach: Use TypeScript AST parsing with @typescript-eslint/parser for accurate parsing (more robust but heavier dependency).

Implementation Sketch

function findMatchingBrace(content: string, startIndex: number): number {
  let depth = 1;
  let inString: string | null = null; // ', ", or `
  let inSingleLineComment = false;
  let inMultiLineComment = false;

  for (let i = startIndex + 1; i < content.length; i++) {
    const char = content[i];
    const nextChar = content[i + 1];
    const prevChar = content[i - 1];

    // Handle comment starts
    if (!inString && char === '/' && nextChar === '/') {
      inSingleLineComment = true;
      i++; continue;
    }
    if (!inString && char === '/' && nextChar === '*') {
      inMultiLineComment = true;
      i++; continue;
    }

    // Handle comment ends
    if (inSingleLineComment && char === '\n') {
      inSingleLineComment = false;
      continue;
    }
    if (inMultiLineComment && char === '*' && nextChar === '/') {
      inMultiLineComment = false;
      i++; continue;
    }

    // Skip if in comment
    if (inSingleLineComment || inMultiLineComment) continue;

    // Handle strings
    if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
      if (inString === char) {
        inString = null;
      } else if (!inString) {
        inString = char;
      }
      continue;
    }

    // Skip if in string
    if (inString) continue;

    // Count braces
    if (char === '{') depth++;
    if (char === '}') depth--;
    if (depth === 0) return i;
  }

  return -1;
}

Acceptance Criteria

  • Handles braces in double-quoted strings
  • Handles braces in single-quoted strings
  • Handles braces in template literals
  • Handles braces in single-line comments
  • Handles braces in multi-line comments
  • Handles escaped quotes (\\" and \\')
  • All existing tests pass
  • New edge-case tests added

Alternative: TypeScript AST Parsing

For maximum robustness, consider using @typescript-eslint/parser:

import { parse } from '@typescript-eslint/parser';

// Parse and walk AST instead of regex/string matching

Pros: Bulletproof parsing, handles all edge cases
Cons: Heavier dependency, slower for simple rules

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    pkg:presetIssues related to preset packagespriority:lowLow priorityrulesMigration rules and patternstype:featureNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions