UNPKG

code-complexity

Version:

Measure the churn/complexity score. Higher values mean hotspots where refactorings should happen.

96 lines (80 loc) 2.63 kB
import { execSync } from "node:child_process"; import { existsSync } from "node:fs"; import { resolve } from "node:path"; import * as micromatch from "micromatch"; import { buildDebugger, withDuration } from "../../utils"; import { Options, Path } from "../types"; const internal = { debug: buildDebugger("churn") }; const PER_LINE = "\n"; export default { compute: (...args: any[]): Promise<Map<Path, number>> => withDuration(compute, args, internal.debug), }; async function compute(options: Options): Promise<Map<Path, number>> { const gitLogCommand = buildGitLogCommand(options); const singleStringWithAllChurns = executeGitLogCommand(gitLogCommand); return computeChurnsPerFiles( singleStringWithAllChurns, options.directory, options.filter ); } function executeGitLogCommand(gitLogCommand: string): string { return execSync(gitLogCommand, { encoding: "utf8", maxBuffer: 32_000_000 }); } function buildGitLogCommand(options: Options): string { const isWindows = process.platform === "win32"; return [ "git", `-C ${options.directory}`, `log`, `--follow`, // Windows CMD handle quotes differently than linux, this is why we should put empty string as said in: // https://github.com/git-for-windows/git/issues/3131 `--format=${isWindows ? "" : "''"}`, `--name-only`, options.since ? `--since="${options.since}"` : "", options.until ? `--until="${options.until}"` : "", // Windows CMD handle quotes differently isWindows ? "*" : "'*'", ] .filter((s) => s.length > 0) .join(" "); } function computeChurnsPerFiles( gitLogOutput: string, directory: string, filters: string[] | undefined ): Map<Path, number> { const changedFiles = gitLogOutput .split(PER_LINE) .filter((line) => line !== "") .sort(); return changedFiles.reduce((map: Map<Path, number>, path) => { applyFiltersAndExcludeObsoletePath(path, map); return map; }, new Map()); function applyFiltersAndExcludeObsoletePath( path: string, map: Map<Path, number> ) { if (!filters || !filters.length) { if (pathStillExists(path)) { addOrIncrement(map, path); } } else { const pathHasAMatch = filters.every((f) => micromatch.isMatch(path, f)); if (pathHasAMatch) { if (pathStillExists(path)) { addOrIncrement(map, path); } } } } function addOrIncrement(map: Map<Path, number>, path: string) { map.set(path, (map.get(path) ?? 0) + 1); } function pathStillExists(fileName: string) { return existsSync(resolve(directory, fileName)); } }