Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: detect version information from node_modules when not specified…
… in package.json, like pnpm’s catalog
  • Loading branch information
baseballyama committed Feb 9, 2025
commit 74a3f2368e17fbbb590f5786d61e2e66fd32e9c6
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
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;
}
108 changes: 77 additions & 31 deletions packages/eslint-plugin-svelte/src/utils/svelte-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -170,6 +171,23 @@ function getSvelteKitContext(

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

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;
Expand All @@ -180,20 +198,32 @@ 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) {
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';
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 */
Expand All @@ -205,14 +235,15 @@ export function getSvelteVersion(filePath: string): SvelteContext['svelteVersion

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 +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 */
Expand Down