mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add case sensitivity debug logging to search algorithm
- Add detection for case mismatches when search fails - Log case sensitivity issues to console for AI debugging - Show potential case mismatch count in error messages - Add comprehensive tests for case sensitivity detection Fixes #4731
This commit is contained in:
parent
cc369da2d5
commit
4e31af0591
3 changed files with 262 additions and 2 deletions
|
|
@ -781,6 +781,178 @@ function five() {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("case sensitivity detection", () => {
|
||||
let strategy: MultiSearchReplaceDiffStrategy
|
||||
|
||||
beforeEach(() => {
|
||||
strategy = new MultiSearchReplaceDiffStrategy()
|
||||
})
|
||||
|
||||
it("should detect case mismatch when search content differs from file content only by case", async () => {
|
||||
const originalContent = `function getData() {
|
||||
const originalStructRVA = 0;
|
||||
return originalStructRVA;
|
||||
}`
|
||||
const diffContent =
|
||||
"<<<<<<< SEARCH\n" +
|
||||
"function getData() {\n" +
|
||||
" const originalStructRva = 0;\n" +
|
||||
" return originalStructRva;\n" +
|
||||
"}\n" +
|
||||
"=======\n" +
|
||||
"function getData() {\n" +
|
||||
" const newValue = 0;\n" +
|
||||
" return newValue;\n" +
|
||||
"}\n" +
|
||||
">>>>>>> REPLACE"
|
||||
|
||||
const result = await strategy.applyDiff(originalContent, diffContent)
|
||||
expect(result.success).toBe(false)
|
||||
// When there's a single diff block, the error is in result.error, not failParts
|
||||
if (!result.success && result.error) {
|
||||
expect(result.error).toContain("No sufficiently similar match found")
|
||||
expect(result.error).toContain("Potential case mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
it("should not show case mismatch warning when content is genuinely identical", async () => {
|
||||
const originalContent = `function test() {
|
||||
return true;
|
||||
}`
|
||||
const diffContent =
|
||||
"<<<<<<< SEARCH\n" +
|
||||
"function test() {\n" +
|
||||
" return true;\n" +
|
||||
"}\n" +
|
||||
"=======\n" +
|
||||
"function test() {\n" +
|
||||
" return true;\n" +
|
||||
"}\n" +
|
||||
">>>>>>> REPLACE"
|
||||
|
||||
const result = await strategy.applyDiff(originalContent, diffContent)
|
||||
expect(result.success).toBe(false)
|
||||
// The error should be about identical content without case mismatch
|
||||
if (!result.success && result.error) {
|
||||
expect(result.error).toContain("Search and replace content are identical")
|
||||
expect(result.error).not.toContain("potential case mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
it("should detect case mismatches in no match found errors", async () => {
|
||||
const originalContent = `function getData() {
|
||||
const originalStructRVA = 0;
|
||||
return originalStructRVA;
|
||||
}`
|
||||
const diffContent =
|
||||
"<<<<<<< SEARCH\n" +
|
||||
"function getData() {\n" +
|
||||
" const originalstructrva = 0;\n" +
|
||||
" return originalstructrva;\n" +
|
||||
"}\n" +
|
||||
"=======\n" +
|
||||
"function getData() {\n" +
|
||||
" const newValue = 0;\n" +
|
||||
" return newValue;\n" +
|
||||
"}\n" +
|
||||
">>>>>>> REPLACE"
|
||||
|
||||
const result = await strategy.applyDiff(originalContent, diffContent)
|
||||
expect(result.success).toBe(false)
|
||||
if (!result.success && result.error) {
|
||||
expect(result.error).toContain("No sufficiently similar match found")
|
||||
expect(result.error).toContain("Potential case mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
it("should log case sensitivity issues when search and replace differ only by case", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {})
|
||||
|
||||
const originalContent = `function test() {
|
||||
const myVariable = 0;
|
||||
return myVariable;
|
||||
}`
|
||||
const diffContent =
|
||||
"<<<<<<< SEARCH\n" +
|
||||
"function test() {\n" +
|
||||
" const myVariable = 0;\n" +
|
||||
" return myVariable;\n" +
|
||||
"}\n" +
|
||||
"=======\n" +
|
||||
"function test() {\n" +
|
||||
" const MyVariable = 0;\n" +
|
||||
" return MyVariable;\n" +
|
||||
"}\n" +
|
||||
">>>>>>> REPLACE"
|
||||
|
||||
await strategy.applyDiff(originalContent, diffContent)
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("[apply_diff] Case sensitivity mismatch detected"),
|
||||
)
|
||||
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("should handle multiple case mismatches in the same file", async () => {
|
||||
const originalContent = `class TestClass {
|
||||
constructor() {
|
||||
this.myProperty = 0;
|
||||
this.OtherProperty = 1;
|
||||
}
|
||||
|
||||
getMyProperty() {
|
||||
return this.myProperty;
|
||||
}
|
||||
}`
|
||||
const diffContent =
|
||||
"<<<<<<< SEARCH\n" +
|
||||
"class TestClass {\n" +
|
||||
" constructor() {\n" +
|
||||
" this.myproperty = 0;\n" +
|
||||
" this.otherproperty = 1;\n" +
|
||||
" }\n" +
|
||||
"=======\n" +
|
||||
"class TestClass {\n" +
|
||||
" constructor() {\n" +
|
||||
" this.myProp = 0;\n" +
|
||||
" this.otherProp = 1;\n" +
|
||||
" }\n" +
|
||||
">>>>>>> REPLACE"
|
||||
|
||||
const result = await strategy.applyDiff(originalContent, diffContent)
|
||||
expect(result.success).toBe(false)
|
||||
if (!result.success && result.error) {
|
||||
expect(result.error).toContain("No sufficiently similar match found")
|
||||
expect(result.error).toContain("Potential case mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
it("should detect case differences in search vs replace content", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {})
|
||||
|
||||
const originalContent = `function test() {
|
||||
return true;
|
||||
}`
|
||||
const diffContent =
|
||||
"<<<<<<< SEARCH\n" +
|
||||
"function test() {\n" +
|
||||
" return true;\n" +
|
||||
"}\n" +
|
||||
"=======\n" +
|
||||
"function TEST() {\n" +
|
||||
" return TRUE;\n" +
|
||||
"}\n" +
|
||||
">>>>>>> REPLACE"
|
||||
|
||||
await strategy.applyDiff(originalContent, diffContent)
|
||||
|
||||
// This test is for multi-file-search-replace.ts functionality
|
||||
// The console.log is only in multi-file-search-replace.ts
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("fuzzy matching", () => {
|
||||
|
|
|
|||
|
|
@ -532,6 +532,31 @@ Each file requires its own path, start_line, and diff elements.
|
|||
let searchLines = searchContent === "" ? [] : searchContent.split(/\r?\n/)
|
||||
let replaceLines = replaceContent === "" ? [] : replaceContent.split(/\r?\n/)
|
||||
|
||||
// Check for case sensitivity issues after identical content check
|
||||
if (searchContent !== replaceContent && searchContent.toLowerCase() === replaceContent.toLowerCase()) {
|
||||
// The content differs only by case - add this to debug info
|
||||
const caseDifferences: string[] = []
|
||||
const searchWords = searchContent.split(/\s+/)
|
||||
const replaceWords = replaceContent.split(/\s+/)
|
||||
|
||||
for (let i = 0; i < Math.min(searchWords.length, replaceWords.length); i++) {
|
||||
if (
|
||||
searchWords[i] !== replaceWords[i] &&
|
||||
searchWords[i].toLowerCase() === replaceWords[i].toLowerCase()
|
||||
) {
|
||||
caseDifferences.push(`"${searchWords[i]}" vs "${replaceWords[i]}"`)
|
||||
}
|
||||
}
|
||||
|
||||
if (caseDifferences.length > 0) {
|
||||
console.log(`[apply_diff] Case sensitivity mismatch detected in search/replace content:`)
|
||||
console.log(` File: ${originalContent.substring(0, 50)}...`)
|
||||
console.log(
|
||||
` Case differences: ${caseDifferences.slice(0, 3).join(", ")}${caseDifferences.length > 3 ? ` and ${caseDifferences.length - 3} more` : ""}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that search content is not empty
|
||||
if (searchLines.length === 0) {
|
||||
diffResults.push({
|
||||
|
|
@ -634,6 +659,25 @@ Each file requires its own path, start_line, and diff elements.
|
|||
|
||||
const lineRange = startLine ? ` at line: ${startLine}` : ""
|
||||
|
||||
// Check for potential case mismatches in the entire file
|
||||
let caseMismatchInfo = ""
|
||||
const searchLower = searchChunk.toLowerCase()
|
||||
let caseMismatchCount = 0
|
||||
|
||||
for (let i = 0; i <= resultLines.length - searchLines.length; i++) {
|
||||
const chunk = resultLines.slice(i, i + searchLines.length).join("\n")
|
||||
if (chunk.toLowerCase() === searchLower && chunk !== searchChunk) {
|
||||
caseMismatchCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (caseMismatchCount > 0) {
|
||||
caseMismatchInfo = `\n- 🔍 **Potential case mismatch**: Found ${caseMismatchCount} location(s) where content differs only by case`
|
||||
console.log(
|
||||
`[apply_diff] Case sensitivity issue detected: ${caseMismatchCount} potential matches with different casing`,
|
||||
)
|
||||
}
|
||||
|
||||
diffResults.push({
|
||||
success: false,
|
||||
error: `No sufficiently similar match found${lineRange} (${Math.floor(
|
||||
|
|
@ -644,7 +688,7 @@ Each file requires its own path, start_line, and diff elements.
|
|||
bestMatchScore * 100,
|
||||
)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Search Range: ${
|
||||
startLine ? `starting at line ${startLine}` : "start to end"
|
||||
}\n- Tried both standard and aggressive line number stripping\n- Tip: Use the read_file tool to get the latest content of the file before attempting to use the apply_diff tool again, as the file content may have changed\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}`,
|
||||
}\n- Tried both standard and aggressive line number stripping${caseMismatchInfo}\n- Tip: Use the read_file tool to get the latest content of the file before attempting to use the apply_diff tool again, as the file content may have changed\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -441,6 +441,31 @@ Only use a single line of '=======' between search and replacement content, beca
|
|||
let searchLines = searchContent === "" ? [] : searchContent.split(/\r?\n/)
|
||||
let replaceLines = replaceContent === "" ? [] : replaceContent.split(/\r?\n/)
|
||||
|
||||
// Check for case sensitivity issues when search and replace differ only by case
|
||||
if (searchContent !== replaceContent && searchContent.toLowerCase() === replaceContent.toLowerCase()) {
|
||||
// The content differs only by case - add this to debug info
|
||||
const caseDifferences: string[] = []
|
||||
const searchWords = searchContent.split(/\s+/)
|
||||
const replaceWords = replaceContent.split(/\s+/)
|
||||
|
||||
for (let i = 0; i < Math.min(searchWords.length, replaceWords.length); i++) {
|
||||
if (
|
||||
searchWords[i] !== replaceWords[i] &&
|
||||
searchWords[i].toLowerCase() === replaceWords[i].toLowerCase()
|
||||
) {
|
||||
caseDifferences.push(`"${searchWords[i]}" vs "${replaceWords[i]}"`)
|
||||
}
|
||||
}
|
||||
|
||||
if (caseDifferences.length > 0) {
|
||||
console.log(`[apply_diff] Case sensitivity mismatch detected in search/replace content:`)
|
||||
console.log(` File: ${originalContent.substring(0, 50)}...`)
|
||||
console.log(
|
||||
` Case differences: ${caseDifferences.slice(0, 3).join(", ")}${caseDifferences.length > 3 ? ` and ${caseDifferences.length - 3} more` : ""}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that search content is not empty
|
||||
if (searchLines.length === 0) {
|
||||
diffResults.push({
|
||||
|
|
@ -540,9 +565,28 @@ Only use a single line of '=======' between search and replacement content, beca
|
|||
|
||||
const lineRange = startLine ? ` at line: ${startLine}` : ""
|
||||
|
||||
// Check for potential case mismatches in the entire file
|
||||
let caseMismatchInfo = ""
|
||||
const searchLower = searchChunk.toLowerCase()
|
||||
let caseMismatchCount = 0
|
||||
|
||||
for (let i = 0; i <= resultLines.length - searchLines.length; i++) {
|
||||
const chunk = resultLines.slice(i, i + searchLines.length).join("\n")
|
||||
if (chunk.toLowerCase() === searchLower && chunk !== searchChunk) {
|
||||
caseMismatchCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (caseMismatchCount > 0) {
|
||||
caseMismatchInfo = `\n- 🔍 **Potential case mismatch**: Found ${caseMismatchCount} location(s) where content differs only by case`
|
||||
console.log(
|
||||
`[apply_diff] Case sensitivity issue detected: ${caseMismatchCount} potential matches with different casing`,
|
||||
)
|
||||
}
|
||||
|
||||
diffResults.push({
|
||||
success: false,
|
||||
error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)\n\nDebug Info:\n- Similarity Score: ${Math.floor(bestMatchScore * 100)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Search Range: ${startLine ? `starting at line ${startLine}` : "start to end"}\n- Tried both standard and aggressive line number stripping\n- Tip: Use the read_file tool to get the latest content of the file before attempting to use the apply_diff tool again, as the file content may have changed\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}`,
|
||||
error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)\n\nDebug Info:\n- Similarity Score: ${Math.floor(bestMatchScore * 100)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Search Range: ${startLine ? `starting at line ${startLine}` : "start to end"}\n- Tried both standard and aggressive line number stripping${caseMismatchInfo}\n- Tip: Use the read_file tool to get the latest content of the file before attempting to use the apply_diff tool again, as the file content may have changed\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue