UNPKG

@slippy-lint/slippy

Version:

A simple but powerful linter for Solidity

110 lines 3.61 kB
import { Query } from "@nomicfoundation/slang/cst"; export const SortImports = { name: "sort-imports", recommended: false, create: function () { return new SortImportsRule(this.name); }, }; class SortImportsRule { constructor(name) { this.name = name; } run({ file }) { const cursor = file.createTreeCursor(); const matches = cursor.query([ Query.create(` [ImportDirective @importKeyword import_keyword: [ImportKeyword] clause: [ImportClause [_ [StringLiteral @importPath variant: [_]]]] ] `), ]); const importPaths = [...matches] .flatMap((match) => match.captures.importPath ?? []) .map((cursor) => { return { cursor, path: cursor.node.unparse().trim().slice(1, -1), }; }); const sortedPaths = importPaths .slice() .sort((a, b) => compareImportPaths(a.path, b.path)); for (let i = 0; i < importPaths.length; i++) { if (importPaths[i].cursor.node.id !== sortedPaths[i].cursor.node.id) { let correctPosition = sortedPaths[i]; for (const sortedPath of sortedPaths) { if (compareImportPaths(importPaths[i].path, sortedPath.path) === 1) { correctPosition = sortedPath; } } return [ { rule: this.name, sourceId: file.id, message: `Import of "${importPaths[i].path}" should come after import of "${correctPosition.path}"`, line: importPaths[i].cursor.textRange.start.line, column: importPaths[i].cursor.textRange.start.column, }, ]; } } return []; } } export function compareImportPaths(a, b) { const aIsRelative = a.startsWith("."); const bIsRelative = b.startsWith("."); if (a === b) { return 0; } if (!aIsRelative && bIsRelative) { return -1; } if (aIsRelative && !bIsRelative) { return 1; } const aParts = a.split("/"); const bParts = b.split("/"); const aStartsWithDot = aParts[0] === "."; const bStartsWithDot = bParts[0] === "."; if (aStartsWithDot && !bStartsWithDot) { return 1; } if (!aStartsWithDot && bStartsWithDot) { return -1; } // Compare the rest of the path segments const minLength = Math.min(aParts.length, bParts.length); for (let i = 0; i < minLength; i++) { if (aParts[i] !== bParts[i]) { return compareImportPathParts(aParts[i], bParts[i]); } } // If all compared segments are equal, the shorter path is "less than" the longer one if (aParts.length !== bParts.length) { return aParts.length - bParts.length; } // If the segments are equal in length and content, they are equal // and we should not reach here throw new Error("Unreachable code: paths are equal but not caught earlier"); } function compareImportPathParts(a, b) { const aIsFile = a.endsWith(".sol"); const bIsFile = b.endsWith(".sol"); if (aIsFile && !bIsFile) { return 1; } if (!aIsFile && bIsFile) { return -1; } if (a === ".." && b !== "..") { return -1; } if (a !== ".." && b === "..") { return 1; } return a.localeCompare(b); } //# sourceMappingURL=sort-imports.js.map