{"version":3,"sources":["../../../src/checkers/biome/cli.ts"],"sourcesContent":["import { exec } from 'node:child_process'\nimport { stripVTControlCharacters as strip } from 'node:util'\nimport { createFrame } from '../../codeFrame.js'\nimport type { NormalizedDiagnostic } from '../../logger.js'\nimport { normalizePath, readSources } from '../../sources.js'\nimport { DiagnosticLevel } from '../../types.js'\nimport type {\n BiomeOutput,\n Diagnostic,\n LegacyDiagnostic,\n ModernDiagnostic,\n} from './types.js'\n\nexport const severityMap = {\n error: DiagnosticLevel.Error,\n warning: DiagnosticLevel.Warning,\n info: DiagnosticLevel.Suggestion,\n information: DiagnosticLevel.Suggestion,\n} as const\n\nexport function getBiomeCommand(command: string, flags: string, files: string) {\n const defaultFlags = '--reporter json'\n if (flags.includes('--flags')) {\n throw Error(\n `vite-plugin-checker will force append \"--reporter json\" to the flags in dev mode, please don't use \"--flags\" in \"config.biome.flags\".\nIf you need to customize \"--flags\" in build mode, please use \"config.biome.build.flags\" instead.`,\n )\n }\n return ['biome', command, flags, defaultFlags, files]\n .filter(Boolean)\n .join(' ')\n}\n\nexport function runBiome(command: string, cwd: string) {\n return new Promise((resolve, _reject) => {\n exec(\n command,\n {\n cwd,\n maxBuffer: Number.POSITIVE_INFINITY,\n },\n (_error, stdout, _stderr) => {\n parseBiomeOutput(stdout, cwd)\n .then(resolve)\n .catch(() => resolve([]))\n },\n )\n })\n}\n\ntype Entry = {\n file: string\n message: string\n category: string\n severity: string\n start: { line: number; column: number }\n end: { line: number; column: number }\n /** Embedded source code from legacy Biome output (pre-2.4). */\n sourceCode?: string\n}\n\nfunction isModernDiagnostic(d: Diagnostic): d is ModernDiagnostic {\n return d.location !== undefined && typeof d.location.path === 'string'\n}\n\nfunction isLegacyDiagnostic(d: Diagnostic): d is LegacyDiagnostic {\n return (\n d.location !== undefined &&\n typeof d.location.path === 'object' &&\n d.location.path !== null &&\n 'file' in d.location.path\n )\n}\n\nfunction getEntries(parsed: BiomeOutput, cwd: string): Entry[] {\n return parsed.diagnostics.flatMap((d): Entry[] => {\n if (!d.location) return []\n\n if (isModernDiagnostic(d)) {\n return [\n {\n file: normalizePath(d.location.path, cwd),\n message: d.message,\n category: d.category ?? '',\n severity: d.severity,\n start: d.location.start,\n end: d.location.end,\n },\n ]\n }\n\n if (isLegacyDiagnostic(d)) {\n const file = d.location.path?.file ?? ''\n return [\n {\n file: normalizePath(file, cwd),\n message: d.description,\n category: d.category ?? '',\n severity: d.severity,\n start: getLineAndColumn(d.location.sourceCode, d.location.span?.[0]),\n end: getLineAndColumn(d.location.sourceCode, d.location.span?.[1]),\n sourceCode: d.location.sourceCode,\n },\n ]\n }\n\n return []\n })\n}\n\nfunction getUniqueFiles(entries: Entry[]) {\n return Array.from(new Set(entries.map((e) => e.file)))\n}\n\nfunction buildDiagnostics(\n entries: Entry[],\n sources: Map,\n): NormalizedDiagnostic[] {\n return entries.flatMap((entry) => {\n // Prefer embedded source code (legacy), fall back to disk read (modern).\n const source = entry.sourceCode ?? sources.get(entry.file)\n if (!source) return []\n\n const loc = {\n file: entry.file,\n start: entry.start,\n end: entry.end,\n }\n\n const codeFrame = createFrame(source, loc)\n\n return [\n {\n message: `[${entry.category}] ${entry.message}`,\n level:\n severityMap[entry.severity as keyof typeof severityMap] ??\n DiagnosticLevel.Error,\n checker: 'Biome',\n id: entry.file,\n codeFrame,\n stripedCodeFrame: codeFrame && strip(codeFrame),\n loc,\n },\n ]\n })\n}\n\nfunction sanitizeBiomeOutput(output: string) {\n // Biome on Windows emits unescaped backslashes in JSON path values\n return output.replace(/\\\\(?![\"\\\\/bfnrtu])/g, '\\\\\\\\')\n}\n\n/**\n * Convert a byte-offset into `text` to a 1-based line/column pair.\n * Used only for the legacy Biome schema (< 2.4) which reports positions\n * as byte offsets into the embedded `sourceCode`.\n */\nfunction getLineAndColumn(text?: string, offset?: number) {\n if (!text || !offset) return { line: 0, column: 0 }\n\n let line = 1\n let column = 1\n\n for (let i = 0; i < offset; i++) {\n if (text[i] === '\\n') {\n line++\n column = 1\n } else {\n column++\n }\n }\n\n return { line, column }\n}\n\nasync function parseBiomeOutput(\n output: string,\n cwd: string,\n): Promise {\n let parsed: BiomeOutput\n try {\n parsed = JSON.parse(sanitizeBiomeOutput(output))\n } catch {\n return []\n }\n\n const entries = getEntries(parsed, cwd)\n\n // Only read from disk for entries that don't have embedded source code.\n const filesNeedingRead = getUniqueFiles(entries.filter((e) => !e.sourceCode))\n const sourceCache = await readSources(filesNeedingRead)\n\n return buildDiagnostics(entries, sourceCache)\n}\n"],"mappings":"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","names":[]}