// @ts-ignore - ESLint types may not be available
import { ESLint } from 'eslint';
import prettier from 'prettier';
import { readFileSync } from 'fs';
import type { LintResult, FormatResult } from '../types/index.js';
export class LintingUtils {
private eslint: ESLint | null = null;
/**
* Initialize ESLint
*/
private async getESLint(): Promise<ESLint> {
if (!this.eslint) {
this.eslint = new ESLint({
useEslintrc: true,
fix: false,
});
}
return this.eslint;
}
/**
* Lint code files
*/
async lintFiles(filePaths: string[]): Promise<LintResult[]> {
try {
const eslint = await this.getESLint();
const results = await eslint.lintFiles(filePaths);
return results.map((result: any) => ({
file: result.filePath,
messages: result.messages.map((msg: any) => ({
ruleId: msg.ruleId,
severity: msg.severity as 0 | 1 | 2,
message: msg.message,
line: msg.line,
column: msg.column,
endLine: msg.endLine,
endColumn: msg.endColumn,
fix: msg.fix
? {
range: [msg.fix.range[0], msg.fix.range[1]],
text: msg.fix.text,
}
: undefined,
})),
errorCount: result.errorCount,
warningCount: result.warningCount,
fixableErrorCount: result.fixableErrorCount,
fixableWarningCount: result.fixableWarningCount,
}));
} catch (error) {
// If ESLint is not configured, return empty results
if (error instanceof Error && error.message.includes('No ESLint configuration')) {
return filePaths.map((file) => ({
file,
messages: [],
errorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
}));
}
throw error;
}
}
/**
* Fix linting issues automatically
*/
async fixLintIssues(filePaths: string[]): Promise<LintResult[]> {
try {
const eslint = new ESLint({
useEslintrc: true,
fix: true,
});
const results = await eslint.lintFiles(filePaths);
await ESLint.outputFixes(results);
return this.lintFiles(filePaths);
} catch (error) {
if (error instanceof Error && error.message.includes('No ESLint configuration')) {
return filePaths.map((file) => ({
file,
messages: [],
errorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
}));
}
throw error;
}
}
/**
* Format code with Prettier
*/
async formatCode(filePath: string, options?: prettier.Options): Promise<FormatResult> {
try {
const content = readFileSync(filePath, 'utf-8');
const formatted = await prettier.format(content, {
filepath: filePath,
...options,
});
return {
file: filePath,
formatted: content !== formatted,
original: content,
formattedContent: formatted,
};
} catch (error) {
// If Prettier fails, return unformatted
const content = readFileSync(filePath, 'utf-8');
return {
file: filePath,
formatted: false,
original: content,
formattedContent: content,
};
}
}
/**
* Check TypeScript syntax
*/
async checkTypeScript(_filePath: string): Promise<{
errors: Array<{ line: number; column: number; message: string; code: string }>;
valid: boolean;
}> {
// This is a simplified version. In production, we'd use TypeScript compiler API
// For now, return basic validation
try {
// Basic TypeScript syntax checks
const errors: Array<{ line: number; column: number; message: string; code: string }> = [];
// This is a simplified version. In production, we'd use TypeScript compiler API
// For now, return empty errors array
return {
errors,
valid: errors.length === 0,
};
} catch (error) {
return {
errors: [
{
line: 1,
column: 1,
message: error instanceof Error ? error.message : 'Unknown error',
code: 'UNKNOWN',
},
],
valid: false,
};
}
}
/**
* Validate syntax (basic)
*/
validateSyntax(code: string, language: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (language === 'javascript' || language === 'typescript') {
// Basic bracket matching
const openBracesCount = (code.match(/{/g) || []).length;
const closeBracesCount = (code.match(/}/g) || []).length;
if (openBracesCount !== closeBracesCount) {
errors.push('Unmatched braces');
}
const openParens = (code.match(/\(/g) || []).length;
const closeParens = (code.match(/\)/g) || []).length;
if (openParens !== closeParens) {
errors.push('Unmatched parentheses');
}
const openBrackets = (code.match(/\[/g) || []).length;
const closeBrackets = (code.match(/\]/g) || []).length;
if (openBrackets !== closeBrackets) {
errors.push('Unmatched brackets');
}
}
return {
valid: errors.length === 0,
errors,
};
}
}