Skip to content

Commit 0833310

Browse files
committed
Add dotRelative option
Fix: #495 PR-URL: #500 Credit: @isaacs Close: #500 Reviewed-by: @isaacs
1 parent 08e7348 commit 0833310

File tree

5 files changed

+103
-1
lines changed

5 files changed

+103
-1
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,16 @@ share the previously loaded cache.
253253
Only has effect on the {@link hasMagic} function, no effect on
254254
glob pattern matching itself.
255255

256+
- `dotRelative` Prepend all relative path strings with `./` (or
257+
`.\` on Windows).
258+
259+
Without this option, returned relative paths are "bare", so
260+
instead of returning `'./foo/bar'`, they are returned as
261+
`'foo/bar'`.
262+
263+
Relative patterns starting with `'../'` are not prepended with
264+
`./`, even if this option is set.
265+
256266
- `mark` Add a `/` character to directory matches. Note that this
257267
requires additional stat calls.
258268

changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
absolute.
99
- Add `magicalBraces` option to treat brace expansion as "magic"
1010
in the `hasMagic` function.
11+
- Add `dotRelative` option
1112

1213
## 9.0
1314

src/glob.ts

+13
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ export interface GlobOptions {
7575
*/
7676
dot?: boolean
7777

78+
/**
79+
* Prepend all relative path strings with `./` (or `.\` on Windows).
80+
*
81+
* Without this option, returned relative paths are "bare", so instead of
82+
* returning `'./foo/bar'`, they are returned as `'foo/bar'`.
83+
*
84+
* Relative patterns starting with `'../'` are not prepended with `./`, even
85+
* if this option is set.
86+
*/
87+
dotRelative?: boolean
88+
7889
/**
7990
* Follow symlinked directories when expanding `**`
8091
* patterns. This can result in a lot of duplicate references in
@@ -269,6 +280,7 @@ export class Glob<Opts extends GlobOptions> implements GlobOptions {
269280
cwd: string
270281
root?: string
271282
dot: boolean
283+
dotRelative: boolean
272284
follow: boolean
273285
ignore?: Ignore
274286
magicalBraces: boolean
@@ -314,6 +326,7 @@ export class Glob<Opts extends GlobOptions> implements GlobOptions {
314326
this.signal = opts.signal
315327
this.follow = !!opts.follow
316328
this.dot = !!opts.dot
329+
this.dotRelative = !!opts.dotRelative
317330
this.nodir = !!opts.nodir
318331
this.mark = !!opts.mark
319332
if (!opts.cwd) {

src/walker.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface GlobWalkerOpts {
2121
allowWindowsEscape?: boolean
2222
cwd?: string | URL
2323
dot?: boolean
24+
dotRelative?: boolean
2425
follow?: boolean
2526
ignore?: string | string[] | Ignore
2627
mark?: boolean
@@ -200,7 +201,9 @@ export abstract class GlobUtil<O extends GlobWalkerOpts = GlobWalkerOpts> {
200201
this.matchEmit(e.fullpath() + mark)
201202
} else {
202203
const rel = e.relative()
203-
this.matchEmit(!rel && mark ? '.' + mark : rel + mark)
204+
const pre = this.opts.dotRelative && !rel.startsWith('..' + this.#sep)
205+
? '.' + this.#sep : ''
206+
this.matchEmit(!rel && mark ? '.' + mark : pre + rel + mark)
204207
}
205208
}
206209

test/dot-relative.ts

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import t from 'tap'
2+
import { Glob } from '../'
3+
import { bashResults } from './bash-results'
4+
import {resolve, sep} from 'path'
5+
6+
const pattern = 'a/b/**'
7+
process.chdir(__dirname + '/fixtures')
8+
9+
const marks = [true, false]
10+
for (const mark of marks) {
11+
t.test('mark=' + mark, t => {
12+
t.plan(3)
13+
14+
t.test('Emits relative matches prefixed with ./', async t => {
15+
const g = new Glob(pattern, { dotRelative: true })
16+
const results = await g.walk()
17+
18+
t.equal(
19+
results.length,
20+
bashResults[pattern].length,
21+
'must match all files'
22+
)
23+
for (const m of results) {
24+
t.ok(m.startsWith('.' + sep))
25+
}
26+
})
27+
28+
t.test('returns ./ prefixed matches synchronously', async t => {
29+
const g = new Glob(pattern, { dotRelative: true })
30+
const results = g.walkSync()
31+
32+
t.equal(
33+
results.length,
34+
bashResults[pattern].length,
35+
'must match all files'
36+
)
37+
for (const m of results) {
38+
t.ok(m.startsWith('.' + sep))
39+
}
40+
})
41+
42+
t.test('does not prefix with ./ unless dotRelative is true', async t => {
43+
const g = new Glob(pattern, {})
44+
const results = await g.walk()
45+
46+
t.equal(
47+
results.length,
48+
bashResults[pattern].length,
49+
'must match all files'
50+
)
51+
for (const m of results) {
52+
t.ok(mark && m === '.' + sep || !m.startsWith('.' + sep))
53+
}
54+
})
55+
})
56+
}
57+
58+
t.test('does not add ./ for patterns starting in ../', async t => {
59+
t.plan(2)
60+
const pattern = '../a/b/**'
61+
const cwd = resolve(__dirname, 'fixtures/a')
62+
t.test('async', async t => {
63+
const g = new Glob(pattern, { dotRelative: true, cwd })
64+
for await (const m of g) {
65+
t.ok(!m.startsWith('.' + sep + '..' + sep))
66+
}
67+
})
68+
t.test('sync', t => {
69+
const g = new Glob(pattern, { dotRelative: true, cwd })
70+
for (const m of g) {
71+
t.ok(!m.startsWith('.' + sep + '..' + sep))
72+
}
73+
t.end()
74+
})
75+
})

0 commit comments

Comments
 (0)