Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,57 @@ describe("IgnoreFilter", () => {
expect(filter.isIgnored(".idea/workspace.xml")).toBe(true);
expect(filter.isIgnored(".vscode/settings.json")).toBe(true);
});

it("does not throw on './'-prefixed paths and matches correctly", () => {
const filter = createIgnoreFilter(testDir);
expect(filter.isIgnored("./dist/index.js")).toBe(true);
expect(filter.isIgnored("./src/index.ts")).toBe(false);
});

it("treats the empty relative path (project root) as not ignored", () => {
const filter = createIgnoreFilter(testDir);
expect(filter.isIgnored("")).toBe(false);
});

it("does not throw on the input shapes that ignore@7 rejects", () => {
const filter = createIgnoreFilter(testDir);
// These are the previously-throwing inputs from the PR body. The whole
// point of the fix is that they return a boolean rather than throw.
expect(() => filter.isIgnored("")).not.toThrow();
expect(() => filter.isIgnored("./")).not.toThrow();
expect(() => filter.isIgnored("./dist/index.js")).not.toThrow();
expect(() => filter.isIgnored("././foo.log")).not.toThrow();
expect(() => filter.isIgnored(".//foo.log")).not.toThrow();
expect(() => filter.isIgnored("../escapes/root.ts")).not.toThrow();
expect(() => filter.isIgnored("dist\\index.js")).not.toThrow();
expect(() => filter.isIgnored(".\\dist\\index.js")).not.toThrow();
});

it("treats a bare './' (trim-to-empty / project root) as not ignored", () => {
const filter = createIgnoreFilter(testDir);
expect(filter.isIgnored("./")).toBe(false);
});

it("collapses repeated leading './' and duplicate slash runs", () => {
const filter = createIgnoreFilter(testDir);
expect(filter.isIgnored("././dist/index.js")).toBe(true);
expect(filter.isIgnored(".//dist/index.js")).toBe(true);
expect(filter.isIgnored("./src//index.ts")).toBe(false);
});

it("normalizes Windows backslash separators", () => {
const filter = createIgnoreFilter(testDir);
expect(filter.isIgnored("dist\\index.js")).toBe(true);
expect(filter.isIgnored(".\\dist\\index.js")).toBe(true);
expect(filter.isIgnored("src\\index.ts")).toBe(false);
});

it("treats out-of-root '../' paths as not ignored instead of throwing", () => {
const filter = createIgnoreFilter(testDir);
expect(filter.isIgnored("../foo")).toBe(false);
expect(filter.isIgnored("..")).toBe(false);
expect(filter.isIgnored("..\\foo")).toBe(false);
});
});

describe("createIgnoreFilter with user .understandignore", () => {
Expand Down
28 changes: 26 additions & 2 deletions understand-anything-plugin/packages/core/src/ignore-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,16 @@ export const DEFAULT_IGNORE_PATTERNS: string[] = [
];

export interface IgnoreFilter {
/** Returns true if the given relative path should be excluded from analysis. */
/**
* Returns true if the given path should be excluded from analysis.
*
* Accepts a root-relative path. Inputs are normalized before delegating to
* ignore@7 (which throws on anything that isn't a clean `path.relative()`d
* string): backslashes are converted to `/`, any leading `./` segments and
* duplicate slashes are collapsed, and an empty path (the project root)
* returns false. A path that escapes the root (`../...`) is treated as
* not-ignored (returns false) rather than throwing.
*/
isIgnored(relativePath: string): boolean;
}

Expand Down Expand Up @@ -105,7 +114,22 @@ export function createIgnoreFilter(projectRoot: string): IgnoreFilter {

return {
isIgnored(relativePath: string): boolean {
return ig.ignores(relativePath);
if (!relativePath) return false;
// ignore@7's ig.ignores() throws on any input that isn't a clean,
// root-relative POSIX path, so normalize the shapes path.relative() can
// produce before delegating. Production callers always pass
// toPosix(relative(root, target)), but normalizing here keeps the
// function total against the other shapes too.
const normalized = relativePath
.replace(/\\/g, "/") // Windows separators -> POSIX
.replace(/^(\.\/)+/, "") // collapse one-or-more leading "./"
.replace(/\/{2,}/g, "/") // collapse duplicate slash runs
.replace(/^\/+/, ""); // drop any leading root slash
if (!normalized) return false; // empty path == project root, not ignored
// A path that escapes the root can't be under analysis; report not-ignored
// instead of letting ig.ignores() throw on the "../" prefix.
if (normalized === ".." || normalized.startsWith("../")) return false;
return ig.ignores(normalized);
},
};
}
Loading