cspell-gitignore
Version:
Gitignore Glob matcher for cspell
125 lines • 3.86 kB
JavaScript
import { promises as fs } from 'node:fs';
import * as path from 'node:path';
import { GlobMatcher } from 'cspell-glob';
import { isDefined, isParentOf, makeRelativeTo } from './helpers.js';
/**
* Represents an instance of a .gitignore file.
*/
export class GitIgnoreFile {
matcher;
gitignore;
constructor(matcher, gitignore) {
this.matcher = matcher;
this.gitignore = gitignore;
}
get root() {
return this.matcher.root;
}
isIgnored(file) {
return this.matcher.match(file);
}
isIgnoredEx(file) {
const m = this.matcher.matchEx(file);
const { matched } = m;
const partial = m;
const pattern = partial.pattern;
const glob = pattern?.rawGlob ?? partial.glob;
const root = partial.root;
const line = pattern?.line;
return { glob, matched, gitIgnoreFile: this.gitignore, root, line };
}
getGlobPatters() {
return this.matcher.patterns;
}
getGlobs(relativeTo) {
return this.getGlobPatters()
.map((pat) => globToString(pat, relativeTo))
.filter(isDefined);
}
static parseGitignore(content, gitignoreFilename) {
const options = { root: path.dirname(gitignoreFilename) };
const globs = content
.split(/\r?\n/g)
.map((glob, index) => ({
glob: glob.replace(/^#.*/, ''),
source: gitignoreFilename,
line: index + 1,
}))
.filter((g) => !!g.glob);
const globMatcher = new GlobMatcher(globs, options);
return new GitIgnoreFile(globMatcher, gitignoreFilename);
}
static async loadGitignore(gitignore) {
gitignore = path.resolve(gitignore);
const content = await fs.readFile(gitignore, 'utf8');
return this.parseGitignore(content, gitignore);
}
}
/**
* A collection of nested GitIgnoreFiles to be evaluated from top to bottom.
*/
export class GitIgnoreHierarchy {
gitIgnoreChain;
constructor(gitIgnoreChain) {
this.gitIgnoreChain = gitIgnoreChain;
mustBeHierarchical(gitIgnoreChain);
}
isIgnored(file) {
for (const git of this.gitIgnoreChain) {
if (git.isIgnored(file))
return true;
}
return false;
}
/**
* Check to see which `.gitignore` file ignored the given file.
* @param file - fsPath to check.
* @returns IsIgnoredExResult of the match or undefined if there was no match.
*/
isIgnoredEx(file) {
for (const git of this.gitIgnoreChain) {
const r = git.isIgnoredEx(file);
if (r.matched)
return r;
}
return undefined;
}
getGlobPatters() {
return this.gitIgnoreChain.flatMap((gf) => gf.getGlobPatters());
}
getGlobs(relativeTo) {
return this.gitIgnoreChain.flatMap((gf) => gf.getGlobs(relativeTo));
}
}
export async function loadGitIgnore(dir) {
const file = path.join(dir, '.gitignore');
try {
return await GitIgnoreFile.loadGitignore(file);
}
catch {
return undefined;
}
}
function mustBeHierarchical(chain) {
let root = '';
for (const file of chain) {
if (!file.root.startsWith(root)) {
throw new Error('Hierarchy violation - files are not nested');
}
root = file.root;
}
}
function globToString(glob, relativeTo) {
if (glob.isGlobalPattern)
return glob.glob;
if (isParentOf(glob.root, relativeTo) && glob.glob.startsWith('**/'))
return glob.glob;
const base = makeRelativeTo(glob.root, relativeTo);
if (base === undefined)
return undefined;
return (base ? base + '/' : '') + glob.glob;
}
export const __testing__ = {
mustBeHierarchical,
};
//# sourceMappingURL=GitIgnoreFile.js.map