Skip to content

Commit fe5b250

Browse files
Adam Spiersrekram1-node
authored andcommitted
fix: stop false positives for @file references (anomalyco#2198)
This is a @valid/path/to/a/file, and it should also match at the beginning of a line: @another-valid/path/to/a/file but references to email addresses and other strings are not: - Adds a "Co-authored-by:" footer which clarifies which AI agent helped create this commit, using an appropriate `noreply@...` email address. However currently the fileRegex is `/@([^\s]+)/g` which is much too inclusive and triggers false positives. As a first approximation, we can use a negative look-behind assertion to filter out any matches for which the `@` is immediately preceded by an alphanumeric character, and then also impose some heuristics regarding use of backticks, commands, periods, and whitespace. This can be seen as functional at: https://regex101.com/r/F6KAgH/5 Fixes anomalyco#2198.
1 parent bee6a64 commit fe5b250

2 files changed

Lines changed: 106 additions & 2 deletions

File tree

‎packages/opencode/src/session/file-reference.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import path from "path"
33

44
/**
55
* Regular expression to match @ file references in text
6+
* Matches @ followed by file paths, excluding commas, periods at end of sentences, and backticks
7+
* Does not match when preceded by word characters or backticks (to avoid email addresses and quoted references)
68
*/
7-
export const fileRegex = /@([^\s]+)/g
9+
export const fileRegex = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
810

911
/**
1012
* File part type for chat input
@@ -32,7 +34,7 @@ export function processFileReferences(template: string, basePath: string): FileP
3234
const filename = match[1]
3335
const filepath = filename.startsWith("~/")
3436
? path.join(os.homedir(), filename.slice(2))
35-
: path.join(basePath, filename)
37+
: path.resolve(basePath, filename)
3638

3739
parts.push({
3840
type: "file",
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { describe, expect, test, beforeAll, mock } from "bun:test"
2+
3+
describe("processFileReferences", () => {
4+
let result: any
5+
6+
beforeAll(async () => {
7+
mock.module("os", () => ({ default: { homedir: () => "/home/fake-user" } }))
8+
const { processFileReferences } = await import("../../src/session/file-reference")
9+
const template = `This is a @valid/path/to/a/file and it should also match at
10+
the beginning of a line:
11+
12+
@another-valid/path/to/a/file
13+
14+
but this is not:
15+
16+
- Adds a "Co-authored-by:" footer which clarifies which AI agent
17+
helped create this commit, using an appropriate \`noreply@...\`
18+
or \`[email protected]\` email address.
19+
20+
We also need to deal with files followed by @commas, ones
21+
with @file-extensions.md, even @multiple.extensions.bak,
22+
hidden directorys like @.config/ or files like @.bashrc
23+
and ones at the end of a sentence like @foo.md.
24+
25+
Also shouldn't forget @/absolute/paths.txt with and @/without/extensions,
26+
as well as @~/home-files and @~/paths/under/home.txt.
27+
28+
If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.`
29+
result = processFileReferences(template, "/base")
30+
})
31+
32+
test("should extract exactly 12 file references", () => {
33+
expect(result.length).toBe(12)
34+
})
35+
36+
test("all files should have correct type and mime", () => {
37+
result.forEach((file: any) => {
38+
expect(file.type).toBe("file")
39+
expect(file.mime).toBe("text/plain")
40+
})
41+
})
42+
43+
test("should extract valid/path/to/a/file", () => {
44+
expect(result[0].filename).toBe("valid/path/to/a/file")
45+
expect(result[0].url).toBe("file:///base/valid/path/to/a/file")
46+
})
47+
48+
test("should extract another-valid/path/to/a/file", () => {
49+
expect(result[1].filename).toBe("another-valid/path/to/a/file")
50+
expect(result[1].url).toBe("file:///base/another-valid/path/to/a/file")
51+
})
52+
53+
test("should extract paths ignoring comma after", () => {
54+
expect(result[2].filename).toBe("commas")
55+
expect(result[2].url).toBe("file:///base/commas")
56+
})
57+
58+
test("should extract a path with a file extension and comma after", () => {
59+
expect(result[3].filename).toBe("file-extensions.md")
60+
expect(result[3].url).toBe("file:///base/file-extensions.md")
61+
})
62+
63+
test("should extract a path with multiple dots and comma after", () => {
64+
expect(result[4].filename).toBe("multiple.extensions.bak")
65+
expect(result[4].url).toBe("file:///base/multiple.extensions.bak")
66+
})
67+
68+
test("should extract hidden directory", () => {
69+
expect(result[5].filename).toBe(".config/")
70+
expect(result[5].url).toBe("file:///base/.config")
71+
})
72+
73+
test("should extract hidden file", () => {
74+
expect(result[6].filename).toBe(".bashrc")
75+
expect(result[6].url).toBe("file:///base/.bashrc")
76+
})
77+
78+
test("should extract a file ignoring period at end of sentence", () => {
79+
expect(result[7].filename).toBe("foo.md")
80+
expect(result[7].url).toBe("file:///base/foo.md")
81+
})
82+
83+
test("should extract an absolute path with an extension", () => {
84+
expect(result[8].filename).toBe("/absolute/paths.txt")
85+
expect(result[8].url).toBe("file:///absolute/paths.txt")
86+
})
87+
88+
test("should extract an absolute path without an extension", () => {
89+
expect(result[9].filename).toBe("/without/extensions")
90+
expect(result[9].url).toBe("file:///without/extensions")
91+
})
92+
93+
test("should extract an absolute path in home directory", () => {
94+
expect(result[10].filename).toBe("~/home-files")
95+
expect(result[10].url).toBe("file:///home/fake-user/home-files")
96+
})
97+
98+
test("should extract an absolute path under home directory", () => {
99+
expect(result[11].filename).toBe("~/paths/under/home.txt")
100+
expect(result[11].url).toBe("file:///home/fake-user/paths/under/home.txt")
101+
})
102+
})

0 commit comments

Comments
 (0)