141 lines
4.1 KiB
TypeScript
141 lines
4.1 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { ESLint } from 'eslint';
|
|
import js from '@eslint/js';
|
|
import ts from 'typescript-eslint';
|
|
import type { LinterAdapter, AdapterResult, LinterDiagnostic } from './adapter';
|
|
import { getEslintConfigPath } from '../config';
|
|
import { t } from '../i18n/messages';
|
|
import { eslintExtraRules, eslintExtraTsRules } from '../rules/builtin-rules';
|
|
|
|
const TS_FILES = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];
|
|
|
|
const PROJECT_CONFIG_FILES = [
|
|
'eslint.config.js',
|
|
'eslint.config.mjs',
|
|
'eslint.config.cjs',
|
|
'eslint.config.ts',
|
|
'eslint.config.mts',
|
|
'eslint.config.cts',
|
|
];
|
|
|
|
const LEGACY_CONFIG_FILES = [
|
|
'.eslintrc.js',
|
|
'.eslintrc.cjs',
|
|
'.eslintrc.json',
|
|
'.eslintrc.yaml',
|
|
'.eslintrc.yml',
|
|
'.eslintrc',
|
|
];
|
|
|
|
function findConfigFile(dir: string, names: string[]): string | null {
|
|
for (const name of names) {
|
|
const p = path.join(dir, name);
|
|
if (fs.existsSync(p)) {
|
|
return p;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
type EslintConfigResult =
|
|
| { kind: 'use'; config: { overrideConfigFile?: string | true; overrideConfig?: any[] } }
|
|
| { kind: 'legacy'; path: string };
|
|
|
|
function resolveEslintConfig(workingDir: string): EslintConfigResult {
|
|
const globalPath = getEslintConfigPath();
|
|
if (globalPath && globalPath.trim() !== '') {
|
|
const abs = path.isAbsolute(globalPath) ? globalPath : path.resolve(workingDir, globalPath);
|
|
if (fs.existsSync(abs)) {
|
|
return { kind: 'use', config: { overrideConfigFile: abs } };
|
|
}
|
|
}
|
|
|
|
const projectConfig = findConfigFile(workingDir, PROJECT_CONFIG_FILES);
|
|
if (projectConfig) {
|
|
return { kind: 'use', config: { overrideConfigFile: projectConfig } };
|
|
}
|
|
|
|
const legacyConfig = findConfigFile(workingDir, LEGACY_CONFIG_FILES);
|
|
if (legacyConfig) {
|
|
return { kind: 'legacy', path: legacyConfig };
|
|
}
|
|
|
|
return { kind: 'use', config: { overrideConfigFile: true, overrideConfig: ESLintAdapter.getDefaultConfig() } };
|
|
}
|
|
|
|
export class ESLintAdapter implements LinterAdapter {
|
|
id = 'eslint';
|
|
supportedLanguages = ['javascript', 'typescript'];
|
|
|
|
private static defaultConfig: any[] | null = null;
|
|
|
|
public static getDefaultConfig(): any[] {
|
|
if (!ESLintAdapter.defaultConfig) {
|
|
ESLintAdapter.defaultConfig = [
|
|
js.configs.recommended,
|
|
...ts.configs.recommended,
|
|
{ rules: eslintExtraRules },
|
|
{ files: TS_FILES, rules: eslintExtraTsRules },
|
|
];
|
|
}
|
|
return ESLintAdapter.defaultConfig;
|
|
}
|
|
|
|
isAvailable(): boolean {
|
|
return true;
|
|
}
|
|
|
|
async check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult> {
|
|
try {
|
|
const resolved = resolveEslintConfig(workingDir);
|
|
if (resolved.kind === 'legacy') {
|
|
return {
|
|
diagnostics: [],
|
|
status: 'execution-failed',
|
|
errorMessage: t('adapter.eslintLegacyConfig', { 0: resolved.path }),
|
|
};
|
|
}
|
|
|
|
const engine = new ESLint({
|
|
cwd: workingDir,
|
|
...resolved.config,
|
|
});
|
|
const ext = document.languageId === 'typescript' ? 'ts' : 'js';
|
|
const isVirtual = document.uri.scheme === 'untitled';
|
|
const results = await engine.lintText(document.getText(), {
|
|
filePath: isVirtual ? `untitled.${ext}` : document.fileName,
|
|
});
|
|
|
|
const diagnostics: LinterDiagnostic[] = [];
|
|
for (const result of results) {
|
|
for (const msg of result.messages) {
|
|
if (msg.ruleId === null) { continue; }
|
|
|
|
diagnostics.push({
|
|
severity: msg.severity === 2 ? 'error' : 'warning',
|
|
ruleId: `eslint:${msg.ruleId}`,
|
|
message: msg.message,
|
|
range: new vscode.Range(
|
|
msg.line - 1,
|
|
msg.column - 1,
|
|
(msg.endLine ?? msg.line) - 1,
|
|
(msg.endColumn ?? msg.column) - 1
|
|
),
|
|
suggestion: msg.fix?.text,
|
|
});
|
|
}
|
|
}
|
|
|
|
return { diagnostics, status: 'ok' };
|
|
} catch (error) {
|
|
return {
|
|
diagnostics: [],
|
|
status: 'execution-failed',
|
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
};
|
|
}
|
|
}
|
|
}
|