Skip to content

Commit d3638a1

Browse files
committed
feat(utils): return file content alongside filePath in collectFiles
- Updated collectFiles to return `{ filePath, content }[]` instead of string paths - Optimized conflict detection to avoid reading files twice - Added FileEntry type to represent collected files - Updated unit tests to assert both file paths and file contents
1 parent 0924461 commit d3638a1

File tree

2 files changed

+42
-30
lines changed

2 files changed

+42
-30
lines changed

lib/src/utils.test.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,15 @@ describe("collectFiles", () => {
4848
fileFilter: file => file.endsWith(".json"),
4949
});
5050

51-
expect(files).toContain(conflictedFile);
52-
expect(files).not.toContain(cleanFile);
53-
expect(files).not.toContain(ignoredFile);
51+
const paths = files.map(f => f.filePath);
52+
53+
expect(paths).toContain(conflictedFile);
54+
expect(paths).not.toContain(cleanFile);
55+
expect(paths).not.toContain(ignoredFile);
56+
57+
// check content as well
58+
const conflicted = files.find(f => f.filePath === conflictedFile);
59+
expect(conflicted?.content).toContain("<<<<<<<");
5460
});
5561

5662
it("collects conflicted + clean files when includeNonConflicted is true", async () => {
@@ -60,9 +66,14 @@ describe("collectFiles", () => {
6066
includeNonConflicted: true,
6167
});
6268

63-
expect(files).toContain(conflictedFile);
64-
expect(files).toContain(cleanFile);
65-
expect(files).not.toContain(ignoredFile);
69+
const paths = files.map(f => f.filePath);
70+
71+
expect(paths).toContain(conflictedFile);
72+
expect(paths).toContain(cleanFile);
73+
expect(paths).not.toContain(ignoredFile);
74+
75+
const clean = files.find(f => f.filePath === cleanFile);
76+
expect(clean?.content).toContain('"value": 42');
6677
});
6778

6879
it("skips files that do not match fileFilter", async () => {
@@ -71,6 +82,7 @@ describe("collectFiles", () => {
7182
fileFilter: file => file.endsWith(".json"),
7283
});
7384

74-
expect(files).not.toContain(ignoredFile);
85+
const paths = files.map(f => f.filePath);
86+
expect(paths).not.toContain(ignoredFile);
7587
});
7688
});

lib/src/utils.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
import fs from "node:fs/promises";
22
import path from "node:path";
33

4+
export interface FileEntry {
5+
filePath: string;
6+
content: string;
7+
}
8+
49
/**
510
* Checks whether the given file contains Git merge conflict markers.
611
*
7-
* @param filePath - Absolute path to the file.
12+
* @param content - File content to check.
813
* @returns `true` if conflict markers exist, otherwise `false`.
914
*/
10-
const hasConflict = async (filePath: string): Promise<boolean> => {
11-
try {
12-
const content = await fs.readFile(filePath, "utf8");
13-
return (
14-
content.includes("<<<<<<<") && content.includes("=======") && content.includes(">>>>>>>")
15-
);
16-
} catch {
17-
/* v8 ignore next 2 - If file cannot be read (permissions, etc.), treat as non-conflicted */
18-
return false;
19-
}
15+
const hasConflict = (content: string): boolean => {
16+
return content.includes("<<<<<<<") && content.includes("=======") && content.includes(">>>>>>>");
2017
};
2118

2219
export interface CollectFilesOptions {
@@ -37,16 +34,15 @@ export interface CollectFilesOptions {
3734
* Recursively collects files that match the provided `fileFilter`.
3835
*
3936
* - By default, only conflicted files are returned.
40-
* - If `includeNonConflicted` is enabled, matching files are always included
41-
* (conflict check is skipped).
37+
* - If `includeNonConflicted` is enabled, matching files are always included.
4238
*
4339
* @param options - Collection options, including `fileFilter` and traversal root.
44-
* @returns A promise that resolves with an array of matching file paths.
40+
* @returns A promise that resolves with an array of `{ filePath, content }`.
4541
*/
46-
export const collectFiles = async (options: CollectFilesOptions): Promise<string[]> => {
42+
export const collectFiles = async (options: CollectFilesOptions): Promise<FileEntry[]> => {
4743
const { root = process.cwd(), fileFilter, includeNonConflicted = false } = options;
4844

49-
const allFiles: string[] = [];
45+
const collected: FileEntry[] = [];
5046

5147
/**
5248
* Recursively traverses a directory, checking each file against
@@ -64,17 +60,21 @@ export const collectFiles = async (options: CollectFilesOptions): Promise<string
6460
/* v8 ignore next */
6561
await walk(fullPath);
6662
} else if (fileFilter(fullPath)) {
67-
if (includeNonConflicted) {
68-
allFiles.push(fullPath);
69-
} else if (await hasConflict(fullPath)) {
70-
allFiles.push(fullPath);
71-
} else {
72-
console.info(`Skipped (no conflicts): ${fullPath}`);
63+
try {
64+
const content = await fs.readFile(fullPath, "utf8");
65+
66+
if (includeNonConflicted || hasConflict(content)) {
67+
collected.push({ filePath: fullPath, content });
68+
} else {
69+
console.info(`Skipped (no conflicts): ${fullPath}`);
70+
}
71+
} catch {
72+
console.warn(`Skipped (unreadable): ${fullPath}`);
7373
}
7474
}
7575
}
7676
};
7777

7878
await walk(root);
79-
return allFiles;
79+
return collected;
8080
};

0 commit comments

Comments
 (0)