FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix: escape () and [] in scan directory globs · unplugin/unplugin-auto-import@2a0b3d6 · GitHub

Commit 2a0b3d6

Browse files
committed
fix: escape () and [] in scan directory globs
Project paths like Code(Template) were treated as picomatch extglob groups, so dir scanning and HMR watchers matched nothing. Change-Id: Ic62dc65d96d9754cfe08df9dcd35cd5eff989c36
1 parent 853fcf7 commit 2a0b3d6

2 files changed

Lines changed: 92 additions & 13 deletions

File tree

‎src/core/ctx.ts‎

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { isString, slash, throttle, toArray } from '@antfu/utils'
77
import { isPackageExists } from 'local-pkg'
88
import { MagicString } from 'magic-string'
99
import pm from 'picomatch'
10-
import { createUnimport, normalizeScanDirs, resolvePreset } from 'unimport'
10+
import { createUnimport, dedupeDtsExports, normalizeScanDirs, resolvePreset, scanExports, scanFilesFromDir } from 'unimport'
1111
import { createFilter } from 'unplugin-utils'
1212
import { presets } from '../presets'
1313
import { generateBiomeLintConfigs } from './biomelintrc'
@@ -17,6 +17,18 @@ import { resolversAddon } from './resolvers'
1717
export const INCLUDE_RE_LIST = [/\.[jt]sx?$/, /\.astro$/, /\.vue$/, /\.vue\?vue/, /\.vue\.[tj]sx?\?vue/, /\.svelte$/]
1818
export const EXCLUDE_RE_LIST = [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/]
1919

20+
/**
21+
* Escape `()` and `[]` in resolved filesystem paths that are later used as globs.
22+
*
23+
* picomatch / tinyglobby treat those as extglob groups and character classes, so a
24+
* project directory like `Code(Template)` otherwise matches nothing.
25+
*/
26+
export function escapeGlobPathChars(glob: string): string {
27+
const negated = glob.startsWith('!')
28+
const body = negated ? glob.slice(1) : glob
29+
return `${negated ? '!' : ''}${body.replace(/[()[\]]/g, '\\$&')}`
30+
}
31+
2032
export function createContext(options: Options = {}, root = process.cwd()) {
2133
root = slash(root)
2234

@@ -253,9 +265,27 @@ ${dts}`.trim()}\n`
253265
return Promise.all(promises)
254266
}
255267

268+
const normalizedDirPaths = dirs?.length
269+
? dirs.flatMap(dir => normalizeScanDirs([dir], {
270+
...dirsScanOptions,
271+
cwd: root,
272+
})).map(dir => ({ ...dir, glob: escapeGlobPathChars(dir.glob) }))
273+
: []
274+
275+
const normalizedDirMatchers = normalizedDirPaths.map(dir => pm(dir.glob))
276+
256277
async function scanDirs() {
257278
await unimport.modifyDynamicImports(async (imports) => {
258-
const exports_ = await unimport.scanImportsFromDir() as ImportExtended[]
279+
const files = await scanFilesFromDir(normalizedDirPaths, {
280+
...dirsScanOptions,
281+
cwd: root,
282+
})
283+
const includeTypesDirs = normalizedDirPaths.filter(dir => !dir.glob.startsWith('!') && dir.types)
284+
const includeTypesMatchers = includeTypesDirs.map(dir => pm(dir.glob))
285+
const isIncludeTypes = (file: string) => includeTypesMatchers.some(match => match(file))
286+
const exports_ = dedupeDtsExports(
287+
(await Promise.all(files.map(file => scanExports(file, isIncludeTypes(file))))).flat(),
288+
) as ImportExtended[]
259289
exports_.forEach(i => i.__source = 'dir')
260290
return modifyDefaultExportsAlias([
261291
...imports.filter((i: ImportExtended) => i.__source !== 'dir'),
@@ -293,15 +323,6 @@ ${dts}`.trim()}\n`
293323
.filter(isString)
294324
.map(path => resolve(root, path))
295325

296-
const normalizedDirPaths = dirs?.length
297-
? dirs.flatMap(dir => normalizeScanDirs([dir], {
298-
...dirsScanOptions,
299-
cwd: root,
300-
}))
301-
: []
302-
303-
const normalizedDirMatchers = normalizedDirPaths.map(dir => pm(dir.glob))
304-
305326
return {
306327
root,
307328
dirs,

‎test/search.test.ts‎

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { resolve } from 'node:path'
2-
import { describe, expect, it } from 'vitest'
1+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
2+
import { tmpdir } from 'node:os'
3+
import { join, resolve } from 'node:path'
4+
import { slash } from '@antfu/utils'
5+
import { afterEach, describe, expect, it } from 'vitest'
36
import { createContext } from '../src/core/ctx'
47

58
const root = resolve(__dirname, '../examples/vite-react')
@@ -199,3 +202,58 @@ describe('dirsScanOptions', () => {
199202
expect(data).not.toContain('TypeB')
200203
})
201204
})
205+
206+
describe('scan dirs with special characters in the project path', () => {
207+
const temps: string[] = []
208+
209+
afterEach(async () => {
210+
await Promise.all(temps.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
211+
})
212+
213+
async function setupProject(folderName: string) {
214+
const parent = await mkdtemp(join(tmpdir(), 'uai-special-'))
215+
temps.push(parent)
216+
const root = join(parent, folderName)
217+
await mkdir(join(root, 'src', 'composables'), { recursive: true })
218+
await writeFile(
219+
join(root, 'src', 'composables', 'useSpecial.ts'),
220+
'export const useSpecial = () => 1\n',
221+
)
222+
return root
223+
}
224+
225+
it('should scan dirs when the project path contains parentheses', async () => {
226+
const root = await setupProject('Code(Template)')
227+
const ctx = createContext({
228+
dts: false,
229+
dirs: ['src/composables'],
230+
}, root)
231+
232+
await ctx.scanDirs()
233+
const data = await ctx.generateDTS('')
234+
expect(data).toContain('useSpecial')
235+
})
236+
237+
it('should match watched files when the project path contains parentheses', async () => {
238+
const root = await setupProject('Code(Template)')
239+
const ctx = createContext({
240+
dts: false,
241+
dirs: ['src/composables'],
242+
}, root)
243+
const file = slash(join(root, 'src', 'composables', 'useSpecial.ts'))
244+
245+
expect(ctx.normalizedDirMatchers.some(match => match(file))).toBe(true)
246+
})
247+
248+
it('should scan dirs when the project path contains brackets', async () => {
249+
const root = await setupProject('Code[Template]')
250+
const ctx = createContext({
251+
dts: false,
252+
dirs: ['src/composables'],
253+
}, root)
254+
255+
await ctx.scanDirs()
256+
const data = await ctx.generateDTS('')
257+
expect(data).toContain('useSpecial')
258+
})
259+
})

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL