Skip to content

Commit d6a2031

Browse files
breaking: svelte-check v4 (#2453)
- breaking: make TypeScript a peer dependency - breaking: require node 18 or later - breaking: require Svelte 4 or later (devDependencies pinned to Svelte 3 because other packages in this repo still use it. Theoretically we still support Svelte 3 with svelte-check v4 but this gives us the opportunity to adjust that later without a major) - chore: switch from fast-glob to fdir (#2433) closes #2397 fixes #2364 --------- Co-authored-by: Simon Holthausen <[email protected]> Co-authored-by: Ben McCann <[email protected]>
1 parent 0655c67 commit d6a2031

File tree

8 files changed

+138
-217
lines changed

8 files changed

+138
-217
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('/.') || path[0] === '.';
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() } })
@@ -98,14 +121,14 @@ describe('ConfigLoader', () => {
98121
let firstGlobCall = true;
99122
let nrImportCalls = 0;
100123
const configLoader = new ConfigLoader(
101-
(() => {
124+
mockFdir(() => {
102125
if (firstGlobCall) {
103126
firstGlobCall = false;
104127
return ['svelte.config.js'];
105128
} else {
106129
return [];
107130
}
108-
}) as any,
131+
}),
109132
{
110133
existsSync: (p) =>
111134
typeof p === 'string' &&
@@ -139,11 +162,8 @@ describe('ConfigLoader', () => {
139162
});
140163

141164
it('can deal with missing config', () => {
142-
const configLoader = new ConfigLoader(
143-
() => [],
144-
{ existsSync: () => false },
145-
path,
146-
() => Promise.resolve('unimportant')
165+
const configLoader = new ConfigLoader(mockFdir([]), { existsSync: () => false }, path, () =>
166+
Promise.resolve('unimportant')
147167
);
148168
assert.deepStrictEqual(
149169
configLoader.getConfig(normalizePath('/some/file.svelte')),
@@ -153,7 +173,7 @@ describe('ConfigLoader', () => {
153173

154174
it('should await config', async () => {
155175
const configLoader = new ConfigLoader(
156-
() => [],
176+
mockFdir([]),
157177
{ existsSync: () => true },
158178
path,
159179
(module: URL) => Promise.resolve({ default: { preprocess: module.toString() } })
@@ -167,7 +187,7 @@ describe('ConfigLoader', () => {
167187
it('should not load config when disabled', async () => {
168188
const moduleLoader = spy();
169189
const configLoader = new ConfigLoader(
170-
() => [],
190+
mockFdir([]),
171191
{ existsSync: () => true },
172192
path,
173193
moduleLoader

packages/svelte-check/package.json

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "svelte-check",
33
"description": "Svelte Code Checker Terminal Interface",
4-
"version": "3.0.0",
4+
"version": "4.0.0",
55
"main": "./dist/src/index.js",
66
"bin": "./bin/svelte-check",
77
"author": "The Svelte Community",
@@ -22,16 +22,19 @@
2222
"url": "https://github.com/sveltejs/language-tools/issues"
2323
},
2424
"homepage": "https://github.com/sveltejs/language-tools#readme",
25+
"engines": {
26+
"node": ">= 18.0.0"
27+
},
2528
"dependencies": {
2629
"@jridgewell/trace-mapping": "^0.3.17",
2730
"chokidar": "^3.4.1",
31+
"fdir": "^6.2.0",
2832
"picocolors": "^1.0.0",
29-
"sade": "^1.7.4",
30-
"svelte-preprocess": "^5.1.3",
31-
"typescript": "^5.0.3"
33+
"sade": "^1.7.4"
3234
},
3335
"peerDependencies": {
34-
"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
36+
"svelte": "^4.0.0 || ^5.0.0-next.0",
37+
"typescript": ">=5.0.0"
3538
},
3639
"scripts": {
3740
"build": "rollup -c && node ./dist/src/index.js --workspace ./test --tsconfig ./tsconfig.json",
@@ -46,11 +49,12 @@
4649
"@rollup/plugin-typescript": "^10.0.0",
4750
"@types/sade": "^1.7.2",
4851
"builtin-modules": "^3.3.0",
49-
"fast-glob": "^3.2.7",
5052
"rollup": "3.7.5",
5153
"rollup-plugin-cleanup": "^3.2.0",
5254
"rollup-plugin-copy": "^3.4.0",
55+
"svelte": "^3.57.0",
5356
"svelte-language-server": "workspace:*",
57+
"typescript": "^5.5.2",
5458
"vscode-languageserver": "8.0.2",
5559
"vscode-languageserver-protocol": "3.17.2",
5660
"vscode-languageserver-types": "3.17.2",

packages/svelte-check/rollup.config.mjs

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export default [
6565
'sade',
6666
'svelte',
6767
'svelte/compiler',
68-
'svelte-preprocess',
6968
'@jridgewell/trace-mapping'
7069
// import-fresh removed some time ago, no dependency uses it anymore.
7170
// if it creeps back in check if the dependency uses a version that

packages/svelte-check/src/index.ts

+41-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,47 @@ 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+
return path.includes('/node_modules/') || path.includes('/.');
69+
})
70+
.withPathSeparator('/')
71+
.withFullPaths()
72+
.crawl(workspaceUri.fsPath)
73+
.withPromise();
3874

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

packages/svelte-check/src/options.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,19 @@ export function parseOptions(cb: (opts: SvelteCheckCliOptions) => any) {
6767
)
6868
.action((opts) => {
6969
const workspaceUri = getWorkspaceUri(opts);
70+
const tsconfig = getTsconfig(opts, workspaceUri.fsPath);
71+
72+
if (opts.ignore && tsconfig) {
73+
throwError('`--ignore` only has an effect when using `--no-tsconfig`');
74+
}
75+
7076
cb({
7177
workspaceUri,
7278
outputFormat: getOutputFormat(opts),
7379
watch: !!opts.watch,
7480
preserveWatchOutput: !!opts.preserveWatchOutput,
75-
tsconfig: getTsconfig(opts, workspaceUri.fsPath),
76-
filePathsToIgnore: getFilepathsToIgnore(opts),
81+
tsconfig,
82+
filePathsToIgnore: opts.ignore?.split(',') || [],
7783
failOnWarnings: !!opts['fail-on-warnings'],
7884
compilerWarnings: getCompilerWarnings(opts),
7985
diagnosticSources: getDiagnosticSources(opts),
@@ -141,11 +147,15 @@ function getTsconfig(myArgs: Record<string, any>, workspacePath: string) {
141147
tsconfig = path.join(workspacePath, tsconfig);
142148
}
143149
if (tsconfig && !fs.existsSync(tsconfig)) {
144-
throw new Error('Could not find tsconfig/jsconfig file at ' + myArgs.tsconfig);
150+
throwError('Could not find tsconfig/jsconfig file at ' + myArgs.tsconfig);
145151
}
146152
return tsconfig;
147153
}
148154

155+
function throwError(msg: string) {
156+
throw new Error('Invalid svelte-check CLI args: ' + msg);
157+
}
158+
149159
function getCompilerWarnings(opts: Record<string, any>) {
150160
return stringToObj(opts['compiler-warnings']);
151161

@@ -180,10 +190,6 @@ function getDiagnosticSources(opts: Record<string, any>): DiagnosticSource[] {
180190
: diagnosticSources;
181191
}
182192

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

0 commit comments

Comments
 (0)