UNPKG

@slippy-lint/slippy

Version:

A simple but powerful linter for Solidity

153 lines 6.07 kB
import { ContractMember, SourceUnitMember } from "@nomicfoundation/slang/ast"; import * as z from "zod"; import { assertNonterminalNode, NonterminalKind, } from "@nomicfoundation/slang/cst"; import { ignoreLeadingTrivia } from "../slang/trivia.js"; const memberToSlangKind = { pragma: NonterminalKind.PragmaDirective, import: NonterminalKind.ImportDirective, userDefinedValueType: NonterminalKind.UserDefinedValueTypeDefinition, usingFor: NonterminalKind.UsingDirective, constant: NonterminalKind.ConstantDefinition, enum: NonterminalKind.EnumDefinition, struct: NonterminalKind.StructDefinition, event: NonterminalKind.EventDefinition, error: NonterminalKind.ErrorDefinition, function: NonterminalKind.FunctionDefinition, interface: NonterminalKind.InterfaceDefinition, library: NonterminalKind.LibraryDefinition, contract: NonterminalKind.ContractDefinition, stateVariable: NonterminalKind.StateVariableDefinition, constructor: NonterminalKind.ConstructorDefinition, modifier: NonterminalKind.ModifierDefinition, receive: NonterminalKind.ReceiveFunctionDefinition, fallback: NonterminalKind.FallbackFunctionDefinition, }; const fileMembersOrder = [ "pragma", "import", "userDefinedValueType", "usingFor", "constant", "enum", "struct", "event", "error", "function", "interface", "library", "contract", ]; const FileMemberSchema = z.enum(fileMembersOrder); const contractMembersOrder = [ "userDefinedValueType", "usingFor", "enum", "struct", "event", "error", "stateVariable", "constructor", "modifier", "function", "receive", "fallback", ]; const ContractMemberSchema = z.enum(contractMembersOrder); function uniqueMembers(a) { return a.length === new Set(a).size; } const Schema = z .object({ file: z .array(FileMemberSchema) .refine(uniqueMembers, "Custom order must not contain duplicates") .default(fileMembersOrder), contract: z .array(ContractMemberSchema) .refine(uniqueMembers, "Custom order must not contain duplicates") .default(contractMembersOrder), }) .default({ file: fileMembersOrder, contract: contractMembersOrder }); export const SortMembers = { name: "sort-members", recommended: false, parseConfig: (config) => Schema.parse(config), create: function (config) { return new SortMembersRule(this.name, config); }, }; class SortMembersRule { constructor(name, config) { this.name = name; this.config = config; } run({ file }) { const diagnostics = []; const cursor = file.createTreeCursor(); diagnostics.push(...this.checkMembers(file, cursor.spawn(), "Source unit member", SourceUnitMember, NonterminalKind.SourceUnitMember, this.config.file)); const contractsCursor = cursor.spawn(); while (contractsCursor.goToNextNonterminalWithKind(NonterminalKind.ContractDefinition)) { diagnostics.push(...this.checkMembers(file, contractsCursor.spawn(), "Contract member", ContractMember, NonterminalKind.ContractMember, this.config.contract)); } const interfacesCursor = cursor.spawn(); while (interfacesCursor.goToNextNonterminalWithKind(NonterminalKind.InterfaceDefinition)) { diagnostics.push(...this.checkMembers(file, interfacesCursor.spawn(), "Interface member", ContractMember, NonterminalKind.ContractMember, this.config.contract)); } const librariesCursor = cursor.spawn(); while (librariesCursor.goToNextNonterminalWithKind(NonterminalKind.LibraryDefinition)) { diagnostics.push(...this.checkMembers(file, librariesCursor.spawn(), "Library member", ContractMember, NonterminalKind.ContractMember, this.config.contract)); } return diagnostics; } checkMembers(file, cursor, label, AstConstructor, memberKind, membersOrder) { const members = []; const order = membersOrder.map((x) => { return memberToSlangKind[x]; }); const compareFunction = buildCompareMembers(order); while (cursor.goToNextNonterminalWithKind(memberKind)) { assertNonterminalNode(cursor.node, memberKind); const astNode = new AstConstructor(cursor.node); const kind = astNode.variant.cst.kind; if (order.includes(kind)) { members.push({ kind, cursor: cursor.spawn() }); } } const sortedMembers = [...members].sort((a, b) => { return compareFunction(a.kind, b.kind); }); for (let i = 0; i < members.length; i++) { if (members[i].kind !== sortedMembers[i].kind) { let correctPosition = sortedMembers[i]; for (const sortedMember of sortedMembers) { if (compareFunction(members[i].kind, sortedMember.kind) === 1) { correctPosition = sortedMember; } } const memberKind = members[i].kind; const correctPositionKind = correctPosition.kind; const textRangeCursor = members[i].cursor.spawn(); ignoreLeadingTrivia(textRangeCursor); // we only report the first violation return [ { rule: this.name, sourceId: file.id, message: `${label} of kind "${memberKind}" should come after kind "${correctPositionKind}"`, line: textRangeCursor.textRange.start.line, column: textRangeCursor.textRange.start.column, }, ]; } } return []; } } function buildCompareMembers(order) { return (a, b) => { const indexA = order.indexOf(a); const indexB = order.indexOf(b); return indexA - indexB; }; } //# sourceMappingURL=sort-members.js.map