Skip to content

Commit d39c90c

Browse files
authored
feat: detect version information from node_modules for pnpm’s catalog (#1063)
1 parent f0f892b commit d39c90c

File tree

4 files changed

+133
-55
lines changed

4 files changed

+133
-55
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

packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { TSESTree } from '@typescript-eslint/types';
33
import { createRule } from '../utils/index.js';
44
import type { RuleContext } from '../types.js';
55
import { getSvelteVersion } from '../utils/svelte-context.js';
6-
import { getFilename } from '../utils/compat.js';
76

87
const EXPECTED_PROP_NAMES = ['data', 'errors', 'form', 'snapshot'];
98
const EXPECTED_PROP_NAMES_SVELTE5 = [...EXPECTED_PROP_NAMES, 'children'];
@@ -49,7 +48,7 @@ export default createRule('valid-prop-names-in-kit-pages', {
4948
},
5049
create(context) {
5150
let isScript = false;
52-
const isSvelte5 = getSvelteVersion(getFilename(context)) === '5';
51+
const isSvelte5 = getSvelteVersion() === '5';
5352
const expectedPropNames = isSvelte5 ? EXPECTED_PROP_NAMES_SVELTE5 : EXPECTED_PROP_NAMES;
5453
return {
5554
// <script>
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

+56-53
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ 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';
8+
import { VERSION as SVELTE_VERSION } from 'svelte/compiler';
79

810
const isRunInBrowser = !fs.readFileSync;
911

@@ -168,51 +170,37 @@ function getSvelteKitContext(
168170
return result;
169171
}
170172

171-
const svelteVersionCache = createCache<SvelteContext['svelteVersion']>();
172-
173-
export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion'] {
174-
const cached = svelteVersionCache.get(filePath);
175-
if (cached) return cached;
173+
function checkAndSetSvelteVersion(version: string): SvelteContext['svelteVersion'] | null {
174+
const major = extractMajorVersion(version, false);
175+
if (major == null) {
176+
return null;
177+
}
178+
if (major === '3' || major === '4') {
179+
return '3/4';
180+
}
181+
return major as SvelteContext['svelteVersion'];
182+
}
176183

184+
export function getSvelteVersion(): SvelteContext['svelteVersion'] {
177185
// Hack: if it runs in browser, it regards as Svelte project.
178186
if (isRunInBrowser) {
179-
svelteVersionCache.set(filePath, '5');
180187
return '5';
181188
}
182189

183-
try {
184-
const packageJsons = getPackageJsons(filePath);
185-
for (const packageJson of packageJsons) {
186-
const version = packageJson.dependencies?.svelte ?? packageJson.devDependencies?.svelte;
187-
if (typeof version !== 'string') {
188-
continue;
189-
}
190-
const major = extractMajorVersion(version, false);
191-
if (major === '3' || major === '4') {
192-
svelteVersionCache.set(filePath, '3/4');
193-
return '3/4';
194-
}
195-
svelteVersionCache.set(filePath, major as SvelteContext['svelteVersion']);
196-
return major as SvelteContext['svelteVersion'];
197-
}
198-
} catch {
199-
/** do nothing */
200-
}
201-
202-
svelteVersionCache.set(filePath, null);
203-
return null;
190+
return checkAndSetSvelteVersion(SVELTE_VERSION);
204191
}
205192

206193
const svelteKitVersionCache = createCache<SvelteContext['svelteKitVersion']>();
207194

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-
*/
195+
function checkAndSetSvelteKitVersion(
196+
version: string,
197+
filePath: string
198+
): SvelteContext['svelteKitVersion'] {
199+
const major = extractMajorVersion(version, true) as SvelteContext['svelteKitVersion'];
200+
svelteKitVersionCache.set(filePath, major);
201+
return major;
202+
}
203+
216204
function getSvelteKitVersion(filePath: string): SvelteContext['svelteKitVersion'] {
217205
const cached = svelteKitVersionCache.get(filePath);
218206
if (cached) return cached;
@@ -223,27 +211,42 @@ function getSvelteKitVersion(filePath: string): SvelteContext['svelteKitVersion'
223211
return '2';
224212
}
225213

214+
const nodeModule = getNodeModule('@sveltejs/kit', filePath);
215+
if (nodeModule) {
216+
try {
217+
const packageJson = JSON.parse(
218+
fs.readFileSync(path.join(nodeModule, 'package.json'), 'utf8')
219+
);
220+
const result = checkAndSetSvelteKitVersion(packageJson.version, filePath);
221+
if (result != null) {
222+
return result;
223+
}
224+
} catch {
225+
/** do nothing */
226+
}
227+
}
228+
226229
try {
227230
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-
}
231+
if (packageJsons.length > 0) {
232+
if (packageJsons[0].name === 'eslint-plugin-svelte') {
233+
// Hack: CI removes `@sveltejs/kit` and it returns false and test failed.
234+
// So always it returns 2 if it runs on the package.
235+
svelteKitVersionCache.set(filePath, '2');
236+
return '2';
237+
}
235238

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;
239+
for (const packageJson of packageJsons) {
240+
const version =
241+
packageJson.dependencies?.['@sveltejs/kit'] ??
242+
packageJson.devDependencies?.['@sveltejs/kit'];
243+
if (typeof version === 'string') {
244+
const result = checkAndSetSvelteKitVersion(version, filePath);
245+
if (result != null) {
246+
return result;
247+
}
248+
}
243249
}
244-
const major = extractMajorVersion(version, true) as SvelteContext['svelteKitVersion'];
245-
svelteKitVersionCache.set(filePath, major);
246-
return major;
247250
}
248251
} catch {
249252
/** do nothing */
@@ -291,7 +294,7 @@ export function getSvelteContext(context: RuleContext): SvelteContext | null {
291294
if (cached) return cached;
292295

293296
const svelteKitContext = getSvelteKitContext(context);
294-
const svelteVersion = getSvelteVersion(filePath);
297+
const svelteVersion = getSvelteVersion();
295298
const svelteFileType = getSvelteFileType(filePath);
296299

297300
if (svelteVersion == null) {

0 commit comments

Comments
 (0)