Skip to content

Commit a9e63d8

Browse files
committed
fix: normalize regexes
1 parent 1370e49 commit a9e63d8

6 files changed

Lines changed: 84 additions & 5 deletions

File tree

cli/git-user/git-user.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ describe("getLocalGitUser", () => {
2222
expect(getLocalGitUser()).toEqual({ name: "tomhagen" });
2323
});
2424

25+
it("returns an user with a trimmed username", () => {
26+
(execSync as jest.Mock).mockReturnValueOnce("tomhagen ");
27+
expect(getLocalGitUser()).toEqual({ name: "tomhagen" });
28+
(execSync as jest.Mock).mockReturnValueOnce("tomhagen\n\n");
29+
expect(getLocalGitUser()).toEqual({ name: "tomhagen" });
30+
});
31+
2532
it("throws an error if execSync fails", () => {
2633
(execSync as jest.Mock).mockImplementationOnce(() => {
2734
throw new Error();

cli/git-user/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function getLocalGitUser(): GitUser {
1010
if (!name) {
1111
throw new Error();
1212
}
13-
return { name };
13+
return { name: name.trim().replaceAll("\n", "") };
1414
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1515
} catch (_: unknown) {
1616
throw new Error(getRandomMessage(MessageType.NoGitConfig));

scripts/init/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function informFileCreated(
3333
`- The following crews were detected:\n${crews
3434
.map((crew) => ` - ${crew}`)
3535
.join("\n")}
36-
Please specify their members in the codefather config for CLI enforcement.`
36+
\n⚙️ Please specify their members in the codefather config for CLI enforcement.\n`
3737
);
3838
}
3939
}

shared/file-matcher/file-matcher.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,69 @@ describe("matchFilesAgainstRule", () => {
109109
const matched = matchFilesAgainstRule([file], rule.match, configRoot);
110110
expect(matched).toContain(file);
111111
});
112+
test("matches rule with leading slash", () => {
113+
const result = matchFilesAgainstRule(updatedFiles, ["/src/core/**"], root);
114+
expect(result).toEqual([resolve("project/src/core/index.ts")]);
115+
});
116+
test("matches rule with trailing slash", () => {
117+
const result = matchFilesAgainstRule(
118+
updatedFiles,
119+
["src/core/"], // Should auto-expand to src/core/**
120+
root
121+
);
122+
expect(result).toEqual([resolve("project/src/core/index.ts")]);
123+
});
124+
test("matches rule with whitespace", () => {
125+
const result = matchFilesAgainstRule(
126+
updatedFiles,
127+
[" src/core/** "],
128+
root
129+
);
130+
expect(result).toEqual([resolve("project/src/core/index.ts")]);
131+
});
132+
test("matches dotfiles", () => {
133+
const result = matchFilesAgainstRule(updatedFiles, [".env"], root);
134+
expect(result).toEqual([resolve("project/.env")]);
135+
});
136+
test("matches deeply nested file", () => {
137+
const result = matchFilesAgainstRule(
138+
updatedFiles,
139+
["src/models/searches/**"],
140+
root
141+
);
142+
expect(result).toEqual([resolve("project/src/models/searches/utils.ts")]);
143+
});
144+
test("matches pattern starting with ./", () => {
145+
const result = matchFilesAgainstRule(updatedFiles, ["./src/core/**"], root);
146+
expect(result).toEqual([resolve("project/src/core/index.ts")]);
147+
});
148+
test("matches pattern ending with /**/", () => {
149+
const result = matchFilesAgainstRule(updatedFiles, ["src/**/"], root);
150+
expect(result).toContain(resolve("project/src/core/index.ts"));
151+
expect(result).toContain(resolve("project/src/utils/helpers.ts"));
152+
expect(result).toContain(resolve("project/src/models/searches/utils.ts"));
153+
});
154+
test("matches pattern with regex metacharacters in path", () => {
155+
const specialFiles = [
156+
resolve("project/src/utils/file[1].ts"),
157+
resolve("project/src/utils/file(2).ts"),
158+
];
159+
const result = matchFilesAgainstRule(specialFiles, ["src/utils/**"], root);
160+
expect(result).toEqual(specialFiles);
161+
});
162+
test("matches hidden file in subdirectory", () => {
163+
const hiddenFile = resolve("project/src/.hidden/config.ts");
164+
const result = matchFilesAgainstRule([hiddenFile], ["src/**"], root);
165+
expect(result).toEqual([hiddenFile]);
166+
});
167+
test("matches file with unicode characters in path", () => {
168+
const unicodeFile = resolve("project/src/设计/模块.ts");
169+
const result = matchFilesAgainstRule([unicodeFile], ["src/**"], root);
170+
expect(result).toEqual([unicodeFile]);
171+
});
172+
test("matches file with spaces and parentheses in path", () => {
173+
const spacedFile = resolve("project/src/utils/my file (copy).ts");
174+
const result = matchFilesAgainstRule([spacedFile], ["src/utils/**"], root);
175+
expect(result).toEqual([spacedFile]);
176+
});
112177
});

shared/file-matcher/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ const normalizePath = (filePath: string) => filePath.replace(/\\/g, "/");
55

66
function globToRegExp(glob: string | RegExp): RegExp {
77
if (glob instanceof RegExp) return glob;
8-
const escaped = glob
8+
9+
let normalizedGlob = normalizePath(glob.trim().replace(/^\.?\/+/, ""));
10+
11+
// Auto-expand trailing slash to recursive match
12+
if (normalizedGlob.endsWith("/")) normalizedGlob += "**";
13+
14+
const escaped = normalizedGlob
915
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
1016
.replace(/\*\*/g, "§§")
1117
.replace(/\*/g, "[^/]*")
@@ -22,7 +28,7 @@ export function matchFilesAgainstRule(
2228
const regexes = rulePattern.map(globToRegExp);
2329
const normalizedRootDir = normalizePath(rootDir);
2430
return files.filter((file) => {
25-
const normalizedFile = normalizePath(file);
31+
const normalizedFile = normalizePath(file.trim());
2632
const relativePath = normalizePath(
2733
relative(normalizedRootDir, normalizedFile)
2834
);

shared/messages/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ const sharedErrors = [
5050
"𐄂 _committers_: That commit screams betrayal. _goodfellas_ heard it loud and clear.",
5151
"𐄂 _committers_: You broke something built on trust. Now it needs fixing.",
5252
"𐄂 _committers_, this act smells like ambition. Careful—ambition got Fredo killed.",
53-
"𐄂 _committers_: You messed up. Big. _goodfellas_ are already talking.",
5453
"𐄂 _committers_: You acted like a enemy. We don’t deal with enemies.",
5554
"𐄂 _committers_: That move lacked honor. Fix it or fade out.",
5655
"𐄂 _committers_: You weren’t subtle. You weren’t careful. Call _goodfella_ to clear things out.",
@@ -75,10 +74,12 @@ const messagesMap: Record<MessageType, string[]> = {
7574
],
7675
[MessageType.Error]: [
7776
"𐄂 _committers_! You need permission from my trusted associate: _goodfellas_. Nobody touches this without approval.",
77+
"𐄂 _committers_: You messed up. Big. _goodfellas_ is already talking.",
7878
...sharedErrors,
7979
],
8080
[MessageType.MultiErrors]: [
8181
"𐄂 _committers_! You need permission from my trusted associates: _goodfellas_. Nobody touches this without approval.",
82+
"𐄂 _committers_: You messed up. Big. _goodfellas_ are already talking.",
8283
...sharedErrors,
8384
],
8485
[MessageType.Warning]: [

0 commit comments

Comments
 (0)