Skip to content

Commit

Permalink
add windowsPathsNoEscape option
Browse files Browse the repository at this point in the history
Fix: #468

PR-URL: #470
Credit: @isaacs
Close: #470
Reviewed-by: @isaacs
  • Loading branch information
isaacs committed Jan 14, 2023
1 parent af57da2 commit 1756fcc
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 4 deletions.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,26 @@ parallel glob operations will be sped up by sharing information about
the filesystem.

* `cwd` The current working directory in which to search. Defaults
to `process.cwd()`.
to `process.cwd()`. This option is always coerced to use
forward-slashes as a path separator, because it is not tested
as a glob pattern, so there is no need to escape anything.
* `root` The place where patterns starting with `/` will be mounted
onto. Defaults to `path.resolve(options.cwd, "/")` (`/` on Unix
systems, and `C:\` or some such on Windows.)
systems, and `C:\` or some such on Windows.) This option is
always coerced to use forward-slashes as a path separator,
because it is not tested as a glob pattern, so there is no need
to escape anything.
* `windowsPathsNoEscape` Use `\\` as a path separator _only_, and
_never_ as an escape character. If set, all `\\` characters
are replaced with `/` in the pattern. Note that this makes it
**impossible** to match against paths containing literal glob
pattern characters, but allows matching with patterns constructed
using `path.join()` and `path.resolve()` on Windows platforms,
mimicking the (buggy!) behavior of Glob v7 and before on
Windows. Please use with caution, and be mindful of [the caveat
below about Windows paths](#windows). (For legacy reasons,
this is also set if `allowWindowsEscape` is set to the exact
value `false`.)
* `dot` Include `.dot` files in normal matches and `globstar` matches.
Note that an explicit dot in a portion of the pattern will always
match dot files.
Expand Down Expand Up @@ -332,6 +348,11 @@ Results from absolute patterns such as `/foo/*` are mounted onto the
root setting using `path.join`. On windows, this will by default result
in `/foo/*` matching `C:\foo\bar.txt`.

To automatically coerce all `\` characters to `/` in pattern
strings, **thus making it impossible to escape literal glob
characters**, you may set the `windowsPathsNoEscape` option to
`true`.

## Race Conditions

Glob searching, by its very nature, is susceptible to race conditions,
Expand Down
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 8.1

- Add `windowsPathsNoEscape` option

## 8.0

- Only support node v12 and higher
Expand Down
8 changes: 6 additions & 2 deletions common.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ function setopts (self, pattern, options) {
pattern = "**/" + pattern
}

self.windowsPathsNoEscape = !!options.windowsPathsNoEscape ||
options.allowWindowsEscape === false
if (self.windowsPathsNoEscape) {
pattern = pattern.replace(/\\/g, '/')
}

self.silent = !!options.silent
self.pattern = pattern
self.strict = options.strict !== false
Expand Down Expand Up @@ -112,8 +118,6 @@ function setopts (self, pattern, options) {
// Note that they are not supported in Glob itself anyway.
options.nonegate = true
options.nocomment = true
// always treat \ in patterns as escapes, not path separators
options.allowWindowsEscape = true

self.minimatch = new Minimatch(pattern, options)
self.options = self.minimatch.options
Expand Down
70 changes: 70 additions & 0 deletions test/windows-paths-fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// test that escape chars are handled properly according to configs
// when found in patterns and paths containing glob magic.

const t = require('tap')
const dir = t.testdir({
// treat escapes as path separators
a: {
'[x': {
']b': {
y: '',
},
},
},
// escape parent dir name only, not filename
'a[x]b': {
y: '',
},
// no path separators, all escaped
'a[x]by': '',
})

const glob = require('../')
t.test('treat backslash as escape', async t => {
const cases = {
'a[x]b/y': [],
'a\\[x\\]b/y': ['a[x]b/y'],
'a\\[x\\]b\\y': ['a[x]by'],
}
for (const [pattern, expect] of Object.entries(cases)) {
t.test(pattern, t => {
const s = glob.sync(pattern, { cwd: dir })
.map(s => s.replace(/\\/g, '/'))
t.strictSame(s, expect, 'sync')
glob(pattern, {cwd: dir}, (er, s) => {
if (er) {
throw er
}
s = s.map(s => s.replace(/\\/g, '/'))
t.strictSame(s, expect, 'async')
t.end()
})
})
}
})

t.test('treat backslash as separator', async t => {
Object.defineProperty(process, 'platform', {
value: 'win32'
})
const cases = {
'a[x]b/y': [],
'a\\[x\\]b/y': ['a/[x/]b/y'],
'a\\[x\\]b\\y': ['a/[x/]b/y'],
}
for (const [pattern, expect] of Object.entries(cases)) {
t.test(pattern, t => {
const s = glob.sync(pattern, { cwd: dir, windowsPathsNoEscape: true })
.map(s => s.replace(/\\/g, '/'))
t.strictSame(s, expect, 'sync')
glob(pattern, {cwd: dir, windowsPathsNoEscape: true}, (er, s) => {
if (er) {
throw er
}
s = s.map(s => s.replace(/\\/g, '/'))
t.strictSame(s, expect, 'async')
t.end()
})
})
}
})
45 changes: 45 additions & 0 deletions test/windows-paths-no-escape.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const t = require('tap')
const g = require('../')

const platforms = ['win32', 'posix']
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
for (const p of platforms) {
t.test(p, t => {
Object.defineProperty(process, 'platform', {
value: p,
enumerable: true,
configurable: true,
writable: true,
})
t.equal(process.platform, p, 'gut check: actually set platform')
const pattern = '/a/b/c/x\\[a-b\\]y\\*'
const def = new g.Glob(pattern, { noprocess: true })
const winpath = new g.Glob(pattern, {
windowsPathsNoEscape: true,
noprocess: true,
})
const winpathLegacy = new g.Glob(pattern, {
allowWindowsEscape: false,
noprocess: true,
})
const nowinpath = new g.Glob(pattern, {
windowsPathsNoEscape: false,
noprocess: true,
})

t.strictSame([
def.pattern,
nowinpath.pattern,
winpath.pattern,
winpathLegacy.pattern,
], [
'/a/b/c/x\\[a-b\\]y\\*',
'/a/b/c/x\\[a-b\\]y\\*',
'/a/b/c/x/[a-b/]y/*',
'/a/b/c/x/[a-b/]y/*',
])
t.end()
})
}

Object.defineProperty(process, 'platform', originalPlatform)

0 comments on commit 1756fcc

Please sign in to comment.