@bomb.sh/tools
Version:
The internal dev, build, and lint CLI for Bombshell projects
1 lines • 11.7 kB
Source Map (JSON)
{"version":3,"file":"lint.mjs","names":[],"sources":["../../src/commands/lint.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { parse } from \"@bomb.sh/args\";\nimport { publint } from \"publint\";\nimport { x } from \"tinyexec\";\nimport type { CommandContext } from \"../context.ts\";\nimport { local } from \"../utils.ts\";\n\nconst oxlintConfig = fileURLToPath(new URL(\"../../oxlintrc.json\", import.meta.url));\n\n// -- Types --\n\ninterface Violation {\n\ttool: \"oxlint\" | \"publint\" | \"knip\" | \"tsc\";\n\tlevel: \"error\" | \"warning\" | \"suggestion\";\n\tcode: string;\n\tmessage: string;\n\tfile?: string;\n\tline?: number;\n\tcolumn?: number;\n}\n\n// -- Tool Runners --\n\nasync function runOxlint(targets: string[], fix?: boolean): Promise<Violation[]> {\n\tconst args = [\"-c\", oxlintConfig, \"--format=json\", ...targets];\n\tif (fix) args.push(\"--fix\");\n\tconst result = await x(local(\"oxlint\"), args, { throwOnError: false });\n\tconst json = JSON.parse(result.stdout);\n\treturn (json.diagnostics ?? []).map(\n\t\t(d: {\n\t\t\tmessage: string;\n\t\t\tcode: string;\n\t\t\tseverity: string;\n\t\t\tfilename?: string;\n\t\t\tlabels?: Array<{ span?: { line?: number; column?: number } }>;\n\t\t}) => ({\n\t\t\ttool: \"oxlint\" as const,\n\t\t\tlevel: d.severity === \"error\" ? \"error\" : \"warning\",\n\t\t\tcode: d.code ?? \"unknown\",\n\t\t\tmessage: d.message,\n\t\t\tfile: d.filename,\n\t\t\tline: d.labels?.[0]?.span?.line,\n\t\t\tcolumn: d.labels?.[0]?.span?.column,\n\t\t}),\n\t);\n}\n\nasync function runPublint(): Promise<Violation[]> {\n\tconst result = await publint({ strict: true });\n\treturn result.messages.map((m) => ({\n\t\ttool: \"publint\" as const,\n\t\tlevel: m.type === \"error\" ? \"error\" : m.type === \"warning\" ? \"warning\" : \"suggestion\",\n\t\tcode: m.code,\n\t\tmessage: m.code,\n\t\tfile: \"package.json\",\n\t\tline: undefined,\n\t\tcolumn: undefined,\n\t}));\n}\n\ninterface KnipIssue {\n\tfile: string;\n\tdependencies: Array<{ name: string; line: number; col: number }>;\n\tdevDependencies: Array<{ name: string; line: number; col: number }>;\n\texports: Array<{ name: string; line: number; col: number }>;\n\ttypes: Array<{ name: string; line: number; col: number }>;\n}\n\nasync function runKnip(): Promise<Violation[]> {\n\tconst args = [\"--no-progress\", \"--reporter\", \"json\"];\n\tconst result = await x(local(\"knip\"), args, { throwOnError: false });\n\tif (!result.stdout.trim()) return [];\n\tconst json = JSON.parse(result.stdout);\n\tconst violations: Violation[] = [];\n\n\tfor (const issue of json.issues as KnipIssue[]) {\n\t\tfor (const dep of issue.dependencies) {\n\t\t\tviolations.push({\n\t\t\t\ttool: \"knip\",\n\t\t\t\tlevel: \"warning\",\n\t\t\t\tcode: \"unused-dependency\",\n\t\t\t\tmessage: `Unused dependency '${dep.name}'`,\n\t\t\t\tfile: issue.file,\n\t\t\t\tline: dep.line,\n\t\t\t\tcolumn: dep.col,\n\t\t\t});\n\t\t}\n\t\tfor (const dep of issue.devDependencies) {\n\t\t\tviolations.push({\n\t\t\t\ttool: \"knip\",\n\t\t\t\tlevel: \"warning\",\n\t\t\t\tcode: \"unused-devDependency\",\n\t\t\t\tmessage: `Unused devDependency '${dep.name}'`,\n\t\t\t\tfile: issue.file,\n\t\t\t\tline: dep.line,\n\t\t\t\tcolumn: dep.col,\n\t\t\t});\n\t\t}\n\t\tfor (const exp of issue.exports) {\n\t\t\tviolations.push({\n\t\t\t\ttool: \"knip\",\n\t\t\t\tlevel: \"warning\",\n\t\t\t\tcode: \"unused-export\",\n\t\t\t\tmessage: `Unused export '${exp.name}'`,\n\t\t\t\tfile: issue.file,\n\t\t\t\tline: exp.line,\n\t\t\t\tcolumn: exp.col,\n\t\t\t});\n\t\t}\n\t\tfor (const t of issue.types) {\n\t\t\tviolations.push({\n\t\t\t\ttool: \"knip\",\n\t\t\t\tlevel: \"warning\",\n\t\t\t\tcode: \"unused-type\",\n\t\t\t\tmessage: `Unused type '${t.name}'`,\n\t\t\t\tfile: issue.file,\n\t\t\t\tline: t.line,\n\t\t\t\tcolumn: t.col,\n\t\t\t});\n\t\t}\n\t}\n\n\tfor (const file of json.files as string[]) {\n\t\tviolations.push({\n\t\t\ttool: \"knip\",\n\t\t\tlevel: \"warning\",\n\t\t\tcode: \"unused-file\",\n\t\t\tmessage: `Unused file`,\n\t\t\tfile,\n\t\t});\n\t}\n\n\treturn violations;\n}\n\nasync function runTypeScript(targets: string[]): Promise<Violation[]> {\n\tconst args =\n\t\ttargets.length > 0\n\t\t\t? [\"--noEmit\", \"--pretty\", \"false\", ...targets]\n\t\t\t: [\"--noEmit\", \"--pretty\", \"false\"];\n\tconst result = await x(local(\"tsgo\"), args, { throwOnError: false });\n\tconst output = result.stdout + result.stderr;\n\tif (!output.trim()) return [];\n\n\tconst violations: Violation[] = [];\n\tconst re = /^(.+)\\((\\d+),(\\d+)\\): (error|warning) (TS\\d+): (.+)$/gm;\n\tlet match: RegExpExecArray | null;\n\twhile ((match = re.exec(output)) !== null) {\n\t\tviolations.push({\n\t\t\ttool: \"tsc\",\n\t\t\tlevel: match[4] === \"error\" ? \"error\" : \"warning\",\n\t\t\tcode: match[5]!,\n\t\t\tmessage: match[6]!,\n\t\t\tfile: match[1]!,\n\t\t\tline: Number(match[2]),\n\t\t\tcolumn: Number(match[3]),\n\t\t});\n\t}\n\treturn violations;\n}\n\n// -- Output --\n\nfunction printViolations(violations: Violation[]) {\n\tconst grouped = new Map<string, Violation[]>();\n\tfor (const v of violations) {\n\t\tconst key = v.file ?? \"(project)\";\n\t\tif (!grouped.has(key)) grouped.set(key, []);\n\t\tgrouped.get(key)!.push(v);\n\t}\n\n\tconst colors = {\n\t\terror: \"\\x1b[31m\",\n\t\twarning: \"\\x1b[33m\",\n\t\tsuggestion: \"\\x1b[34m\",\n\t\tdim: \"\\x1b[2m\",\n\t\treset: \"\\x1b[0m\",\n\t};\n\n\tfor (const [file, items] of grouped) {\n\t\tconsole.log(`\\n${file}`);\n\t\tfor (const v of items) {\n\t\t\tconst loc = v.line != null ? ` ${v.line}:${v.column ?? 0}` : \" -\";\n\t\t\tconst color = colors[v.level];\n\t\t\tconst tag = `${v.tool}/${v.code}`;\n\t\t\tconsole.log(\n\t\t\t\t`${colors.dim}${loc.padEnd(10)}${colors.reset}${color}${v.level.padEnd(12)}${colors.reset}${v.message} ${colors.dim}${tag}${colors.reset}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst counts = { error: 0, warning: 0, suggestion: 0 };\n\tfor (const v of violations) counts[v.level]++;\n\tconst parts = [];\n\tif (counts.error)\n\t\tparts.push(`${colors.error}${counts.error} error${counts.error > 1 ? \"s\" : \"\"}${colors.reset}`);\n\tif (counts.warning)\n\t\tparts.push(\n\t\t\t`${colors.warning}${counts.warning} warning${counts.warning > 1 ? \"s\" : \"\"}${colors.reset}`,\n\t\t);\n\tif (counts.suggestion)\n\t\tparts.push(\n\t\t\t`${colors.suggestion}${counts.suggestion} suggestion${counts.suggestion > 1 ? \"s\" : \"\"}${colors.reset}`,\n\t\t);\n\tif (parts.length > 0) {\n\t\tconsole.log(`\\n${parts.join(\", \")}`);\n\t} else {\n\t\tconsole.log(\"\\nNo issues found.\");\n\t}\n}\n\n// -- Main --\n\nasync function collectViolations(targets: string[]): Promise<Violation[]> {\n\tconst results = await Promise.allSettled([\n\t\trunOxlint(targets),\n\t\trunPublint(),\n\t\trunKnip(),\n\t\trunTypeScript(targets),\n\t]);\n\n\tconst violations: Violation[] = [];\n\tfor (const result of results) {\n\t\tif (result.status === \"fulfilled\") {\n\t\t\tviolations.push(...result.value);\n\t\t} else {\n\t\t\tconsole.error(result.reason);\n\t\t}\n\t}\n\treturn violations;\n}\n\nexport async function lint(ctx: CommandContext) {\n\tconst args = parse(ctx.args, {\n\t\tboolean: [\"fix\"],\n\t});\n\tconst targets = args._.length > 0 ? args._.map(String) : [\"./src\"];\n\n\tif (args.fix) {\n\t\tawait runOxlint(targets, true);\n\n\t\t// Report remaining\n\t\tconst remaining = await collectViolations(targets);\n\t\tif (remaining.length > 0) {\n\t\t\tprintViolations(remaining);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(\"No issues found.\");\n\t\treturn;\n\t}\n\n\t// Default: report only\n\tconst violations = await collectViolations(targets);\n\tprintViolations(violations);\n\tif (violations.some((v) => v.level === \"error\")) {\n\t\tprocess.exit(1);\n\t}\n}\n"],"mappings":"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"}