Skip to content

fix: escape dots when a brace range falls back to literal matching - #231

Open
Kumar44developer wants to merge 1 commit into
micromatch:masterfrom
Kumar44developer:fix/escape-dots-in-literal-range-fallback
Open

Kumar44developer wants to merge 1 commit into
micromatch:masterfrom
Kumar44developer:fix/escape-dots-in-literal-range-fallback

Conversation

@Kumar44developer

Copy link
Copy Markdown

Escape dots when a brace range falls back to literal matching

Problem

When a brace range can't be expressed as a character class, expandRange falls back to matching the range as literal text. That fallback escapes each bound but joins them with a raw '..':

// lib/parse.js
return args.map(v => utils.escapeRegex(v)).join('..');

escapeRegex escapes ., so the bounds are safe — but the two dots in the joiner are not, and they reach the generated regex as wildcards.

{01..03} takes this path, because its character class [01-03] contains the invalid range 1-0 and new RegExp throws. The result is an overly permissive pattern:

picomatch.makeRe('{01..03}');
//=> /^(?:01..03)$/

picomatch.isMatch('01ab03', '{01..03}');  //=> true   (expected false)
picomatch.isMatch('01__03', '{01..03}');  //=> true   (expected false)
picomatch.isMatch('01/x03', '{01..03}');  //=> true   (expected false)

The last case is the most surprising one: the wildcards let the pattern match across a path separator, which a brace range should never do.

A realistic version:

picomatch.isMatch('logs/01ab03.txt', 'logs/{01..03}.txt'); //=> true (expected false)

Fix

Escape the dots in the fallback so it matches exactly the literal text it represents:

return args.map(v => utils.escapeRegex(v)).join('\\.\\.');
picomatch.makeRe('{01..03}');
//=> /^(?:01\.\.03)$/

picomatch.isMatch('01..03', '{01..03}');  //=> true   (unchanged)
picomatch.isMatch('01ab03', '{01..03}');  //=> false  (fixed)
picomatch.isMatch('01/x03', '{01..03}');  //=> false  (fixed)

Ranges that can be expressed as a character class are untouched, since they never reach the fallback — {a..c} still compiles to [a-c] and {1..3} to [1-3].

Scope

This PR only fixes the escaping of the fallback output. It deliberately does not add zero-padded range expansion ({01..03} → 01 02 03), since the readme points to options.expandRange with fill-range for that, and adding it here would change matching behaviour rather than fix a regex-escaping bug. Happy to follow up separately if you'd like that supported natively.

Tests

Added a test to test/braces.js covering the literal match, the wildcard false-positives, and the path-separator case. Verified it fails on current master:

1) braces
     should escape the dots of ranges that fall back to literal matching:
   AssertionError: assert(!isMatch('01ab03', '{01..03}'))
   + expected - actual
   -false
   +true

With the fix the full suite passes — 1999 passing (1998 before, plus the new test) — and eslint is clean.

When a brace range cannot be expressed as a character class, expandRange falls back to matching the range as literal text. The fallback escaped each bound but joined them with a raw '..', so the dots were emitted as regex wildcards.

As a result '{01..03}', whose character class '[01-03]' is an invalid range, compiled to /^(?:01..03)$/ and matched '01ab03', '01__03' and even '01/x03', crossing a path separator. Escaping the dots makes the fallback match only the literal text it represents.
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.

1 participant