/** * Code Style › Formatter: "Do not format" globs and formatter on/off markers * in code comments (`@formatter:off` … `@formatter:on`). Reformat Code, format * on save and the before-commit reformat skip excluded files and leave the * text between markers untouched. */ import { editorPrefs } from "./editorSettings"; export interface FormatGuardOptions { doNotFormat: readonly string[]; formatterTagsEnabled: boolean; formatterOffTag: string; formatterOnTag: string; formatterTagsRegexp: boolean; } export interface TextChangeLike { from: number; to: number; insert: string; } /** The Formatter options of the effective settings. */ export function formatGuardOptions(settings?: unknown): FormatGuardOptions { const cs = editorPrefs(settings).codeStyle; return { doNotFormat: cs.doNotFormat, formatterTagsEnabled: cs.formatterTagsEnabled, formatterOffTag: cs.formatterOffTag, formatterOnTag: cs.formatterOnTag, formatterTagsRegexp: cs.formatterTagsRegexp, }; } function globRegExp(glob: string): RegExp { let re = ""; for (let i = 0; i < glob.length; i++) { const c = glob[i]!; if (c === "*" && glob[i + 1] === "*") { re += ".*"; i++; if (glob[i + 1] === "/") i++; } else if (c === "*") re += "[^/]*"; else if (c === "?") re += "[^/]"; else re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&"); } return new RegExp(`^${re}$`); } /** * Whether "Do not format" excludes `path`. A pattern without `/` matches the * file name or any folder name (`*.min.js`, `generated`); one with `/` matches * the project-relative path or a folder containing it (`src/gen/**`, `vendor/`). */ export function formatExcluded(path: string, root: string | null, globs: readonly string[]): boolean { const rel = root && path.startsWith(root + "/") ? path.slice(root.length + 1) : path; const segments = rel.split("/").filter(Boolean); for (const raw of globs) { const glob = raw.trim().replace(/^\.\//, ""); if (!glob) continue; if (!glob.includes("/")) { const re = globRegExp(glob); if (segments.some((s) => re.test(s))) return true; continue; } const re = globRegExp(glob.replace(/^\/+/, "").replace(/\/+$/, "")); for (let i = segments.length; i > 0; i--) if (re.test(segments.slice(0, i).join("/"))) return true; } return false; } /** A comment opener before the marker on its line (`//`, `#`, `/*`, `