Skip to content

Instantly share code, notes, and snippets.

@alkimiadev
Created August 27, 2025 09:35
Show Gist options
  • Select an option

  • Save alkimiadev/1f8f23c90c808825951b90b7757d952a to your computer and use it in GitHub Desktop.

Select an option

Save alkimiadev/1f8f23c90c808825951b90b7757d952a to your computer and use it in GitHub Desktop.
Useful script for analyzing deno lint output. It includes utilities for grouping by code, file, search patterns, and so on.
// deno_package/scripts/analyze_lint.ts
import { parse } from "https://deno.land/std@0.224.0/flags/mod.ts";
import * as path from "https://deno.land/std@0.224.0/path/mod.ts";
interface LintRange {
start: { line: number; col: number; bytePos: number };
end: { line: number; col: number; bytePos: number };
}
interface LintDiagnostic {
filename: string;
range: LintRange;
message: string;
code: string;
hint: string;
}
interface LintResult {
version: number;
diagnostics: LintDiagnostic[];
errors: unknown[];
checkedFiles: string[];
}
interface FilterOptions {
codes?: string[];
files?: string[];
groupBy?: "code" | "file";
}
interface Stats {
total: number;
byCode: Record<string, number>;
byFile: Record<string, number>;
filesWithIssues: number;
}
function filterDiagnostics(
diagnostics: LintDiagnostic[],
options: FilterOptions
): LintDiagnostic[] {
let result = diagnostics;
if (options.codes) {
const codes = new Set(options.codes);
result = result.filter(d => codes.has(d.code));
}
if (options.files) {
const filePatterns = options.files.map(f => new RegExp(f));
result = result.filter(d =>
filePatterns.some(pattern => pattern.test(d.filename))
);
}
return result;
}
function groupDiagnostics(
diagnostics: LintDiagnostic[],
groupBy: "code" | "file"
): Record<string, LintDiagnostic[]> {
const groups: Record<string, LintDiagnostic[]> = {};
for (const diag of diagnostics) {
const key = groupBy === "code" ? diag.code : diag.filename;
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(diag);
}
return groups;
}
function calculateStats(diagnostics: LintDiagnostic[]): Stats {
const byCode: Record<string, number> = {};
const byFile: Record<string, number> = {};
for (const diag of diagnostics) {
byCode[diag.code] = (byCode[diag.code] || 0) + 1;
byFile[diag.filename] = (byFile[diag.filename] || 0) + 1;
}
return {
total: diagnostics.length,
byCode,
byFile,
filesWithIssues: Object.keys(byFile).length
};
}
function printStats(stats: Stats, topN: number = 10) {
console.log("\n=== LINT ISSUE STATISTICS ===");
console.log(`Total issues: ${stats.total}`);
console.log(`Files with issues: ${stats.filesWithIssues}`);
console.log(`\nTop ${topN} issue types:`);
const sortedByCode = Object.entries(stats.byCode).sort((a, b) => b[1] - a[1]);
for (let i = 0; i < Math.min(topN, sortedByCode.length); i++) {
const [code, count] = sortedByCode[i];
console.log(` ${code}: ${count}`);
}
console.log(`\nTop ${topN} files with most issues:`);
const sortedByFile = Object.entries(stats.byFile).sort((a, b) => b[1] - a[1]);
for (let i = 0; i < Math.min(topN, sortedByFile.length); i++) {
const [file, count] = sortedByFile[i];
console.log(` ${path.basename(file)}: ${count}`);
}
}
function printGroupedDiagnostics(
groups: Record<string, LintDiagnostic[]>,
groupBy: "code" | "file",
limit?: number
) {
const sortedEntries = Object.entries(groups).sort(
(a, b) => b[1].length - a[1].length
);
const entriesToShow = limit ? sortedEntries.slice(0, limit) : sortedEntries;
for (const [key, diagnostics] of entriesToShow) {
console.log(`\n${groupBy.toUpperCase()}: ${key} (${diagnostics.length} issues)`);
// Show first 5 issues for each group to avoid overwhelming output
const issuesToShow = Math.min(5, diagnostics.length);
for (let i = 0; i < issuesToShow; i++) {
const diag = diagnostics[i];
console.log(
` ${path.basename(diag.filename)}:${diag.range.start.line + 1}:${diag.range.start.col + 1} - ${diag.message}`
);
}
if (diagnostics.length > issuesToShow) {
console.log(` ... and ${diagnostics.length - issuesToShow} more issues`);
}
}
if (limit && sortedEntries.length > limit) {
console.log(`\n... and ${sortedEntries.length - limit} more groups`);
}
}
async function runDenoLint(): Promise<LintResult> {
const command = new Deno.Command(Deno.execPath(), {
args: ["lint", "--json"],
stdout: "piped",
stderr: "piped",
});
const { code, stdout, stderr } = await command.output();
if (code !== 0 && code !== 1) { // Deno lint returns 1 when there are lint issues
const errorOutput = new TextDecoder().decode(stderr);
throw new Error(`Lint command failed:\n${errorOutput}`);
}
const output = new TextDecoder().decode(stdout);
return JSON.parse(output);
}
async function main() {
const args = parse(Deno.args, {
alias: {
f: "file",
c: "code",
g: "group",
h: "help",
s: "stats",
l: "limit"
},
string: ["file", "code", "group"],
boolean: ["help", "stats"],
number: ["limit"],
default: { limit: 0 } // 0 means no limit
});
if (args.help) {
console.log(`
Usage: deno run analyze_lint.ts [options] [lint-output.json]
Options:
-f, --file <pattern> Filter by file path pattern (regex, can be used multiple times)
-c, --code <codes> Filter by comma-separated lint codes
-g, --group <type> Group by "code" or "file"
-s, --stats Show statistics summary
-l, --limit <number> Limit number of groups to display when grouping
--help Show this help message
Examples:
deno run analyze_lint.ts -c no-unused-vars
deno run analyze_lint.ts -f ".*\\.ts" -f ".*\\.tsx" -g file
deno run analyze_lint.ts --code=no-explicit-any,verbatim-module-syntax
deno run analyze_lint.ts --stats
deno run analyze_lint.ts --group code --limit 5
`);
return;
}
let lintResult: LintResult;
// Read from file or run lint command
if (args._.length > 0) {
const filePath = String(args._[0]);
const content = await Deno.readTextFile(filePath);
lintResult = JSON.parse(content);
} else {
lintResult = await runDenoLint();
}
// Apply filters
const filterOptions: FilterOptions = {};
if (args.code) {
filterOptions.codes = args.code.split(",").map(c => c.trim());
}
if (args.file) {
// Handle multiple file patterns
filterOptions.files = Array.isArray(args.file)
? args.file
: [args.file];
}
const filteredDiagnostics = filterDiagnostics(
lintResult.diagnostics,
filterOptions
);
// Show statistics if requested
if (args.stats) {
const stats = calculateStats(filteredDiagnostics);
printStats(stats);
}
// Group or show all diagnostics
if (args.group) {
const groups = groupDiagnostics(filteredDiagnostics, args.group as "code" | "file");
printGroupedDiagnostics(groups, args.group as "code" | "file", args.limit || undefined);
} else if (!args.stats) {
// Only show JSON output if neither stats nor grouping is requested
console.log(JSON.stringify({ diagnostics: filteredDiagnostics }, null, 2));
}
if (!args.stats) {
console.log(`\nFound ${filteredDiagnostics.length} issues matching criteria`);
}
}
if (import.meta.main) {
main().catch(console.error);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment