@slippy-lint/slippy
Version:
A simple but powerful linter for Solidity
110 lines • 3.61 kB
JavaScript
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
import_keyword: [ImportKeyword]
clause: [ImportClause [_ [StringLiteral 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