Skip to content

Commit 2a26947

Browse files
committed
Fix code search context line parsing
1 parent 1e5b808 commit 2a26947

2 files changed

Lines changed: 109 additions & 18 deletions

File tree

common/src/util/__tests__/format-code-search.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,37 @@ describe('formatCodeSearchOutput', () => {
5454
expect(output).toContain('Found 1 matches')
5555
})
5656

57+
it('keeps hyphenated file paths intact for context lines', () => {
58+
const output = formatCodeSearchOutput(
59+
[
60+
'src/component-2-test.ts-9-const before = true',
61+
'src/component-2-test.ts:10:const match = true',
62+
'src/component-2-test.ts-11-const after = true',
63+
].join('\n'),
64+
)
65+
66+
expect(output).toBe(
67+
[
68+
'Found 1 matches',
69+
'src/component-2-test.ts:',
70+
' Line 9: const before = true',
71+
' Line 10: const match = true',
72+
' Line 11: const after = true',
73+
].join('\n'),
74+
)
75+
})
76+
77+
it('keeps hyphenated context content intact', () => {
78+
const output = formatCodeSearchOutput(
79+
[
80+
'src/component-2-test.ts-9-const before = "alpha-123-beta"',
81+
'src/component-2-test.ts:10:const match = true',
82+
].join('\n'),
83+
)
84+
85+
expect(output).toContain(' Line 9: const before = "alpha-123-beta"')
86+
})
87+
5788
it('reports zero matches for empty output', () => {
5889
expect(formatCodeSearchOutput('')).toBe('Found 0 matches')
5990
})

common/src/util/format-code-search.ts

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ export function formatCodeSearchOutput(
2323
return 'Found 0 matches'
2424
}
2525
const lines = stdout.split('\n')
26+
const knownFilePaths = collectMatchFilePaths(lines)
2627
const formatted: string[] = [
27-
`Found ${options.matchCount ?? countFormattedMatches(lines)} matches`,
28+
`Found ${options.matchCount ?? knownFilePaths.matchCount} matches`,
2829
]
2930
let currentFile: string | null = null
3031

@@ -43,9 +44,9 @@ export function formatCodeSearchOutput(
4344
// - Match lines: filename:line_number:content
4445
// - Context lines (with -A/-B/-C flags): filename-line_number-content
4546

46-
// Use regex to find the pattern: separator + digits + separator
47-
// This handles filenames with hyphens/colons by matching the line number pattern
48-
const parsedLine = parseRipgrepLine(line)
47+
// Use known match file paths to disambiguate context lines, which use
48+
// hyphens as separators and can otherwise conflict with hyphenated paths.
49+
const parsedLine = parseRipgrepLine(line, knownFilePaths.filePaths)
4950

5051
if (!parsedLine) {
5152
formatted.push(line)
@@ -76,13 +77,34 @@ export function formatCodeSearchOutput(
7677
return formatted.join('\n')
7778
}
7879

79-
function parseRipgrepLine(line: string): {
80+
function parseRipgrepLine(
81+
line: string,
82+
knownFilePaths: string[] = [],
83+
): {
8084
filePath: string
8185
lineNumber: string
8286
content: string
8387
isContext: boolean
8488
} | null {
85-
// Try match line pattern: filename:digits:content
89+
const matchLine = parseRipgrepMatchLine(line)
90+
if (matchLine) {
91+
return matchLine
92+
}
93+
94+
const contextLine = parseRipgrepContextLine(line, knownFilePaths)
95+
if (contextLine) {
96+
return contextLine
97+
}
98+
99+
return null
100+
}
101+
102+
function parseRipgrepMatchLine(line: string): {
103+
filePath: string
104+
lineNumber: string
105+
content: string
106+
isContext: false
107+
} | null {
86108
const matchLineMatch = line.match(/(.*?):(\d+):(.*)$/)
87109
if (matchLineMatch) {
88110
return {
@@ -93,23 +115,61 @@ function parseRipgrepLine(line: string): {
93115
}
94116
}
95117

96-
// Try context line pattern: filename-digits-content
97-
const contextLineMatch = line.match(/(.*?)-(\d+)-(.*)$/)
98-
if (contextLineMatch) {
118+
return null
119+
}
120+
121+
function parseRipgrepContextLine(
122+
line: string,
123+
knownFilePaths: string[],
124+
): {
125+
filePath: string
126+
lineNumber: string
127+
content: string
128+
isContext: true
129+
} | null {
130+
for (const filePath of knownFilePaths) {
131+
if (!line.startsWith(filePath + '-')) {
132+
continue
133+
}
134+
const rest = line.slice(filePath.length + 1)
135+
const lineNumberMatch = rest.match(/^(\d+)-(.*)$/)
136+
if (!lineNumberMatch) {
137+
continue
138+
}
99139
return {
100-
filePath: contextLineMatch[1],
101-
lineNumber: contextLineMatch[2],
102-
content: contextLineMatch[3],
140+
filePath,
141+
lineNumber: lineNumberMatch[1],
142+
content: lineNumberMatch[2],
103143
isContext: true,
104144
}
105145
}
106146

107-
return null
147+
const contextLineMatch = line.match(/(.*)-(\d+)-(.*)$/)
148+
return contextLineMatch
149+
? {
150+
filePath: contextLineMatch[1],
151+
lineNumber: contextLineMatch[2],
152+
content: contextLineMatch[3],
153+
isContext: true,
154+
}
155+
: null
108156
}
109157

110-
function countFormattedMatches(lines: string[]): number {
111-
return lines.filter((line) => {
112-
const parsedLine = parseRipgrepLine(line)
113-
return parsedLine && !parsedLine.isContext
114-
}).length
158+
function collectMatchFilePaths(lines: string[]): {
159+
filePaths: string[]
160+
matchCount: number
161+
} {
162+
const filePaths = new Set<string>()
163+
let matchCount = 0
164+
for (const line of lines) {
165+
const parsedLine = parseRipgrepMatchLine(line)
166+
if (parsedLine) {
167+
filePaths.add(parsedLine.filePath)
168+
matchCount += 1
169+
}
170+
}
171+
return {
172+
filePaths: [...filePaths].sort((a, b) => b.length - a.length),
173+
matchCount,
174+
}
115175
}

0 commit comments

Comments
 (0)