UNPKG

@slippy-lint/slippy

Version:

A simple but powerful linter for Solidity

190 lines (165 loc) 5.16 kB
#!/usr/bin/env node import path from "node:path"; import fs from "node:fs/promises"; import process from "node:process"; import fg from "fast-glob"; import minimist from "minimist"; import { formatAndPrintResults } from "../formatter.js"; import chalk from "chalk"; import { SlippyDirectoriesNotSupportedError, SlippyError, SlippyUnmatchedPatternError, } from "../errors.js"; import { exists, findUp, isDirectory } from "../helpers/fs.js"; import { RunLinterSuccess, RunLinterWorker } from "./worker.js"; import { initConfig } from "./init.js"; import workerpool from "workerpool"; async function main() { try { const exitCode = await runCli(); process.exit(exitCode); } catch (error) { if (SlippyError.isSlippyError(error)) { console.error(chalk.red("[slippy]"), error.message); if (error.hint !== undefined) { console.error(); console.error(`${chalk.bold("Hint")}: ${error.hint}`); } process.exit(1); } const slippyVersion = await getSlippyVersion(); console.error( chalk.red("[slippy]"), `Unexpected error, please report this issue: https://github.com/fvictorio/slippy/issues/new?body=${encodeURIComponent(`Slippy: ${slippyVersion}\nNode: ${process.version}`)}`, ); console.error(); throw error; } } interface Argv { help: boolean; init: boolean; } async function runCli(): Promise<number> { const unknownArgs: string[] = []; const argv = minimist<Argv>(process.argv.slice(2), { boolean: ["help", "init"], alias: { h: "help" }, unknown: (arg) => { if (arg.startsWith("-")) { unknownArgs.push(arg); return false; } return true; }, }); const rawSourceIds: string[] = ( await Promise.all( argv._.map(async (arg) => { const matches = await fg.async(arg); if (matches.length === 0) { const asAbsolutePath = path.resolve(process.cwd(), arg); if ( (await exists(asAbsolutePath)) && (await isDirectory(asAbsolutePath)) ) { throw new SlippyDirectoriesNotSupportedError(arg); } throw new SlippyUnmatchedPatternError(arg); } return matches; }), ) ).flat(); const sourceIds = [...new Set(rawSourceIds)]; if (unknownArgs.length > 0) { console.error(`Unexpected argument ${unknownArgs[0]}`); console.error(); printShortHelp({ error: true }); return 1; } if (argv.init) { await initConfig(); return 0; } if (argv.help || sourceIds.length === 0) { printHelp({ error: false }); return 0; } const pool = workerpool.pool( new URL("./worker.js", import.meta.url).pathname, ); const results: RunLinterSuccess[] = await Promise.all( sourceIds.map(async (sourceId) => { const result = await pool.exec<RunLinterWorker>("runLinter", [sourceId]); if ("lintResults" in result) { return result; } else { // This can cause non-deterministic behavior if two files throw different errors: // a user could see one of the errors on one run and the other error on another run. // This can be fixed by using Promise.allSettled instead of Promise.all, // but that means that all workers have to finish before the error is shown. // For now, we just throw the error. throw new SlippyError(result.message, result.code, result.hint); } }), ).then( async (x) => { await pool.terminate(); return x; }, async (e) => { await pool.terminate(); throw e; }, ); const sortedResults = results .flatMap((x) => x.lintResults) .sort((a, b) => { if (a.sourceId !== b.sourceId) { return a.sourceId.localeCompare(b.sourceId); } if (a.line !== b.line) { return a.line - b.line; } return a.column - b.column; }); let sourceIdToAbsolutePath: Record<string, string> = {}; for (const result of results) { sourceIdToAbsolutePath = { ...sourceIdToAbsolutePath, ...result.sourceIdToAbsolutePath, }; } formatAndPrintResults(sortedResults, sourceIdToAbsolutePath); const exitCode = sortedResults.some((result) => result.severity === "error") ? 1 : 0; return exitCode; } async function getSlippyVersion(): Promise<string> { const packageJsonPath = await findUp("package.json", import.meta.dirname); if (packageJsonPath === undefined) { return "unknown"; } const packageJsonContents = await fs.readFile(packageJsonPath, "utf8"); const packageJson = JSON.parse(packageJsonContents) as { version?: string }; return packageJson.version ?? "unknown"; } function printShortHelp({ error }: { error: boolean }) { console[error ? "error" : "log"]( `${chalk.bold("Usage")}: slippy [OPTIONS] <file>...`, ); } function printHelp({ error }: { error: boolean }) { printShortHelp({ error }); console[error ? "error" : "log"]( ` ${chalk.bold("Options")}: --help, -h Show this help message --init Initialize a new Slippy configuration `.trimEnd(), ); } await main();