Skip to content

Commit ea8c0bd

Browse files
chore: switch from fast-glob to fdir (#2433)
closes #2397 fixes #2364 --------- Co-authored-by: Simon Holthausen <[email protected]>
1 parent 5aca29f commit ea8c0bd

File tree

7 files changed

+111
-39
lines changed

7 files changed

+111
-39
lines changed

packages/language-server/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@vscode/emmet-helper": "2.8.4",
5151
"chokidar": "^3.4.1",
5252
"estree-walker": "^2.0.1",
53-
"fast-glob": "^3.2.7",
53+
"fdir": "^6.2.0",
5454
"lodash": "^4.17.21",
5555
"prettier": "~3.2.5",
5656
"prettier-plugin-svelte": "^3.2.2",

packages/language-server/src/lib/documents/configLoader.ts

+18-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CompileOptions } from 'svelte/types/compiler/interfaces';
44
// @ts-ignore
55
import { PreprocessorGroup } from 'svelte/types/compiler/preprocess';
66
import { importSveltePreprocess } from '../../importPackage';
7-
import _glob from 'fast-glob';
7+
import { fdir } from 'fdir';
88
import _path from 'path';
99
import _fs from 'fs';
1010
import { pathToFileURL, URL } from 'url';
@@ -47,6 +47,8 @@ const _dynamicImport = new Function('modulePath', 'return import(modulePath)') a
4747
modulePath: URL
4848
) => Promise<any>;
4949

50+
const configRegex = /\/svelte\.config\.(js|cjs|mjs)$/;
51+
5052
/**
5153
* Loads svelte.config.{js,cjs,mjs} files. Provides both a synchronous and asynchronous
5254
* interface to get a config file because snapshots need access to it synchronously.
@@ -61,7 +63,7 @@ export class ConfigLoader {
6163
private disabled = false;
6264

6365
constructor(
64-
private globSync: typeof _glob.sync,
66+
private globSync: typeof fdir,
6567
private fs: Pick<typeof _fs, 'existsSync'>,
6668
private path: Pick<typeof _path, 'dirname' | 'relative' | 'join'>,
6769
private dynamicImport: typeof _dynamicImport
@@ -84,12 +86,19 @@ export class ConfigLoader {
8486
Logger.log('Trying to load configs for', directory);
8587

8688
try {
87-
const pathResults = this.globSync('**/svelte.config.{js,cjs,mjs}', {
88-
cwd: directory,
89-
// the second pattern is necessary because else fast-glob treats .tmp/../node_modules/.. as a valid match for some reason
90-
ignore: ['**/node_modules/**', '**/.*/**'],
91-
onlyFiles: true
92-
});
89+
const pathResults = new this.globSync({})
90+
.withPathSeparator('/')
91+
.exclude((_, path) => {
92+
// no / at the start, path could start with node_modules
93+
return path.includes('node_modules/') || path.includes('/.');
94+
})
95+
.filter((path, isDir) => {
96+
return !isDir && configRegex.test(path);
97+
})
98+
.withRelativePaths()
99+
.crawl(directory)
100+
.sync();
101+
93102
const someConfigIsImmediateFileInDirectory =
94103
pathResults.length > 0 && pathResults.some((res) => !this.path.dirname(res));
95104
if (!someConfigIsImmediateFileInDirectory) {
@@ -296,4 +305,4 @@ export class ConfigLoader {
296305
}
297306
}
298307

299-
export const configLoader = new ConfigLoader(_glob.sync, _fs, _path, _dynamicImport);
308+
export const configLoader = new ConfigLoader(fdir, _fs, _path, _dynamicImport);

packages/language-server/test/lib/documents/configLoader.test.ts

