From 74a3f2368e17fbbb590f5786d61e2e66fd32e9c6 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 9 Feb 2025 19:38:26 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20detect=20version=20information=20fr?= =?UTF-8?q?om=20node=5Fmodules=20when=20not=20specified=20in=20package.jso?= =?UTF-8?q?n,=20like=20pnpm=E2=80=99s=20catalog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/two-poets-wait.md | 5 + .../src/utils/get-node-module.ts | 71 ++++++++++++ .../src/utils/svelte-context.ts | 108 +++++++++++++----- 3 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 .changeset/two-poets-wait.md create mode 100644 packages/eslint-plugin-svelte/src/utils/get-node-module.ts diff --git a/.changeset/two-poets-wait.md b/.changeset/two-poets-wait.md new file mode 100644 index 000000000..ff9a23c85 --- /dev/null +++ b/.changeset/two-poets-wait.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat: detect version information from node_modules when not specified in package.json, like pnpm’s catalog diff --git a/packages/eslint-plugin-svelte/src/utils/get-node-module.ts b/packages/eslint-plugin-svelte/src/utils/get-node-module.ts new file mode 100644 index 000000000..9b2a445fe --- /dev/null +++ b/packages/eslint-plugin-svelte/src/utils/get-node-module.ts @@ -0,0 +1,71 @@ +import fs from 'fs'; +import path from 'path'; +import { createCache } from './cache.js'; + +const isRunOnBrowser = !fs.readFileSync; +const nodeModuleCache = createCache(); +const nodeModulesCache = createCache(); + +/** + * Find package directory in node_modules + */ +function findPackageInNodeModules(dir: string, packageName: string): string | null { + if (isRunOnBrowser) return null; + + const nodeModulesPath = path.join(dir, 'node_modules'); + const packagePath = path.join(nodeModulesPath, packageName); + + try { + const stats = fs.statSync(packagePath); + if (stats.isDirectory()) { + return packagePath; + } + } catch { + // ignore if directory not found + } + + return null; +} + +/** + * Get first found package path from node_modules + */ +export function getNodeModule(packageName: string, startPath = 'a.js'): string | null { + if (isRunOnBrowser) return null; + + const cacheKey = `${startPath}:${packageName}`; + const cached = nodeModulesCache.get(cacheKey); + if (cached) { + return cached[0] ?? null; + } + + const startDir = path.dirname(path.resolve(startPath)); + let dir = startDir; + let prevDir = ''; + + do { + // check cache + const cachePath = nodeModuleCache.get(`${dir}:${packageName}`); + if (cachePath) { + if (cachePath !== null) { + nodeModulesCache.set(cacheKey, [cachePath]); + return cachePath; + } + } else { + // search new + const packagePath = findPackageInNodeModules(dir, packageName); + nodeModuleCache.set(`${dir}:${packageName}`, packagePath); + if (packagePath) { + nodeModulesCache.set(cacheKey, [packagePath]); + return packagePath; + } + } + + // go to parent + prevDir = dir; + dir = path.resolve(dir, '..'); + } while (dir !== prevDir); + + nodeModulesCache.set(cacheKey, []); + return null; +} diff --git a/packages/eslint-plugin-svelte/src/utils/svelte-context.ts b/packages/eslint-plugin-svelte/src/utils/svelte-context.ts index e821e3a81..b9551afb0 100644 --- a/packages/eslint-plugin-svelte/src/utils/svelte-context.ts +++ b/packages/eslint-plugin-svelte/src/utils/svelte-context.ts @@ -2,6 +2,7 @@ import type { RuleContext } from '../types.js'; import fs from 'fs'; import path from 'path'; import { getPackageJsons } from './get-package-json.js'; +import { getNodeModule } from './get-node-module.js'; import { getFilename, getSourceCode } from './compat.js'; import { createCache } from './cache.js'; @@ -170,6 +171,23 @@ function getSvelteKitContext( const svelteVersionCache = createCache(); +function checkAndSetSvelteVersion( + version: string, + filePath: string +): SvelteContext['svelteVersion'] | null { + const major = extractMajorVersion(version, false); + if (major == null) { + svelteVersionCache.set(filePath, null); + return null; + } + if (major === '3' || major === '4') { + svelteVersionCache.set(filePath, '3/4'); + return '3/4'; + } + svelteVersionCache.set(filePath, major as SvelteContext['svelteVersion']); + return major as SvelteContext['svelteVersion']; +} + export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion'] { const cached = svelteVersionCache.get(filePath); if (cached) return cached; @@ -180,6 +198,21 @@ export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion return '5'; } + const nodeModule = getNodeModule('svelte', filePath); + if (nodeModule) { + try { + const packageJson = JSON.parse( + fs.readFileSync(path.join(nodeModule, 'package.json'), 'utf8') + ); + const result = checkAndSetSvelteVersion(packageJson.version, filePath); + if (result != null) { + return result; + } + } catch { + /** do nothing */ + } + } + try { const packageJsons = getPackageJsons(filePath); for (const packageJson of packageJsons) { @@ -187,13 +220,10 @@ export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion if (typeof version !== 'string') { continue; } - const major = extractMajorVersion(version, false); - if (major === '3' || major === '4') { - svelteVersionCache.set(filePath, '3/4'); - return '3/4'; + const result = checkAndSetSvelteVersion(version, filePath); + if (result != null) { + return result; } - svelteVersionCache.set(filePath, major as SvelteContext['svelteVersion']); - return major as SvelteContext['svelteVersion']; } } catch { /** do nothing */ @@ -205,14 +235,15 @@ export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion const svelteKitVersionCache = createCache(); -/** - * Check givin file is under SvelteKit project. - * - * If it runs on browser, it always returns true. - * - * @param filePath A file path. - * @returns - */ +function checkAndSetSvelteKitVersion( + version: string, + filePath: string +): SvelteContext['svelteKitVersion'] { + const major = extractMajorVersion(version, true) as SvelteContext['svelteKitVersion']; + svelteKitVersionCache.set(filePath, major); + return major; +} + function getSvelteKitVersion(filePath: string): SvelteContext['svelteKitVersion'] { const cached = svelteKitVersionCache.get(filePath); if (cached) return cached; @@ -223,27 +254,42 @@ function getSvelteKitVersion(filePath: string): SvelteContext['svelteKitVersion' return '2'; } + const nodeModule = getNodeModule('@sveltejs/kit', filePath); + if (nodeModule) { + try { + const packageJson = JSON.parse( + fs.readFileSync(path.join(nodeModule, 'package.json'), 'utf8') + ); + const result = checkAndSetSvelteKitVersion(packageJson.version, filePath); + if (result != null) { + return result; + } + } catch { + /** do nothing */ + } + } + try { const packageJsons = getPackageJsons(filePath); - if (packageJsons.length === 0) return null; - if (packageJsons[0].name === 'eslint-plugin-svelte') { - // Hack: CI removes `@sveltejs/kit` and it returns false and test failed. - // So always it returns 2 if it runs on the package. - svelteKitVersionCache.set(filePath, '2'); - return '2'; - } + if (packageJsons.length > 0) { + if (packageJsons[0].name === 'eslint-plugin-svelte') { + // Hack: CI removes `@sveltejs/kit` and it returns false and test failed. + // So always it returns 2 if it runs on the package. + svelteKitVersionCache.set(filePath, '2'); + return '2'; + } - for (const packageJson of packageJsons) { - const version = - packageJson.dependencies?.['@sveltejs/kit'] ?? - packageJson.devDependencies?.['@sveltejs/kit']; - if (typeof version !== 'string') { - svelteKitVersionCache.set(filePath, null); - return null; + for (const packageJson of packageJsons) { + const version = + packageJson.dependencies?.['@sveltejs/kit'] ?? + packageJson.devDependencies?.['@sveltejs/kit']; + if (typeof version === 'string') { + const result = checkAndSetSvelteKitVersion(version, filePath); + if (result != null) { + return result; + } + } } - const major = extractMajorVersion(version, true) as SvelteContext['svelteKitVersion']; - svelteKitVersionCache.set(filePath, major); - return major; } } catch { /** do nothing */ From 42100df568a302865a4c812f90d69bc5521db47f Mon Sep 17 00:00:00 2001 From: baseballyama Date: Mon, 10 Feb 2025 09:26:31 +0900 Subject: [PATCH 2/2] use VERSION --- .../rules/valid-prop-names-in-kit-pages.ts | 3 +- .../src/utils/svelte-context.ts | 53 ++----------------- 2 files changed, 6 insertions(+), 50 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts b/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts index ab3f9d526..95cbe49ea 100644 --- a/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts +++ b/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts @@ -3,7 +3,6 @@ import type { TSESTree } from '@typescript-eslint/types'; import { createRule } from '../utils/index.js'; import type { RuleContext } from '../types.js'; import { getSvelteVersion } from '../utils/svelte-context.js'; -import { getFilename } from '../utils/compat.js'; const EXPECTED_PROP_NAMES = ['data', 'errors', 'form', 'snapshot']; const EXPECTED_PROP_NAMES_SVELTE5 = [...EXPECTED_PROP_NAMES, 'children']; @@ -49,7 +48,7 @@ export default createRule('valid-prop-names-in-kit-pages', { }, create(context) { let isScript = false; - const isSvelte5 = getSvelteVersion(getFilename(context)) === '5'; + const isSvelte5 = getSvelteVersion() === '5'; const expectedPropNames = isSvelte5 ? EXPECTED_PROP_NAMES_SVELTE5 : EXPECTED_PROP_NAMES; return { //