Skip to content

Commit

Permalink
Detect leading dots in extglob subpatterns
Browse files Browse the repository at this point in the history
Fix: isaacs/node-glob#387

Backport of f35d0b8 and
35b00ba from v6 main branch.
  • Loading branch information
isaacs committed Jan 17, 2023
1 parent 9556826 commit 4d0b7f5
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 30 deletions.
74 changes: 52 additions & 22 deletions minimatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,11 +440,23 @@ class Minimatch {
let pl
let sp
// . and .. never match anything that doesn't start with .,
// even when options.dot is set.
const patternStart = pattern.charAt(0) === '.' ? '' // anything
// not (start or / followed by . or .. followed by / or end)
: options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))'
: '(?!\\.)'
// even when options.dot is set. However, if the pattern
// starts with ., then traversal patterns can match.
let dotTravAllowed = pattern.charAt(0) === '.'
let dotFileAllowed = options.dot || dotTravAllowed
const patternStart = () =>
dotTravAllowed
? ''
: dotFileAllowed
? '(?!(?:^|\\/)\\.{1,2}(?:$|\\/))'
: '(?!\\.)'
const subPatternStart = (p) =>
p.charAt(0) === '.'
? ''
: options.dot
? '(?!(?:^|\\/)\\.{1,2}(?:$|\\/))'
: '(?!\\.)'


