Skip to content

Fix O(n²) performance issue with unmatched parentheses - #324

Merged
bartveneman merged 2 commits into
mainfrom
claude/serene-dijkstra-95axry
Oct 25, 2026
Merged

bartveneman merged 2 commits into
mainfrom
claude/serene-dijkstra-95axry

Conversation

@bartveneman

Copy link
Copy Markdown
Member

Summary

This PR fixes a quadratic time complexity issue in the condition parser when handling unmatched opening parentheses. Previously, the parser would rescan the same range repeatedly for each unclosed parenthesis, causing severe performance degradation on inputs with many consecutive ( characters.

Key Changes

  • Added caching for unmatched parentheses: Introduced unmatched_opens Set and unmatched_opens_end to cache positions of parentheses that cannot be matched within a given range, avoiding redundant rescans
  • Early exit optimization: When scanning for a matching parenthesis, the parser now checks if the current position was previously identified as unmatched and returns immediately
  • Stack tracking: Added open_stack array to track all open parenthesis positions during scanning, enabling efficient recording of unmatched opens on failure
  • Added performance tests: Included test cases verifying linear-time parsing of pathological inputs (32,000 consecutive () and confirming that matched features after unclosed parentheses are still correctly parsed

Implementation Details

  • The cache is only valid for a specific end position; it's invalidated when the range changes
  • The open parenthesis stack is maintained during token scanning to record which positions remain unmatched when the scan fails
  • The optimization is transparent to callers and maintains correctness for both matched and unmatched parenthesis scenarios

https://claude.ai/code/session_01HC4UoMkXyXbW2knnFrvmJ6

…iner preludes

scan_matching_paren rescanned to the end of the prelude for every
unmatched "(", so e.g. `@media ((((…` took ~6s at 32KB. Remember the
parens left open after a failed scan so later scans starting at one of
them return immediately, making the parse linear.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Package Size Increase

📦 Package 📏 Base Size 📏 Source Size 📈 Size Change
@projectwallace/css-parser 43.9 kB 44.1 kB +156 B

Copy link
Copy Markdown
Member Author

The Audit packages check is red, but not because of this PR. pnpm audit --audit-level=high reports GHSA-mrhx-c7xc-m3w5 (zod < 4.6.6, "can exhaust memory while validating oversized constrained collections"), reached via the dev dependency .>knip>zod. This PR only touches src/parse-condition.ts and a test file, not package.json or the lockfile, and the same advisory reproduces on the base branch.

No fix exists on the base yet. A fix is either bumping knip to a release that depends on zod >= 4.6.6, or adding a pnpm.overrides entry for zod. I left it out of this PR to keep it scoped to the parser fix, so it should land as a separate change. Everything else that has finished so far (unit tests, lint-unused, pack) is green; build, types and lint are still running.


Generated by Claude Code

@bartveneman
bartveneman merged commit a7210e9 into main Oct 25, 2026
15 of 16 checks passed
@bartveneman
bartveneman deleted the claude/serene-dijkstra-95axry branch October 25, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants