mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 14:38:23 -10:00
fix(core): stop sed simulation from misreading backslashes in brackets (#13557)
This commit is contained in:
1 parent
c61044fae4
commit
3d65e23cf6
2 files changed
+100
-7
No files matched your search
@@ -321,8 +321,8 @@ describe('sedEditParser', () => {
|
||||
});
|
||||
|
||||
it('declines the same bracket expressions under -E', () => {
|
||||
// The -E path hands the pattern to RegExp untouched, so it diverges the
|
||||
// same way and has to be declined separately.
|
||||
// -E goes through the same bracket translation, so the same check
|
||||
// declines these.
|
||||
expect(parseSedEditCommand("sed -E -i 's/[]a]/X/g' f.txt")).toBeNull();
|
||||
expect(parseSedEditCommand("sed -E -i 's/[^]]/X/g' f.txt")).toBeNull();
|
||||
});
|
||||
@@ -335,4 +335,58 @@ describe('sedEditParser', () => {
|
||||
expect(applySed("sed -i 's/[^abc]/X/g' f.txt", 'a]b')).toBe('aXb');
|
||||
expect(applySed("sed -i 's/a\\[b/X/g' f.txt", 'a[b]c')).toBe('X]c');
|
||||
});
|
||||
|
||||
// A backslash inside a bracket expression is a literal member under -E
|
||||
// too; JS read it as an escape. Expected values measured with GNU sed 4.9.
|
||||
it('keeps backslashes literal inside bracket expressions under -E', () => {
|
||||
// [\[\]] is the class {\, [} followed by a literal ]; sed leaves this
|
||||
// input alone, the simulation deleted both brackets.
|
||||
expect(applySed("sed -E -i 's/[\\[\\]]//g' f.txt", 'a[b]c')).toBe('a[b]c');
|
||||
expect(applySed("sed -E -i 's/[\\+]/X/g' f.txt", 'a + \\ b')).toBe(
|
||||
'a X X b',
|
||||
);
|
||||
expect(applySed("sed -E -i 's/[\\.]/X/g' f.txt", 'a.b\\c')).toBe('aXbXc');
|
||||
expect(applySed("sed -E -i 's/[\\]]/X/g' f.txt", 'a\\]b]')).toBe('aXb]');
|
||||
// \-c is a range from backslash to c, so it covers b as well.
|
||||
expect(applySed("sed -E -i 's/[a\\-c]/X/g' f.txt", 'a-b\\c')).toBe('X-XXX');
|
||||
});
|
||||
|
||||
// GNU sed expands \t, \n and friends inside a bracket expression, so
|
||||
// [ \t] is a space or a TAB there; the BRE translation read it as a space,
|
||||
// backslash or t. Other seds disagree with GNU, so these go to real sed.
|
||||
it('declines character escapes inside bracket expressions', () => {
|
||||
// On 'bat', sed writes 'bat'; the simulation wrote 'ba'.
|
||||
expect(parseSedEditCommand("sed -i 's/[ \\t]*$//' f.txt")).toBeNull();
|
||||
});
|
||||
|
||||
it.each(['n', 't', 'r', 'f', 'v', 'a', 'd065', 'o101', 'x41', 'cA'])(
|
||||
'declines [\\%s] under BRE and -E',
|
||||
(escape) => {
|
||||
expect(
|
||||
parseSedEditCommand(`sed -i 's/[\\${escape}]/X/g' f.txt`),
|
||||
).toBeNull();
|
||||
expect(
|
||||
parseSedEditCommand(`sed -E -i 's/[\\${escape}]/X/g' f.txt`),
|
||||
).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it('still simulates escapes outside brackets and escaped backslashes', () => {
|
||||
// Guards over-correcting. GNU sed reads \t outside a bracket as a TAB,
|
||||
// as the simulation does; the ] in [a]\t closes the bracket, so that \t
|
||||
// is simulated (GNU sed 4.9 writes 'at Xb'); a \\ inside a bracket
|
||||
// leaves the t after it literal; and under -E an escaped [ does not
|
||||
// open a bracket expression.
|
||||
expect(applySed("sed -i 's/a\\tb/X/g' f.txt", 'a\tb')).toBe('X');
|
||||
expect(applySed("sed -i 's/[a]\\t/X/' f.txt", 'at a\tb')).toBe('at Xb');
|
||||
expect(applySed("sed -E -i 's/[a]\\t/X/' f.txt", 'at a\tb')).toBe('at Xb');
|
||||
expect(applySed("sed -i 's/[\\\\t]/X/g' f.txt", 'a\tb\\t')).toBe('a\tbXX');
|
||||
expect(applySed("sed -E -i 's/[\\\\t]/X/g' f.txt", 'a\tb\\t')).toBe(
|
||||
'a\tbXX',
|
||||
);
|
||||
expect(applySed("sed -E -i 's/a\\.b/X/g' f.txt", 'a.b axb')).toBe('X axb');
|
||||
expect(applySed("sed -E -i 's/\\[a\\]/X/g' f.txt", '[a] a\\]')).toBe(
|
||||
'X a\\]',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -181,7 +181,8 @@ function canCompileSedPattern(sedInfo: SedEditInfo): boolean {
|
||||
if (
|
||||
hasPosixBracketExpression(jsPattern) ||
|
||||
hasLeadingBracketLiteral(jsPattern) ||
|
||||
hasSedJavascriptDivergentEscape(jsPattern)
|
||||
hasSedJavascriptDivergentEscape(jsPattern) ||
|
||||
hasBracketCharacterEscape(sedInfo.pattern)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -230,6 +231,36 @@ function hasSedJavascriptDivergentEscape(pattern: string): boolean {
|
||||
return /\\[<>dDwWsS]/u.test(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* True when a bracket expression holds a character escape such as `\t`,
|
||||
* `\n` or `\x41`. GNU sed expands these, so `[ \t]` is a space or a TAB;
|
||||
* POSIX reads a backslash and a `t`, as the translation does. The seds
|
||||
* disagree, so the caller declines the pattern and real sed runs it.
|
||||
*/
|
||||
function hasBracketCharacterEscape(pattern: string): boolean {
|
||||
let inCharacterClass = false;
|
||||
for (let i = 0; i < pattern.length; i++) {
|
||||
const char = pattern[i]!;
|
||||
if (char === '\\') {
|
||||
const next = pattern[i + 1] ?? '';
|
||||
if (inCharacterClass && /[ntrfvadoxc]/u.test(next)) {
|
||||
return true;
|
||||
}
|
||||
// Inside a bracket only `\\` pairs up, so `[\\t]` ends in a plain `t`.
|
||||
if (!inCharacterClass || next === '\\') {
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (char === '[') {
|
||||
inCharacterClass = true;
|
||||
} else if (char === ']') {
|
||||
inCharacterClass = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasReplacementBackrefBeyondCaptures(
|
||||
replacement: string,
|
||||
captureCount: number,
|
||||
@@ -505,15 +536,13 @@ export function applySedSubstitution(
|
||||
function toJavascriptPattern(sedInfo: SedEditInfo): string {
|
||||
const pattern = unescapeSedDelimiter(sedInfo.pattern);
|
||||
|
||||
if (sedInfo.extendedRegex) {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
let jsPattern = '';
|
||||
let inCharacterClass = false;
|
||||
for (let i = 0; i < pattern.length; i++) {
|
||||
const char = pattern[i]!;
|
||||
if (inCharacterClass) {
|
||||
// A backslash inside a bracket expression is a literal member in BRE
|
||||
// and ERE alike, not an escape as in JS.
|
||||
if (char === '\\') {
|
||||
jsPattern += '\\\\';
|
||||
continue;
|
||||
@@ -531,6 +560,16 @@ function toJavascriptPattern(sedInfo: SedEditInfo): string {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sedInfo.extendedRegex) {
|
||||
if (char === '\\' && i + 1 < pattern.length) {
|
||||
jsPattern += char + pattern[i + 1]!;
|
||||
i++;
|
||||
} else {
|
||||
jsPattern += char;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '\\' && i + 1 < pattern.length) {
|
||||
const next = pattern[i + 1]!;
|
||||
if (BRE_OPERATORS.has(next)) {
|
||||
|
||||
Reference in new issue
Block a user