fix: address code scanning alerts (#240)
This commit is contained in:
40
src/tools/BashTool/sedEditParser.test.ts
Normal file
40
src/tools/BashTool/sedEditParser.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { expect, test } from 'bun:test'
|
||||
|
||||
import { applySedSubstitution, type SedEditInfo } from './sedEditParser.js'
|
||||
|
||||
function sedInfo(pattern: string, replacement: string, extendedRegex = false): SedEditInfo {
|
||||
return {
|
||||
filePath: 'example.txt',
|
||||
pattern,
|
||||
replacement,
|
||||
flags: 'g',
|
||||
extendedRegex,
|
||||
}
|
||||
}
|
||||
|
||||
test('BRE mode keeps unescaped plus literal', () => {
|
||||
const result = applySedSubstitution(
|
||||
'a+b and aaab',
|
||||
sedInfo('a+b', 'literal-plus'),
|
||||
)
|
||||
|
||||
expect(result).toBe('literal-plus and aaab')
|
||||
})
|
||||
|
||||
test('BRE mode treats escaped plus as one-or-more', () => {
|
||||
const result = applySedSubstitution(
|
||||
'abbb and a+b',
|
||||
sedInfo('ab\\+', 'one-or-more'),
|
||||
)
|
||||
|
||||
expect(result).toBe('one-or-more and a+b')
|
||||
})
|
||||
|
||||
test('BRE mode preserves escaped backslashes', () => {
|
||||
const result = applySedSubstitution(
|
||||
String.raw`foo\bar foo/bar`,
|
||||
sedInfo(String.raw`foo\\bar`, 'backslash-match'),
|
||||
)
|
||||
|
||||
expect(result).toBe('backslash-match foo/bar')
|
||||
})
|
||||
@@ -7,18 +7,6 @@ import { randomBytes } from 'crypto'
|
||||
import { tryParseShellCommand } from '../../utils/bash/shellQuote.js'
|
||||
|
||||
// BRE→ERE conversion placeholders (null-byte sentinels, never appear in user input)
|
||||
const BACKSLASH_PLACEHOLDER = '\x00BACKSLASH\x00'
|
||||
const PLUS_PLACEHOLDER = '\x00PLUS\x00'
|
||||
const QUESTION_PLACEHOLDER = '\x00QUESTION\x00'
|
||||
const PIPE_PLACEHOLDER = '\x00PIPE\x00'
|
||||
const LPAREN_PLACEHOLDER = '\x00LPAREN\x00'
|
||||
const RPAREN_PLACEHOLDER = '\x00RPAREN\x00'
|
||||
const BACKSLASH_PLACEHOLDER_RE = new RegExp(BACKSLASH_PLACEHOLDER, 'g')
|
||||
const PLUS_PLACEHOLDER_RE = new RegExp(PLUS_PLACEHOLDER, 'g')
|
||||
const QUESTION_PLACEHOLDER_RE = new RegExp(QUESTION_PLACEHOLDER, 'g')
|
||||
const PIPE_PLACEHOLDER_RE = new RegExp(PIPE_PLACEHOLDER, 'g')
|
||||
const LPAREN_PLACEHOLDER_RE = new RegExp(LPAREN_PLACEHOLDER, 'g')
|
||||
const RPAREN_PLACEHOLDER_RE = new RegExp(RPAREN_PLACEHOLDER, 'g')
|
||||
|
||||
export type SedEditInfo = {
|
||||
/** The file path being edited */
|
||||
@@ -33,6 +21,40 @@ export type SedEditInfo = {
|
||||
extendedRegex: boolean
|
||||
}
|
||||
|
||||
function convertBrePatternToJs(pattern: string): string {
|
||||
let result = ''
|
||||
|
||||
for (let i = 0; i < pattern.length; i++) {
|
||||
const char = pattern[i]!
|
||||
|
||||
if (char === '\\') {
|
||||
const next = pattern[i + 1]
|
||||
if (next === undefined) {
|
||||
result += '\\\\'
|
||||
continue
|
||||
}
|
||||
if (next === '\\') {
|
||||
result += '\\\\'
|
||||
} else if ('+?|()'.includes(next)) {
|
||||
result += next
|
||||
} else {
|
||||
result += `\\${next}`
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if ('+?|()'.includes(char)) {
|
||||
result += `\\${char}`
|
||||
continue
|
||||
}
|
||||
|
||||
result += char
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a command is a sed in-place edit command
|
||||
* Returns true only for simple sed -i 's/pattern/replacement/flags' file commands
|
||||
@@ -273,28 +295,7 @@ export function applySedSubstitution(
|
||||
// ERE/JS: + means "one or more", \+ is literal
|
||||
// We need to convert BRE escaping to ERE for JavaScript regex
|
||||
if (!sedInfo.extendedRegex) {
|
||||
jsPattern = jsPattern
|
||||
// Step 1: Protect literal backslashes (\\) first - in both BRE and ERE, \\ is literal backslash
|
||||
.replace(/\\\\/g, BACKSLASH_PLACEHOLDER)
|
||||
// Step 2: Replace escaped metacharacters with placeholders (these should become unescaped in JS)
|
||||
.replace(/\\\+/g, PLUS_PLACEHOLDER)
|
||||
.replace(/\\\?/g, QUESTION_PLACEHOLDER)
|
||||
.replace(/\\\|/g, PIPE_PLACEHOLDER)
|
||||
.replace(/\\\(/g, LPAREN_PLACEHOLDER)
|
||||
.replace(/\\\)/g, RPAREN_PLACEHOLDER)
|
||||
// Step 3: Escape unescaped metacharacters (these are literal in BRE)
|
||||
.replace(/\+/g, '\\+')
|
||||
.replace(/\?/g, '\\?')
|
||||
.replace(/\|/g, '\\|')
|
||||
.replace(/\(/g, '\\(')
|
||||
.replace(/\)/g, '\\)')
|
||||
// Step 4: Replace placeholders with their JS equivalents
|
||||
.replace(BACKSLASH_PLACEHOLDER_RE, '\\\\')
|
||||
.replace(PLUS_PLACEHOLDER_RE, '+')
|
||||
.replace(QUESTION_PLACEHOLDER_RE, '?')
|
||||
.replace(PIPE_PLACEHOLDER_RE, '|')
|
||||
.replace(LPAREN_PLACEHOLDER_RE, '(')
|
||||
.replace(RPAREN_PLACEHOLDER_RE, ')')
|
||||
jsPattern = convertBrePatternToJs(jsPattern)
|
||||
}
|
||||
|
||||
// Unescape sed-specific escapes in replacement
|
||||
|
||||
Reference in New Issue
Block a user