Skip to content

feat: detect version information from node_modules for pnpm’s catalog #1063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/two-poets-wait.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -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 {
// <script>
Expand Down
71 changes: 71 additions & 0 deletions packages/eslint-plugin-svelte/src/utils/get-node-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import fs from 'fs';
import path from 'path';
import { createCache } from './cache.js';

const isRunOnBrowser = !fs.readFileSync;
const nodeModuleCache = createCache<string | null>();
const nodeModulesCache = createCache<string[]>();

/**
* 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;
}
109 changes: 56 additions & 53 deletions packages/eslint-plugin-svelte/src/utils/svelte-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ 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';
import { VERSION as SVELTE_VERSION } from 'svelte/compiler';

const isRunInBrowser = !fs.readFileSync;

Expand Down Expand Up @@ -168,51 +170,37 @@ function getSvelteKitContext(
return result;
}

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

export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion'] {
const cached = svelteVersionCache.get(filePath);
if (cached) return cached;
function checkAndSetSvelteVersion(version: string): SvelteContext['svelteVersion'] | null {
const major = extractMajorVersion(version, false);
if (major == null) {
return null;
}
if (major === '3' || major === '4') {
return '3/4';
}
return major as SvelteContext['svelteVersion'];
}

export function getSvelteVersion(): SvelteContext['svelteVersion'] {
// Hack: if it runs in browser, it regards as Svelte project.
if (isRunInBrowser) {
svelteVersionCache.set(filePath, '5');
return '5';
}

try {
const packageJsons = getPackageJsons(filePath);
for (const packageJson of packageJsons) {
const version = packageJson.dependencies?.svelte ?? packageJson.devDependencies?.svelte;
if (typeof version !== 'string') {
continue;
}
const major = extractMajorVersion(version, false);
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'];
}
} catch {
/** do nothing */
}

svelteVersionCache.set(filePath, null);
return null;
return checkAndSetSvelteVersion(SVELTE_VERSION);
}

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

/**
* 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;
Expand All @@ -223,27 +211,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 */
Expand Down Expand Up @@ -291,7 +294,7 @@ export function getSvelteContext(context: RuleContext): SvelteContext | null {
if (cached) return cached;

const svelteKitContext = getSvelteKitContext(context);
const svelteVersion = getSvelteVersion(filePath);
const svelteVersion = getSvelteVersion();
const svelteFileType = getSvelteFileType(filePath);

if (svelteVersion == null) {
Expand Down