UNPKG

clean-code-metrics

Version:
169 lines (149 loc) 4.96 kB
import fs from "fs"; import gitignore_parser from "gitignore-parser"; import node_dir from "node-dir"; // @ts-ignore no types available TODO add them import extract from "esprima-extract-comments"; import { SummaryTask } from "./types/SummaryTask"; import { TaskOccurrence, TaskFile, Config } from "./types/TaskMetrics"; import { load } from "./CCMConfig"; /** * Removes leading "*" from content, then removes leading task from content * @param content * @param task */ function asTaskComment(content: string, task: string) { content = content.trim(); if (content.startsWith("*")) { content = content.slice(1).trim(); } if (content.startsWith(task)) { return content.slice(task.length).trim(); } else { return content; } } export default class TaskMetrics { public static async createMetrics( configOverridePath?: fs.PathLike, packageJSONPath?: fs.PathLike, configPath?: fs.PathLike, ): Promise<TaskMetrics> { const config = load(configOverridePath, packageJSONPath, configPath); const metrics = new TaskMetrics(config); await metrics.update(); return metrics; } private readonly searchConfig: Config; private readonly matchPattern; private taskData: TaskData; private static matchIgnoreCase(value: string, pattern: string): boolean { return value.toUpperCase().includes(pattern.toUpperCase()); } private static matchUpperCase(value: string, pattern: string): boolean { return value.includes(pattern.toUpperCase()); } private constructor(config: Config) { this.searchConfig = config; this.matchPattern = this.searchConfig.ignoreCase ? TaskMetrics.matchIgnoreCase : TaskMetrics.matchUpperCase; this.taskData = new TaskData([], this.searchConfig); } public async allFiles(): Promise<string[]> { const winRegex = new RegExp(/\\/, "g"); const ignore = this.searchConfig.ignorePath .map((path) => fs.readFileSync(path, "utf-8")) //load ignore files .map(gitignore_parser.compile); //parse ignore files const allFiles = await node_dir.promiseFiles(this.searchConfig.basePath); //load all files const ntfsCorrected = allFiles.map((path) => path.replace(winRegex, "/")); //Corrects the path to a unix path if it is a windows path (replaces \\ with /) return ntfsCorrected.filter((file) => ignore.every((i) => i.accepts(file))); //remove ignored files } public searchTasks(file: string): TaskOccurrence[] { const data = fs.readFileSync(file).toString("utf-8"); const matches: TaskOccurrence[] = []; for (const comment of extract(data)) { const lines: string[] = comment.value.split(/\r?\n/); lines.forEach((value, line) => { for (const pattern of this.searchConfig.searchPattern) { if (this.matchPattern(value, pattern)) { const task = { file: file, lineNumber: comment.loc.start.line + line, content: asTaskComment(value.trim(), pattern.toUpperCase()), comment: value.trim(), task: pattern.toUpperCase(), }; matches.push(task); } } }); } return matches; } public get(): TaskData { return this.taskData; } public async update(): Promise<void> { this.taskData = await this.taskList(); } private async taskList(): Promise<TaskData> { const all_Files = await this.allFiles(); let allTasks: TaskOccurrence[] = []; for (const file of all_Files) { allTasks = allTasks.concat(this.searchTasks(file)); } return new TaskData(allTasks, this.searchConfig); } } class TaskData { private readonly list: TaskOccurrence[]; private readonly searchConfig: Config; constructor(list: TaskOccurrence[], searchConfig: Config) { this.list = list; this.searchConfig = { ...searchConfig }; } public getConfig(): Config { return { ...this.searchConfig } as Config; } public getRawData() { return this.list; } public taskSummary( includeEmpty = false, filter: string[] = this.searchConfig.searchPattern, ): SummaryTask[] { return this.searchConfig.searchPattern .filter((task) => filter.includes(task)) .map((task) => { return { task: task, amount: this.list.filter((occ) => occ.task === task).length, }; }) .filter((task) => task.amount || includeEmpty); } public getList(fileFilter?: string[]): TaskFile[] { const result: TaskFile[] = []; const filtered = this.list.filter( (o) => !fileFilter || fileFilter.includes(o.file), ); for (const occurrence of filtered) { let file = result.find((f) => f.file === occurrence.file); if (!file) { file = { file: occurrence.file, tasks: [] }; result.push(file); } file.tasks.push({ task: occurrence.task, lineNumber: occurrence.lineNumber, comment: occurrence.content, content: asTaskComment(occurrence.content, occurrence.task), }); } return result; } public getTasksOfType(task: string | string[]): TaskOccurrence[] { if (typeof task == "string") task = [task]; return this.list.filter((occ) => task.includes(occ.task)); } }