const clearStateChar = () => {
if (stateChar) {
Expand Down Expand Up @@ -533,7 +545,7 @@ class Minimatch {
if (options.noext) clearStateChar()
continue

case '(':
case '(': {
if (inClass) {
re += '('
continue
Expand All @@ -544,46 +556,64 @@ class Minimatch {
continue
}

patternListStack.push({
const plEntry = {
type: stateChar,
start: i - 1,
reStart: re.length,
open: plTypes[stateChar].open,
close: plTypes[stateChar].close
})
// negation is (?:(?!js)[^/]*)
re += stateChar === '!' ? '(?:(?!(?:' : '(?:'
close: plTypes[stateChar].close,
}
this.debug(this.pattern, '\t', plEntry)
patternListStack.push(plEntry)
// negation is (?:(?!(?:js)(?:<rest>))[^/]*)
re += plEntry.open
// next entry starts with a dot maybe?
if (plEntry.start === 0 && plEntry.type !== '!') {
dotTravAllowed = true
re += subPatternStart(pattern.slice(i + 1))
}
this.debug('plType %j %j', stateChar, re)
stateChar = false
continue
continue
}

case ')':
if (inClass || !patternListStack.length) {
case ')': {
const plEntry = patternListStack[patternListStack.length - 1]
if (inClass || !plEntry) {
re += '\\)'
continue
}
patternListStack.pop()

// closing an extglob
clearStateChar()
hasMagic = true
pl = patternListStack.pop()
pl = plEntry
// negation is (?:(?!js)[^/]*)
// The others are (?:<pattern>)<type>
re += pl.close
if (pl.type === '!') {
negativeLists.push(pl)
negativeLists.push(Object.assign(pl, { reEnd: re.length }))
}
pl.reEnd = re.length
continue
continue
}

case '|':
if (inClass || !patternListStack.length) {
case '|': {
const plEntry = patternListStack[patternListStack.length - 1]
if (inClass || !plEntry) {
re += '\\|'
continue
}

clearStateChar()
re += '|'
continue
// next subpattern can start with a dot?
if (plEntry.start === 0 && plEntry.type !== '!') {
dotTravAllowed = true
re += subPatternStart(pattern.slice(i + 1))
}
continue
}

// these are mostly the same in regexp and glob
case '[':
Expand Down Expand Up @@ -743,7 +773,7 @@ class Minimatch {
}

if (addPatternStart) {
re = patternStart + re
re = patternStart() + re
}

// parsing just a piece of a larger pattern.
Expand Down
64 changes: 56 additions & 8 deletions tap-snapshots/test/basic.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ exports[`test/basic.js TAP basic tests > makeRe !!a* 1`] = `
/^(?:(?=.)a[^/]*?)$/
`

exports[`test/basic.js TAP basic tests > makeRe !(.a|js)@(.*) 1`] = `
/^(?:(?!\\.)(?=.)(?:(?!(?:\\.a|js)(?:\\.[^/]*?))[^/]*?)(?:\\.[^/]*?))$/
`

exports[`test/basic.js TAP basic tests > makeRe !\\!a* 1`] = `
/^(?!^(?:(?=.)\\!a[^/]*?)$).*$/
`
Expand All @@ -33,20 +37,24 @@ exports[`test/basic.js TAP basic tests > makeRe #* 1`] = `
/^(?:(?=.)#[^/]*?)$/
`

exports[`test/basic.js TAP basic tests > makeRe * 1`] = `
/^(?:(?!(?:^|\\/)\\.{1,2}(?:$|\\/))(?=.)[^/]*?)$/
`

exports[`test/basic.js TAP basic tests > makeRe *(a/b) 1`] = `
/^(?:(?!\\.)(?=.)[^/]*?\\(a\\/b\\))$/
/^(?:(?=.)[^/]*?\\((?!\\.)a\\/b\\))$/
`

exports[`test/basic.js TAP basic tests > makeRe *(a|{b),c)} 1`] = `
/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/
/^(?:(?=.)(?:(?!\\.)a|(?!\\.)b)*|(?=.)(?:(?!\\.)a|(?!\\.)c)*)$/
`

exports[`test/basic.js TAP basic tests > makeRe *(a|{b,c}) 1`] = `
/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/
/^(?:(?=.)(?:(?!\\.)a|(?!\\.)b)*|(?=.)(?:(?!\\.)a|(?!\\.)c)*)$/
`

exports[`test/basic.js TAP basic tests > makeRe *(a|{b|c,c}) 1`] = `
/^(?:(?!\\.)(?=.)(?:a|b|c)*|(?!\\.)(?=.)(?:a|c)*)$/
/^(?:(?=.)(?:(?!\\.)a|(?!\\.)b|(?!\\.)c)*|(?=.)(?:(?!\\.)a|(?!\\.)c)*)$/
`

exports[`test/basic.js TAP basic tests > makeRe *(a|{b|c,c}) 2`] = `
Expand Down Expand Up @@ -110,11 +118,15 @@ exports[`test/basic.js TAP basic tests > makeRe *c*?** 1`] = `
`

exports[`test/basic.js TAP basic tests > makeRe +(a)!(b)+(c) 1`] = `
/^(?:(?!\\.)(?=.)(?:a)+(?:(?!(?:b)(?:c)+)[^/]*?)(?:c)+)$/
/^(?:(?=.)(?:(?!\\.)a)+(?:(?!(?:b)(?:c)+)[^/]*?)(?:c)+)$/
`

exports[`test/basic.js TAP basic tests > makeRe +(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g 1`] = `
/^(?:(?=.)\\+\\(a\\|[^/]*?\\|c\\\\\\\\\\|d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|f\\\\\\\\\\\\\\\\\\|g)$/
/^(?:(?=.)\\+\\((?!\\.)a\\|(?!\\.)[^/]*?\\|c\\\\\\\\\\|(?!\\.)d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|(?!\\.)f\\\\\\\\\\\\\\\\\\|g)$/
`

exports[`test/basic.js TAP basic tests > makeRe .* 1`] = `
/^(?:(?=.)\\.[^/]*?)$/
`

exports[`test/basic.js TAP basic tests > makeRe /^root:/{s/^[^:]*:[^:]*:([^:]*).*$// 1`] = `
Expand Down Expand Up @@ -157,6 +169,42 @@ exports[`test/basic.js TAP basic tests > makeRe ??**********?****c 1`] = `
/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/
`

exports[`test/basic.js TAP basic tests > makeRe @(*|.*) 1`] = `
/^(?:(?=.)(?:(?!\\.)[^/]*?|\\.[^/]*?))$/
`

exports[`test/basic.js TAP basic tests > makeRe @(*|a) 1`] = `
/^(?:(?=.)(?:(?!(?:^|\\/)\\.{1,2}(?:$|\\/))[^/]*?|(?!(?:^|\\/)\\.{1,2}(?:$|\\/))a))$/
`

exports[`test/basic.js TAP basic tests > makeRe @(.*) 1`] = `
/^(?:(?=.)(?:\\.[^/]*?))$/
`

exports[`test/basic.js TAP basic tests > makeRe @(.*) 2`] = `
/^(?:(?=.)(?:\\.[^/]*?))$/
`

exports[`test/basic.js TAP basic tests > makeRe @(.*|*) 1`] = `
/^(?:(?=.)(?:\\.[^/]*?|(?!\\.)[^/]*?))$/
`

exports[`test/basic.js TAP basic tests > makeRe @(.*|js) 1`] = `
/^(?:(?=.)(?:\\.[^/]*?|(?!\\.)js))$/
`

exports[`test/basic.js TAP basic tests > makeRe @(a|a[(])b 1`] = `
/^(?:(?=.)(?:(?!\\.)a|(?!\\.)a[(])b)$/
`

exports[`test/basic.js TAP basic tests > makeRe @(a|a[)])b 1`] = `
/^(?:(?=.)(?:(?!\\.)a|(?!\\.)a[\\)])b)$/
`

exports[`test/basic.js TAP basic tests > makeRe @(js|.*) 1`] = `
/^(?:(?=.)(?:(?!\\.)js|\\.[^/]*?))$/
`

exports[`test/basic.js TAP basic tests > makeRe X* 1`] = `
/^(?:(?=.)X[^/]*?)$/
`
Expand Down Expand Up @@ -422,9 +470,9 @@ exports[`test/basic.js TAP basic tests > makeRe {/?,*} 1`] = `
`

exports[`test/basic.js TAP basic tests > makeRe {a,*(b|c,d)} 1`] = `
/^(?:a|(?!\\.)(?=.)[^/]*?\\(b\\|c|d\\))$/
/^(?:a|(?=.)[^/]*?\\((?!\\.)b\\|(?!\\.)c|d\\))$/
`

exports[`test/basic.js TAP basic tests > makeRe {a,*(b|{c,d})} 1`] = `
/^(?:a|(?!\\.)(?=.)(?:b|c)*|(?!\\.)(?=.)(?:b|d)*)$/
/^(?:a|(?=.)(?:(?!\\.)b|(?!\\.)c)*|(?=.)(?:(?!\\.)b|(?!\\.)d)*)$/
`
18 changes: 18 additions & 0 deletions test/patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,24 @@ module.exports = [
]
},
['+(a)!(b)+(c)', ['ac', 'acc', 'adc']],

'https://github.com/isaacs/node-glob/issues/387',
() => (files = ['.a', '.a.js', '.js', 'a', 'a.js', 'js']),
['.*', ['.a', '.a.js', '.js']],
['*', ['.a', '.a.js', '.js', 'a', 'a.js', 'js'], { dot: true }],
['@(*|.*)', ['.a', '.a.js', '.js', 'a', 'a.js', 'js']],
['@(.*|*)', ['.a', '.a.js', '.js', 'a', 'a.js', 'js']],
['@(*|a)', ['.a', '.a.js', '.js', 'a', 'a.js', 'js'], { dot: true }],
['@(.*)', ['.a', '.a.js', '.js']],
['@(.*)', ['.a', '.a.js', '.js'], { dot: true }],
['@(js|.*)', ['js', '.a', '.a.js', '.js']],
['@(.*|js)', ['js', '.a', '.a.js', '.js']],
// doesn't start at 0, no dice
// neg extglobs don't trigger this behavior.
['!(.a|js)@(.*)', ['a.js'], { nonegate: true }],
() => files=['a(b', 'ab', 'a)b'],
['@(a|a[(])b', ['a(b', 'ab']],
['@(a|a[)])b', ['a)b', 'ab']],
]

Object.defineProperty(module.exports, 'files', {
Expand Down

0 comments on commit 4d0b7f5

Please sign in to comment.