Skip to content

Commit 3510a29

Browse files
committed
feat: detect version information from node_modules when not specified in package.json, like pnpm’s catalog
1 parent 20a2f32 commit 3510a29

File tree

3 files changed

+153
-31
lines changed

3 files changed

+153
-31
lines changed

.changeset/two-poets-wait.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': minor
3+
---
4+
5+
feat: detect version information from node_modules when not specified in package.json, like pnpm’s catalog
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import { createCache } from './cache.js';
4+
5+
const isRunOnBrowser = !fs.readFileSync;
6+
const nodeModuleCache = createCache<string | null>();
7+
const nodeModulesCache = createCache<string[]>();
8+
9+
/**
10+
* Find package directory in node_modules
11+
*/
12+
function findPackageInNodeModules(dir: string, packageName: string): string | null {
13+
if (isRunOnBrowser) return null;
14+
15+
const nodeModulesPath = path.join(dir, 'node_modules');
16+
const packagePath = path.join(nodeModulesPath, packageName);
17+
18+
try {
19+
const stats = fs.statSync(packagePath);
20+
if (stats.isDirectory()) {
21+
return packagePath;
22+
}
23+
} catch {
24+
// ignore if directory not found
25+
}
26+
27+
return null;
28+
}
29+
30+
/**
31+
* Get first found package path from node_modules
32+
*/
33+
export function getNodeModule(packageName: string, startPath = 'a.js'): string | null {
34+
if (isRunOnBrowser) return null;
35+
36+
const cacheKey = `${startPath}:${packageName}`;
37+
const cached = nodeModulesCache.get(cacheKey);
38+
if (cached) {
39+
return cached[0] ?? null;
40+
}
41+
42+
const startDir = path.dirname(path.resolve(startPath));
43+
let dir = startDir;
44+
let prevDir = '';
45+
46+
do {
47+
// check cache
48+
const cachePath = nodeModuleCache.get(`${dir}:${packageName}`);
49+
if (cachePath) {
50+
if (cachePath !== null) {
51+
nodeModulesCache.set(cacheKey, [cachePath]);
52+
return cachePath;
53+
}
54+
} else {
55+
// search new
56+
const packagePath = findPackageInNodeModules(dir, packageName);
57+
nodeModuleCache.set(`${dir}:${packageName}`, packagePath);
58+
if (packagePath) {
59+
nodeModulesCache.set(cacheKey, [packagePath]);
60+
return packagePath;
61+
}
62+
}
63+
64+
// go to parent
65+
prevDir = dir;
66+
dir = path.resolve(dir, '..');
67+
} while (dir !== prevDir);
68+
69+
nodeModulesCache.set(cacheKey, []);
70+
return null;
71+
}

packages/eslint-plugin-svelte/src/utils/svelte-context.ts

+77-31
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { RuleContext } from '../types.js';
22
import fs from 'fs';
33
import path from 'path';
44
import { getPackageJsons } from './get-package-json.js';
5+
import { getNodeModule } from './get-node-module.js';
56
import { getFilename, getSourceCode } from './compat.js';
67
import { createCache } from './cache.js';
78

@@ -170,6 +171,23 @@ function getSvelteKitContext(
170171

171172
const svelteVersionCache = createCache<SvelteContext['svelteVersion']>();
172173

174+
function checkAndSetSvelteVersion(
175+
version: string,
176+
filePath: string
177+
): SvelteContext['svelteVersion'] | null {
178+
const major = extractMajorVersion(version, false);
179+
if (major == null) {
180+
svelteVersionCache.set(filePath, null);
181+
return null;
182+
}
183+
if (major === '3' || major === '4') {
184+
svelteVersionCache.set(filePath, '3/4');
185+
return '3/4';
186+
}
187+
svelteVersionCache.set(filePath, major as SvelteContext['svelteVersion']);
188+
return major as SvelteContext['svelteVersion'];
189+
}
190+
173191
export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion'] {
174192
const cached = svelteVersionCache.get(filePath);
175193
if (cached) return cached;
@@ -187,32 +205,45 @@ export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion
187205
if (typeof version !== 'string') {
188206
continue;
189207
}
190-
const major = extractMajorVersion(version, false);
191-
if (major === '3' || major === '4') {
192-
svelteVersionCache.set(filePath, '3/4');
193-
return '3/4';
208+
const result = checkAndSetSvelteVersion(version, filePath);
209+
if (result != null) {
210+
return result;
194211
}
195-
svelteVersionCache.set(filePath, major as SvelteContext['svelteVersion']);
196-
return major as SvelteContext['svelteVersion'];
197212
}
198213
} catch {
199214
/** do nothing */
200215
}
201216

