gather-ts
Version:
A powerful code analysis and packaging tool designed for creating AI-friendly code representations for javascript and typescript projects.
353 lines • 14.9 kB
JavaScript
"use strict";
// src/core/dependency/IgnoreHandler.ts
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IgnoreHandler = void 0;
const micromatch_1 = __importDefault(require("micromatch"));
const errors_1 = require("@/errors");
const services_1 = require("@/types/services");
class IgnoreHandler extends services_1.BaseService {
constructor(projectRoot, deps, options = {}) {
super();
this.deps = deps;
this.patterns = [];
if (!projectRoot || typeof projectRoot !== "string") {
throw new errors_1.ValidationError("Invalid project root", { projectRoot });
}
this.projectRoot = projectRoot;
this.debug = options.debug || false;
// Defer pattern loading to initialize()
if (options.extraPatterns) {
this.logDebug(`Received ${options.extraPatterns.length} extra patterns`);
this.patterns = [...options.extraPatterns];
}
}
async initialize() {
await super.initialize();
this.logDebug("Initializing IgnoreHandler");
try {
if (!this.deps.fileSystem.exists(this.projectRoot)) {
throw new errors_1.ValidationError("Project root does not exist", {
projectRoot: this.projectRoot,
});
}
await this.loadIgnorePatterns();
this.logDebug("IgnoreHandler initialization complete");
}
catch (error) {
throw new errors_1.ValidationError(`Failed to initialize IgnoreHandler: ${error instanceof Error ? error.message : String(error)}`);
}
}
cleanup() {
this.logDebug("Cleaning up IgnoreHandler");
this.patterns = [];
super.cleanup();
}
logDebug(message) {
if (this.debug) {
this.deps.logger.debug(message);
}
}
async loadIgnorePatterns(options = {}) {
const gatherTSIgnorePath = this.deps.fileSystem.joinPath(this.projectRoot, ".gather-ts-ignore");
const gitignorePath = this.deps.fileSystem.joinPath(this.projectRoot, ".gitignore");
try {
this.logDebug("Loading ignore patterns");
// Load patterns from both files
const gatherTSPatterns = await this.loadIgnoreFile(gatherTSIgnorePath);
const gitignorePatterns = options.skipGitignore
? []
: await this.loadIgnoreFile(gitignorePath);
// Process patterns from each source
const processedGatherTSPatterns = this.processPatterns(gatherTSPatterns, ".gather-ts-ignore");
const processedGitignorePatterns = this.processPatterns(gitignorePatterns, ".gitignore");
// Combine all patterns
const defaultPatterns = ["node_modules/**", ".git/**"];
this.patterns = [
...defaultPatterns,
...processedGatherTSPatterns,
...processedGitignorePatterns,
...this.patterns, // Keep any extra patterns added in constructor
];
// Remove duplicates while preserving order
this.patterns = [...new Set(this.patterns)];
if (gatherTSPatterns.length > 0 || gitignorePatterns.length > 0) {
this.deps.logger.info("\nLoaded ignore patterns from:");
if (gatherTSPatterns.length > 0) {
this.deps.logger.info(`- .gather-ts-ignore (${gatherTSPatterns.length} patterns)`);
}
if (gitignorePatterns.length > 0) {
this.deps.logger.info(`- .gitignore (${gitignorePatterns.length} patterns)`);
}
}
this.logDebug(`Total patterns loaded: ${this.patterns.length}`);
}
catch (error) {
if (error instanceof errors_1.ValidationError) {
throw error;
}
throw new errors_1.ValidationError(`Failed to load ignore patterns: ${error instanceof Error ? error.message : String(error)}`);
}
}
async loadIgnoreFile(filePath) {
try {
if (!this.deps.fileSystem.exists(filePath)) {
this.logDebug(`Ignore file not found: ${filePath}`);
return [];
}
const content = await this.deps.fileSystem.readFile(filePath, {
encoding: "utf8",
});
const patterns = content
.split("\n")
.map((line) => line.trim())
.filter((line) => line && !line.startsWith("#"));
this.logDebug(`Loaded ${patterns.length} patterns from ${filePath}`);
return patterns;
}
catch (error) {
throw new errors_1.FileSystemError(`Failed to read ignore file: ${error instanceof Error ? error.message : String(error)}`, filePath, "read");
}
}
validatePattern(pattern) {
if (typeof pattern !== "string") {
throw new errors_1.ValidationError("Invalid ignore pattern", { pattern });
}
// Remove any leading ./ from the pattern
pattern = pattern.replace(/^\.\//, "");
// Validate pattern syntax
try {
(0, micromatch_1.default)(["test"], pattern);
return pattern;
}
catch (error) {
throw new errors_1.ValidationError(`Invalid ignore pattern syntax: ${error instanceof Error ? error.message : String(error)}`, { pattern });
}
}
parsePattern(pattern) {
try {
const normalizedPattern = this.validatePattern(pattern);
return {
isValid: true,
normalizedPattern,
};
}
catch (error) {
return {
isValid: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
validatePatterns(patterns) {
this.logDebug(`Validating ${patterns.length} patterns`);
const validPatterns = [];
const errors = [];
patterns.forEach((pattern) => {
const result = this.parsePattern(pattern);
if (result.isValid && result.normalizedPattern) {
validPatterns.push(result.normalizedPattern);
}
else {
errors.push(`Invalid pattern "${pattern}": ${result.error}`);
}
});
if (errors.length > 0) {
throw new errors_1.ValidationError("Invalid patterns found", { errors });
}
return validPatterns;
}
processPatterns(patterns, source) {
this.logDebug(`Processing patterns from ${source}`);
try {
return patterns.reduce((acc, pattern) => {
pattern = this.validatePattern(pattern);
// Don't modify negation patterns
if (pattern.startsWith("!")) {
acc.push(pattern);
return acc;
}
// For directory patterns ending with /, add the directory and its contents
if (pattern.endsWith("/")) {
acc.push(pattern);
acc.push(`${pattern}**`);
return acc;
}
// For patterns that already have globs or path separators, add as-is
if (pattern.includes("*") || pattern.includes("/")) {
acc.push(pattern);
return acc;
}
// For simple patterns, add both the exact match and the **/ version
acc.push(pattern);
acc.push(`**/${pattern}`);
return acc;
}, []);
}
catch (error) {
throw new errors_1.ValidationError(`Failed to process patterns from ${source}: ${error instanceof Error ? error.message : String(error)}`, { source, patterns });
}
}
shouldIgnore(filePath) {
if (!this.isInitialized) {
throw new errors_1.ValidationError("IgnoreHandler not initialized");
}
this.validateFilePath(filePath);
try {
const relativePath = this.deps.fileSystem
.getRelativePath(this.projectRoot, filePath)
.split(this.deps.fileSystem.getDirName("/"))
.join("/");
if (relativePath.includes("node_modules")) {
return true;
}
this.logDebug(`Checking '${relativePath}'...`);
let lastMatchedPattern = null;
let wasNegated = false;
for (const pattern of this.patterns) {
try {
const isMatch = micromatch_1.default.isMatch(relativePath, pattern, {
dot: true,
matchBase: true,
});
if (isMatch) {
lastMatchedPattern = pattern;
if (pattern.startsWith("!")) {
wasNegated = true;
this.logDebug(`File included by negation pattern: '${pattern}'`);
return false;
}
this.logDebug(`File will be ignored due to pattern: '${pattern}'`);
return true;
}
}
catch (error) {
this.deps.logger.warn(`Pattern matching error for '${pattern}': ${error instanceof Error ? error.message : String(error)}`);
continue;
}
}
if (lastMatchedPattern) {
this.logDebug(wasNegated
? `File included by negation pattern: '${lastMatchedPattern}'`
: `File matched pattern: '${lastMatchedPattern}'`);
}
return false;
}
catch (error) {
throw new errors_1.ValidationError(`Error checking ignore status: ${error instanceof Error ? error.message : String(error)}`, { filePath });
}
}
validateFilePath(filePath) {
if (!filePath) {
throw new errors_1.ValidationError("Empty file path provided");
}
try {
const absolutePath = this.deps.fileSystem.resolvePath(this.deps.fileSystem.isAbsolute(filePath) ? "" : this.projectRoot, filePath);
if (!this.deps.fileSystem.exists(absolutePath)) {
throw new errors_1.ValidationError("File does not exist", {
filePath: absolutePath,
});
}
if (!this.deps.fileSystem.isReadable(absolutePath)) {
throw new errors_1.ValidationError("File is not readable", {
filePath: absolutePath,
});
}
}
catch (error) {
if (error instanceof errors_1.ValidationError) {
throw error;
}
throw new errors_1.ValidationError(`Failed to validate file path: ${error instanceof Error ? error.message : String(error)}`, { filePath });
}
}
getPatterns() {
return [...this.patterns];
}
addPattern(pattern) {
this.logDebug(`Adding pattern: ${pattern}`);
try {
const validatedPattern = this.validatePattern(pattern);
if (!this.patterns.includes(validatedPattern)) {
this.patterns.push(validatedPattern);
this.logDebug(`Added pattern: ${validatedPattern}`);
}
}
catch (error) {
throw new errors_1.ValidationError(`Failed to add ignore pattern: ${error instanceof Error ? error.message : String(error)}`, { pattern });
}
}
removePattern(pattern) {
this.logDebug(`Removing pattern: ${pattern}`);
const index = this.patterns.indexOf(pattern);
if (index !== -1) {
this.patterns.splice(index, 1);
this.logDebug(`Removed pattern: ${pattern}`);
return true;
}
return false;
}
resetPatterns() {
this.logDebug("Resetting patterns to defaults");
this.patterns = ["node_modules/**", ".git/**"];
}
printDebugInfo(filePath) {
try {
this.validateFilePath(filePath);
const relativePath = this.deps.fileSystem
.getRelativePath(this.projectRoot, filePath)
.split(this.deps.fileSystem.getDirName("/"))
.join("/");
this.deps.logger.debug("\nDebug Information:");
this.deps.logger.debug("----------------");
this.deps.logger.debug(`File: ${relativePath}`);
this.deps.logger.debug(`Project Root: ${this.projectRoot}`);
this.deps.logger.debug("\nTesting patterns:");
const matchResults = this.patterns.map((pattern) => {
try {
const isMatch = micromatch_1.default.isMatch(relativePath, pattern, {
dot: true,
matchBase: true,
});
return {
pattern,
isMatch,
error: null,
};
}
catch (error) {
return {
pattern,
isMatch: false,
error: error instanceof Error ? error.message : String(error),
};
}
});
matchResults.forEach(({ pattern, isMatch, error }) => {
if (error) {
this.deps.logger.debug(`! ERROR: ${pattern} - ${error}`);
}
else {
this.deps.logger.debug(`${isMatch ? "✓" : "✗"} ${pattern}`);
}
});
const negationPatterns = matchResults.filter(({ pattern, isMatch }) => pattern.startsWith("!") && isMatch);
if (negationPatterns.length > 0) {
this.deps.logger.debug("\nNegation patterns matched:");
negationPatterns.forEach(({ pattern }) => {
this.deps.logger.debug(`! ${pattern}`);
});
}
const isIgnored = this.shouldIgnore(filePath);
this.deps.logger.debug("\nFinal result:");
this.deps.logger.debug(`File will be ${isIgnored ? "IGNORED" : "INCLUDED"}`);
}
catch (error) {
throw new errors_1.ValidationError(`Failed to print debug info: ${error instanceof Error ? error.message : String(error)}`, { filePath });
}
}
}
exports.IgnoreHandler = IgnoreHandler;
//# sourceMappingURL=IgnoreHandler.js.map