@slippy-lint/slippy
Version:
A simple but powerful linter for Solidity
115 lines • 4.74 kB
JavaScript
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