217+
const nodeModule = getNodeModule('svelte', filePath);
218+
if (nodeModule) {
219+
try {
220+
const packageJson = JSON.parse(
221+
fs.readFileSync(path.join(nodeModule, 'package.json'), 'utf8')
222+
);
223+
const result = checkAndSetSvelteVersion(packageJson.version, filePath);
224+
if (result != null) {
225+
return result;
226+
}
227+
} catch {
228+
/** do nothing */
229+
}
230+
}
231+
202232
svelteVersionCache.set(filePath, null);
203233
return null;
204234
}
205235

206236
const svelteKitVersionCache = createCache<SvelteContext['svelteKitVersion']>();
207237

208-
/**
209-
* Check givin file is under SvelteKit project.
210-
*
211-
* If it runs on browser, it always returns true.
212-
*
213-
* @param filePath A file path.
214-
* @returns
215-
*/
238+
function checkAndSetSvelteKitVersion(
239+
version: string,
240+
filePath: string
241+
): SvelteContext['svelteKitVersion'] {
242+
const major = extractMajorVersion(version, true) as SvelteContext['svelteKitVersion'];
243+
svelteKitVersionCache.set(filePath, major);
244+
return major;
245+
}
246+
216247
function getSvelteKitVersion(filePath: string): SvelteContext['svelteKitVersion'] {
217248
const cached = svelteKitVersionCache.get(filePath);
218249
if (cached) return cached;
@@ -225,30 +256,45 @@ function getSvelteKitVersion(filePath: string): SvelteContext['svelteKitVersion'
225256

226257
try {
227258
const packageJsons = getPackageJsons(filePath);
228-
if (packageJsons.length === 0) return null;
229-
if (packageJsons[0].name === 'eslint-plugin-svelte') {
230-
// Hack: CI removes `@sveltejs/kit` and it returns false and test failed.
231-
// So always it returns 2 if it runs on the package.
232-
svelteKitVersionCache.set(filePath, '2');
233-
return '2';
234-
}
259+
if (packageJsons.length > 0) {
260+
if (packageJsons[0].name === 'eslint-plugin-svelte') {
261+
// Hack: CI removes `@sveltejs/kit` and it returns false and test failed.
262+
// So always it returns 2 if it runs on the package.
263+
svelteKitVersionCache.set(filePath, '2');
264+
return '2';
265+
}
235266

236-
for (const packageJson of packageJsons) {
237-
const version =
238-
packageJson.dependencies?.['@sveltejs/kit'] ??
239-
packageJson.devDependencies?.['@sveltejs/kit'];
240-
if (typeof version !== 'string') {
241-
svelteKitVersionCache.set(filePath, null);
242-
return null;
267+
for (const packageJson of packageJsons) {
268+
const version =
269+
packageJson.dependencies?.['@sveltejs/kit'] ??
270+
packageJson.devDependencies?.['@sveltejs/kit'];
271+
if (typeof version === 'string') {
272+
const result = checkAndSetSvelteKitVersion(version, filePath);
273+
if (result != null) {
274+
return result;
275+
}
276+
}
243277
}
244-
const major = extractMajorVersion(version, true) as SvelteContext['svelteKitVersion'];
245-
svelteKitVersionCache.set(filePath, major);
246-
return major;
247278
}
248279
} catch {
249280
/** do nothing */
250281
}
251282

283+
const nodeModule = getNodeModule('@sveltejs/kit', filePath);
284+
if (nodeModule) {
285+
try {
286+
const packageJson = JSON.parse(
287+
fs.readFileSync(path.join(nodeModule, 'package.json'), 'utf8')
288+
);
289+
const result = checkAndSetSvelteKitVersion(packageJson.version, filePath);
290+
if (result != null) {
291+
return result;
292+
}
293+
} catch {
294+
/** do nothing */
295+
}
296+
}
297+
252298
svelteKitVersionCache.set(filePath, null);
253299
return null;
254300
}

0 commit comments

Comments
 (0)