UNPKG

@slippy-lint/slippy

Version:

A simple but powerful linter for Solidity

123 lines (103 loc) 3.21 kB
import { LintResult, LintResultToReport, RuleDefinition, SourceFile, } from "./rules/types.js"; import { filterByCommentDirectives } from "./comment-directives.js"; import { compilationUnitFromContent } from "./slang/compilation-unit.js"; import { ConfigLoader } from "./config.js"; import { SlippyRuleConfigError, SlippyRuleNotRegisteredError, } from "./errors.js"; import { getAllRules } from "./rules/get-all-rules.js"; import * as z from "zod"; export class Linter { private ruleNameToRule: Map<string, RuleDefinition<any>> = new Map(); constructor(private configLoader: ConfigLoader) { this.registerBuiltInRules(); } public async lintFiles(files: SourceFile[]): Promise<LintResultToReport[]> { const results: LintResultToReport[] = []; for (const file of files) { results.push(...(await this.lintText(file.content, file.filePath))); } return results; } public async lintText( content: string, filePath: string, ): Promise<LintResultToReport[]> { const config = this.configLoader.loadConfig(filePath); const results: LintResultToReport[] = []; const unit = await compilationUnitFromContent({ content, filePath }); const file = unit.file(filePath)!; if (file.errors().length > 0) { results.push({ sourceId: file.id, rule: null, line: file.errors()[0].textRange.start.line, column: file.errors()[0].textRange.start.column, message: "Parsing error", severity: "error", }); return results; } for (const [ruleName, ruleConfig] of Object.entries(config.rules)) { const Rule = this.ruleNameToRule.get(ruleName); if (Rule === undefined) { throw new SlippyRuleNotRegisteredError(ruleName); } const severity = ruleConfig[0]; if (severity === "off") continue; let rule; if ("parseConfig" in Rule) { try { const config = Rule.parseConfig(ruleConfig[1]); rule = Rule.create(config); } catch (error: unknown) { if (error instanceof z.ZodError) { const problem = z.prettifyError(error); throw new SlippyRuleConfigError(Rule.name, `\n\n${problem}`); } throw error; } } else { if (ruleConfig.length > 1) { throw new SlippyRuleConfigError( Rule.name, "Rule requires no configuration, but received an array with more than one element.", ); } rule = Rule.create(); } const ruleResults: LintResult[] = rule.run({ unit, file, }); const ruleResultsToReport: LintResultToReport[] = ruleResults.map( (result) => { return { ...result, severity, }; }, ); results.push(...ruleResultsToReport); } const filteredResults = filterByCommentDirectives( content, results, file, unit.languageVersion, ); return filteredResults; } private registerBuiltInRules() { const rules = getAllRules(); for (const rule of rules) { this.ruleNameToRule.set(rule.name, rule); } } }