UNPKG

@slippy-lint/slippy

Version:

A simple but powerful linter for Solidity

115 lines 4.74 kB
import * as z from "zod"; import { ignoreLeadingTrivia } from "../slang/trivia.js"; import { NonterminalKind } from "@nomicfoundation/slang/cst"; import { AssertionError } from "../errors.js"; const ExceptionSchema = z .object({ contracts: z.number().default(0), interfaces: z.number().default(0), libraries: z.number().default(0), }) .refine((o) => { return o.contracts > 0 || o.interfaces > 0 || o.libraries > 0; }, "At least one of contracts, interfaces, or libraries must be specified and positive"); const Schema = z .object({ allow: z.array(ExceptionSchema), }) .default({ allow: [] }); export const OneContractPerFile = { name: "one-contract-per-file", recommended: false, parseConfig: (config) => Schema.parse(config), create: function (config) { return new OneContractPerFileRule(this.name, config); }, }; class OneContractPerFileRule { constructor(name, config) { this.name = name; this.config = config; } run({ file }) { const cursor = file.createTreeCursor(); let contracts = 0; let interfaces = 0; let libraries = 0; let invalidNode; while (cursor.goToNextNonterminalWithKinds([ NonterminalKind.ContractDefinition, NonterminalKind.InterfaceDefinition, NonterminalKind.LibraryDefinition, ])) { if (cursor.node.kind === NonterminalKind.ContractDefinition) { contracts++; } else if (cursor.node.kind === NonterminalKind.InterfaceDefinition) { interfaces++; } else if (cursor.node.kind === NonterminalKind.LibraryDefinition) { libraries++; } if (invalidNode === undefined && !this.checkLimits(contracts, interfaces, libraries)) { invalidNode = cursor.spawn(); } } if (invalidNode !== undefined) { const message = this.buildErrorMessage(contracts, interfaces, libraries); ignoreLeadingTrivia(invalidNode); return [ { rule: this.name, sourceId: file.id, message, line: invalidNode.textRange.start.line, column: invalidNode.textRange.start.column, }, ]; } return []; } checkLimits(contracts, interfaces, libraries) { for (const exception of this.config.allow) { if (contracts <= exception.contracts && interfaces <= exception.interfaces && libraries <= exception.libraries) { return true; } } return contracts + interfaces + libraries <= 1; } buildErrorMessage(contracts, interfaces, libraries) { let message = `The file has `; if (contracts > 0 && interfaces > 0 && libraries > 0) { message += `${contracts} ${pluralize(contracts, "contract", "contracts")}, ${interfaces} ${pluralize(interfaces, "interface", "interfaces")}, and ${libraries} ${pluralize(libraries, "library", "libraries")}`; } else if (contracts > 0 && interfaces > 0 && libraries === 0) { message += `${contracts} ${pluralize(contracts, "contract", "contracts")} and ${interfaces} ${pluralize(interfaces, "interface", "interfaces")}`; } else if (contracts > 0 && interfaces === 0 && libraries > 0) { message += `${contracts} ${pluralize(contracts, "contract", "contracts")} and ${libraries} ${pluralize(libraries, "library", "libraries")}`; } else if (contracts === 0 && interfaces > 0 && libraries > 0) { message += `${interfaces} ${pluralize(interfaces, "interface", "interfaces")} and ${libraries} ${pluralize(libraries, "library", "libraries")}`; } else if (contracts > 0 && interfaces === 0 && libraries === 0) { message += `${contracts} ${pluralize(contracts, "contract", "contracts")}`; } else if (contracts === 0 && interfaces > 0 && libraries === 0) { message += `${interfaces} ${pluralize(interfaces, "interface", "interfaces")}`; } else if (contracts === 0 && interfaces === 0 && libraries > 0) { message += `${libraries} ${pluralize(libraries, "library", "libraries")}`; } else { throw new AssertionError("Unreachable"); } message += `, which is not allowed`; return message; } } function pluralize(count, singular, plural) { return count === 1 ? singular : plural; } //# sourceMappingURL=one-contract-per-file.js.map