| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 853fcf7 commit 2a0b3d6
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,7 +7,7 @@ import { isString, slash, throttle, toArray } from '@antfu/utils' | |||
| 7 | 7 | import { isPackageExists } from 'local-pkg' | |
| 8 | 8 | import { MagicString } from 'magic-string' | |
| 9 | 9 | import pm from 'picomatch' | |
| 10 | - import { createUnimport, normalizeScanDirs, resolvePreset } from 'unimport' | ||
| 10 | + import { createUnimport, dedupeDtsExports, normalizeScanDirs, resolvePreset, scanExports, scanFilesFromDir } from 'unimport' | ||
| 11 | 11 | import { createFilter } from 'unplugin-utils' | |
| 12 | 12 | import { presets } from '../presets' | |
| 13 | 13 | import { generateBiomeLintConfigs } from './biomelintrc' | |
@@ -17,6 +17,18 @@ import { resolversAddon } from './resolvers' | |||
| 17 | 17 | export const INCLUDE_RE_LIST = [/\.[jt]sx?$/, /\.astro$/, /\.vue$/, /\.vue\?vue/, /\.vue\.[tj]sx?\?vue/, /\.svelte$/] | |
| 18 | 18 | export const EXCLUDE_RE_LIST = [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/] | |
| 19 | 19 | ||
| 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 | + | ||
| 20 | 32 | export function createContext(options: Options = {}, root = process.cwd()) { | |
| 21 | 33 | root = slash(root) | |
| 22 | 34 | ||
@@ -253,9 +265,27 @@ ${dts}`.trim()}\n` | |||
| 253 | 265 | return Promise.all(promises) | |
| 254 | 266 | } | |
| 255 | 267 | ||
| 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 | + | ||
| 256 | 277 | async function scanDirs() { | |
| 257 | 278 | 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[] | ||
| 259 | 289 | exports_.forEach(i => i.__source = 'dir') | |
| 260 | 290 | return modifyDefaultExportsAlias([ | |
| 261 | 291 | ...imports.filter((i: ImportExtended) => i.__source !== 'dir'), | |
@@ -293,15 +323,6 @@ ${dts}`.trim()}\n` | |||
| 293 | 323 | .filter(isString) | |
| 294 | 324 | .map(path => resolve(root, path)) | |
| 295 | 325 | ||
| 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 | - | ||
| 305 | 326 | return { | |
| 306 | 327 | root, | |
| 307 | 328 | dirs, | |
| Original file line number | Diff line number | Diff 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' | ||
| 3 | 6 | import { createContext } from '../src/core/ctx' | |
| 4 | 7 | ||
| 5 | 8 | const root = resolve(__dirname, '../examples/vite-react') | |
@@ -199,3 +202,58 @@ describe('dirsScanOptions', () => { | |||
| 199 | 202 | expect(data).not.toContain('TypeB') | |
| 200 | 203 | }) | |
| 201 | 204 | }) | |
| 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 | + }) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments