Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: isaacs/minimatch
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v9.0.1
Choose a base ref
...
head repository: isaacs/minimatch
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v9.0.2
Choose a head ref
  • 2 commits
  • 8 files changed
  • 1 contributor

Commits on Jun 23, 2023

  1. Prevent dots only at the start of repeat patterns

    A pattern like `+(?)` will note that the `?` matches the start of the
    pattern, and prevent it from matching a dot. However, this is not
    "anything other than a dot, repeating", but rather "repeating, where the
    first repetition doesn't start with a dot".
    
    With this change, repetitive extglob patterns in the start position are
    expanded such that the first instance of the pattern may not start with
    a dot, but any subsequent repetitions may begin with a dot.
    
    Fix: #211
    isaacs committed Jun 23, 2023
    Copy the full SHA
    37f6df6 View commit details
  2. 9.0.2

    isaacs committed Jun 23, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    b7bd6d6 View commit details
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
"name": "minimatch",
"description": "a glob matcher in javascript",
"version": "9.0.1",
"version": "9.0.2",
"repository": {
"type": "git",
"url": "git://github.com/isaacs/minimatch.git"
84 changes: 55 additions & 29 deletions src/ast.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// parse a single path portion

import { MinimatchOptions, MMRegExp } from './index.js'
import { parseClass } from './brace-expressions.js'
import { MinimatchOptions, MMRegExp } from './index.js'
import { unescape } from './unescape.js'

// classes [] are handled by the parseClass method
@@ -50,7 +50,7 @@ const isExtglobType = (c: string): c is ExtglobType =>
// entire string, or just a single path portion, to prevent dots
// and/or traversal patterns, when needed.
// Exts don't need the ^ or / bit, because the root binds that already.
const startNoTraversal = '(?!\\.\\.?(?:$|/))'
const startNoTraversal = '(?!(?:^|/)\\.\\.?(?:$|/))'
const startNoDot = '(?!\\.)'

// characters that indicate a start of pattern needs the "no dots" bit,
@@ -467,12 +467,10 @@ export class AST {
// - Since the start for a join is eg /(?!\.) and the start for a part
// is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
// or start or whatever) and prepend ^ or / at the Regexp construction.
toRegExpSource(): [
re: string,
body: string,
hasMagic: boolean,
uflag: boolean
] {
toRegExpSource(
allowDot?: boolean
): [re: string, body: string, hasMagic: boolean, uflag: boolean] {
const dot = allowDot ?? !!this.#options.dot
if (this.#root === this) this.#fillNegs()
if (!this.type) {
const noEmpty = this.isStart() && this.isEnd()
@@ -481,7 +479,7 @@ export class AST {
const [re, _, hasMagic, uflag] =
typeof p === 'string'
? AST.#parseGlob(p, this.#hasMagic, noEmpty)
: p.toRegExpSource()
: p.toRegExpSource(allowDot)
this.#hasMagic = this.#hasMagic || hasMagic
this.#uflag = this.#uflag || uflag
return re
@@ -504,14 +502,14 @@ export class AST {
// and prevent that.
const needNoTrav =
// dots are allowed, and the pattern starts with [ or .
(this.#options.dot && aps.has(src.charAt(0))) ||
(dot && aps.has(src.charAt(0))) ||
// the pattern starts with \., and then [ or .
(src.startsWith('\\.') && aps.has(src.charAt(2))) ||
// the pattern starts with \.\., and then [ or .
(src.startsWith('\\.\\.') && aps.has(src.charAt(4)))
// no need to prevent dots if it can't match a dot, or if a
// sub-pattern will be preventing it anyway.
const needNoDot = !this.#options.dot && aps.has(src.charAt(0))
const needNoDot = !dot && !allowDot && aps.has(src.charAt(0))

start = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : ''
}
@@ -536,23 +534,15 @@ export class AST {
]
}

// We need to calculate the body *twice* if it's a repeat pattern
// at the start, once in nodot mode, then again in dot mode, so a
// pattern like *(?) can match 'x.y'

const repeated = this.type === '*' || this.type === '+'
// some kind of extglob
const start = this.type === '!' ? '(?:(?!(?:' : '(?:'
const body = this.#parts
.map(p => {
// extglob ASTs should only contain parent ASTs
/* c8 ignore start */
if (typeof p === 'string') {
throw new Error('string type in extglob ast??')
}
/* c8 ignore stop */
// can ignore hasMagic, because extglobs are already always magic
const [re, _, _hasMagic, uflag] = p.toRegExpSource()
this.#uflag = this.#uflag || uflag
return re
})
.filter(p => !(this.isStart() && this.isEnd()) || !!p)
.join('|')
let body = this.#partsToRegExp(dot)

if (this.isStart() && this.isEnd() && !body && this.type !== '!') {
// invalid extglob, has to at least be *something* present, if it's
// the entire path portion.
@@ -562,21 +552,39 @@ export class AST {
this.#hasMagic = undefined
return [s, unescape(this.toString()), false, false]
}

// XXX abstract out this map method
let bodyDotAllowed =
!repeated || allowDot || dot || !startNoDot
? ''
: this.#partsToRegExp(true)
if (bodyDotAllowed === body) {
bodyDotAllowed = ''
}
if (bodyDotAllowed) {
body = `(?:${body})(?:${bodyDotAllowed})*?`
}

// an empty !() is exactly equivalent to a starNoEmpty
let final = ''
if (this.type === '!' && this.#emptyExt) {
final =
(this.isStart() && !this.#options.dot ? startNoDot : '') + starNoEmpty
final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty
} else {
const close =
this.type === '!'
? // !() must match something,but !(x) can match ''
'))' +
(this.isStart() && !this.#options.dot ? startNoDot : '') +
(this.isStart() && !dot && !allowDot ? startNoDot : '') +
star +
')'
: this.type === '@'
? ')'
: this.type === '?'
? ')?'
: this.type === '+' && bodyDotAllowed
? ')'
: this.type === '*' && bodyDotAllowed
? `)?`
: `)${this.type}`
final = start + body + close
}
@@ -588,6 +596,24 @@ export class AST {
]
}

#partsToRegExp(dot: boolean) {
return this.#parts
.map(p => {
// extglob ASTs should only contain parent ASTs
/* c8 ignore start */
if (typeof p === 'string') {
throw new Error('string type in extglob ast??')
}
/* c8 ignore stop */
// can ignore hasMagic, because extglobs are already always magic
const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot)
this.#uflag = this.#uflag || uflag
return re
})
.filter(p => !(this.isStart() && this.isEnd()) || !!p)
.join('|')
}

static #parseGlob(
glob: string,
hasMagic: boolean | undefined,
Loading