/** `.gitattributes` parsing shared by the editor's LFS guard and the language breakdown. */
const REGEX_SPECIAL = /[.+^${}()|\\]/;
/** gitattributes/fnmatch globs: `*` and `?` stay within a segment, `**` spans them, `[...]` is a class. */
export function attributeGlob(pattern: string): RegExp {
let source = "";
for (let index = 0; index < pattern.length; index++) {
const char = pattern[index];
if (char === "*" && pattern[index + 1] === "*") {
const slash = pattern[index + 2] === "/";
source += slash ? "(?:.*/)?" : ".*";
index += slash ? 2 : 1;
} else if (char === "*") source += "[^/]*";
else if (char === "?") source += "[^/]";
else if (char === "[") {
const close = pattern.indexOf("]", index + 2);
if (close === -1) {
source += "\\[";
continue;
}
const body = pattern
.slice(index + 1, close)
.replace(/^!/, "^")
.replace(/\\/g, "\\\\");
source += `[${body}]`;
index = close;
} else if (char === "\\" && index + 1 < pattern.length) {
index++;
source += pattern[index].replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
} else source += REGEX_SPECIAL.test(char) || char === "]" ? `\\${char}` : char;
}
return new RegExp(`^${source}$`);
}
/** A value set by a rule: `attr` is true, `-attr` false, `!attr` unset (null), `attr=value` the value. */
export type AttributeValue = string | boolean | null;
export type AttributeRule = { regex: RegExp; anchored: boolean; attributes: Map<string, AttributeValue> };
/** Rules in file order (later ones win, as in git); macros and nested `.gitattributes` files are not read. */
export function parseAttributes(text: string, maxRules: number): AttributeRule[] {