+32-12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@ describe('ConfigLoader', () => {
1919
return path.join(...filePath.split('/'));
2020
}
2121

22+
function mockFdir(results: string[] | (() => string[])): any {
23+
return class {
24+
withPathSeparator() {
25+
return this;
26+
}
27+
exclude() {
28+
return this;
29+
}
30+
filter() {
31+
return this;
32+
}
33+
withRelativePaths() {
34+
return this;
35+
}
36+
crawl() {
37+
return this;
38+
}
39+
sync() {
40+
return typeof results === 'function' ? results() : results;
41+
}
42+
};
43+
}
44+
2245
async function assertFindsConfig(
2346
configLoader: ConfigLoader,
2447
filePath: string,
@@ -32,7 +55,7 @@ describe('ConfigLoader', () => {
3255

3356
it('should load all config files below and the one inside/above given directory', async () => {
3457
const configLoader = new ConfigLoader(
35-
(() => ['svelte.config.js', 'below/svelte.config.js']) as any,
58+
mockFdir(['svelte.config.js', 'below/svelte.config.js']),
3659
{ existsSync: () => true },
3760
path,
3861
(module: URL) => Promise.resolve({ default: { preprocess: module.toString() } })
@@ -63,7 +86,7 @@ describe('ConfigLoader', () => {
6386

6487
it('finds first above if none found inside/below directory', async () => {
6588
const configLoader = new ConfigLoader(
66-
() => [],
89+
mockFdir([]),
6790
{
6891
existsSync: (p) =>
6992
typeof p === 'string' && p.endsWith(path.join('some', 'svelte.config.js'))
@@ -78,7 +101,7 @@ describe('ConfigLoader', () => {
78101

79102
it('adds fallback if no config found', async () => {
80103
const configLoader = new ConfigLoader(
81-
() => [],
104+
mockFdir([]),
82105
{ existsSync: () => false },
83106
path,
84107
(module: URL) => Promise.resolve({ default: { preprocess: module.toString() } })
@@ -99,14 +122,14 @@ describe('ConfigLoader', () => {
99122
let firstGlobCall = true;
100123
let nrImportCalls = 0;
101124
const configLoader = new ConfigLoader(
102-
(() => {
125+
mockFdir(() => {
103126
if (firstGlobCall) {
104127
firstGlobCall = false;
105128
return ['svelte.config.js'];
106129
} else {
107130
return [];
108131
}
109-
}) as any,
132+
}),
110133
{
111134
existsSync: (p) =>
112135
typeof p === 'string' &&
@@ -140,11 +163,8 @@ describe('ConfigLoader', () => {
140163
});
141164

142165
it('can deal with missing config', () => {
143-
const configLoader = new ConfigLoader(
144-
() => [],
145-
{ existsSync: () => false },
146-
path,
147-
() => Promise.resolve('unimportant')
166+
const configLoader = new ConfigLoader(mockFdir([]), { existsSync: () => false }, path, () =>
167+
Promise.resolve('unimportant')
148168
);
149169
assert.deepStrictEqual(
150170
configLoader.getConfig(normalizePath('/some/file.svelte')),
@@ -154,7 +174,7 @@ describe('ConfigLoader', () => {
154174

155175
it('should await config', async () => {
156176
const configLoader = new ConfigLoader(
157-
() => [],
177+
mockFdir([]),
158178
{ existsSync: () => true },
159179
path,
160180
(module: URL) => Promise.resolve({ default: { preprocess: module.toString() } })
@@ -168,7 +188,7 @@ describe('ConfigLoader', () => {
168188
it('should not load config when disabled', async () => {
169189
const moduleLoader = spy();
170190
const configLoader = new ConfigLoader(
171-
() => [],
191+
mockFdir([]),
172192
{ existsSync: () => true },
173193
path,
174194
moduleLoader

packages/svelte-check/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"dependencies": {
2929
"@jridgewell/trace-mapping": "^0.3.17",
3030
"chokidar": "^3.4.1",
31+
"fdir": "^6.2.0",
3132
"picocolors": "^1.0.0",
3233
"sade": "^1.7.4",
3334
"svelte-preprocess": "^5.1.3"
@@ -49,7 +50,6 @@
4950
"@rollup/plugin-typescript": "^10.0.0",
5051
"@types/sade": "^1.7.2",
5152
"builtin-modules": "^3.3.0",
52-
"fast-glob": "^3.2.7",
5353
"rollup": "3.7.5",
5454
"rollup-plugin-cleanup": "^3.2.0",
5555
"rollup-plugin-copy": "^3.4.0",

packages/svelte-check/src/index.ts

+42-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { watch } from 'chokidar';
66
import * as fs from 'fs';
7-
import glob from 'fast-glob';
7+
import { fdir } from 'fdir';
88
import * as path from 'path';
99
import { SvelteCheck, SvelteCheckOptions } from 'svelte-language-server';
1010
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol';
@@ -30,11 +30,48 @@ async function openAllDocuments(
3030
filePathsToIgnore: string[],
3131
svelteCheck: SvelteCheck
3232
) {
33-
const files = await glob('**/*.svelte', {
34-
cwd: workspaceUri.fsPath,
35-
ignore: ['node_modules/**'].concat(filePathsToIgnore.map((ignore) => `${ignore}/**`))
33+
const offset = workspaceUri.fsPath.length + 1;
34+
// We support a very limited subset of glob patterns: You can only have ** at the end or the start
35+
const ignored: Array<(path: string) => boolean> = filePathsToIgnore.map((i) => {
36+
if (i.endsWith('**')) i = i.slice(0, -2);
37+
38+
if (i.startsWith('**')) {
39+
i = i.slice(2);
40+
41+
if (i.includes('*'))
42+
throw new Error(
43+
'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported'
44+
);
45+
46+
return (path) => path.includes(i);
47+
}
48+
49+
if (i.includes('*'))
50+
throw new Error(
51+
'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported'
52+
);
53+
54+
return (path) => path.startsWith(i);
3655
});
37-
const absFilePaths = files.map((f) => path.resolve(workspaceUri.fsPath, f));
56+
const isIgnored = (path: string) => {
57+
path = path.slice(offset);
58+
for (const i of ignored) {
59+
if (i(path)) {
60+
return true;
61+
}
62+
}
63+
return false;
64+
};
65+
const absFilePaths = await new fdir()
66+
.filter((path) => path.endsWith('.svelte') && !isIgnored(path))
67+
.exclude((_, path) => {
68+
path = path.slice(offset);
69+
return path.startsWith('.') || path.startsWith('node_modules');
70+
})
71+
.withPathSeparator('/')
72+
.withFullPaths()
73+
.crawl(workspaceUri.fsPath)
74+
.withPromise();
3875

3976
for (const absFilePath of absFilePaths) {
4077
const text = fs.readFileSync(absFilePath, 'utf-8');

packages/svelte-check/src/options.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function parseOptions(cb: (opts: SvelteCheckCliOptions) => any) {
7373
watch: !!opts.watch,
7474
preserveWatchOutput: !!opts.preserveWatchOutput,
7575
tsconfig: getTsconfig(opts, workspaceUri.fsPath),
76-
filePathsToIgnore: getFilepathsToIgnore(opts),
76+
filePathsToIgnore: opts.ignore?.split(',') || [],
7777
failOnWarnings: !!opts['fail-on-warnings'],
7878
compilerWarnings: getCompilerWarnings(opts),
7979
diagnosticSources: getDiagnosticSources(opts),
@@ -180,10 +180,6 @@ function getDiagnosticSources(opts: Record<string, any>): DiagnosticSource[] {
180180
: diagnosticSources;
181181
}
182182

183-
function getFilepathsToIgnore(opts: Record<string, any>): string[] {
184-
return opts.ignore?.split(',') || [];
185-
}
186-
187183
const thresholds = ['warning', 'error'] as const;
188184
type Threshold = (typeof thresholds)[number];
189185

pnpm-lock.yaml

+16-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)