mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(oxfmt/lsp): avoid newlines line ending changes (#23463)
close https://github.com/oxc-project/oxc/issues/23462 Manually tested in VS Code. A snapshot test will not work, because it does not reflect how editors work, it just replaces position→bytes offset, not interpreting what an editor/ LSP client does. _https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocuments_ > So you can not specify a position that denotes \r|\n or \n| where | represents the character offset.
This commit is contained in:
1 parent
fef29bdbce
commit
37a34a128f
1 file changed
+40
-4
@@ -437,6 +437,8 @@ fn deserialize_lsp_options(value: serde_json::Value) -> LSPFormatOptions {
|
||||
}
|
||||
|
||||
/// Returns the minimal text edit (start, end, replacement) to transform `source_text` into `formatted_text`
|
||||
///
|
||||
/// Respects EOL characters as a whole edit. Appending `\r` before `\n` is invalid and should be treated as a single edit replacing `\n` with `\r\n`.
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
fn compute_minimal_text_edit<'a>(
|
||||
source_text: &str,
|
||||
@@ -461,10 +463,25 @@ fn compute_minimal_text_edit<'a>(
|
||||
let src_len = src_bytes.len();
|
||||
let fmt_len = fmt_bytes.len();
|
||||
|
||||
while suffix_byte < src_len - prefix_byte
|
||||
&& suffix_byte < fmt_len - prefix_byte
|
||||
&& src_bytes[src_len - 1 - suffix_byte] == fmt_bytes[fmt_len - 1 - suffix_byte]
|
||||
{
|
||||
while suffix_byte < src_len - prefix_byte && suffix_byte < fmt_len - prefix_byte {
|
||||
let src_idx = src_len - 1 - suffix_byte;
|
||||
let fmt_idx = fmt_len - 1 - suffix_byte;
|
||||
|
||||
// Prevents appending `\r` to a line ending in `\n`, instead we want to replace `\n` with `\r\n` in this case,
|
||||
// aligning how LSP text documents are represented (line / column editing, instead of bytes).
|
||||
if src_bytes[src_idx] == b'\n' && fmt_bytes[fmt_idx] == b'\n' {
|
||||
let src_has_cr = src_idx > 0 && src_bytes[src_idx - 1] == b'\r';
|
||||
let fmt_has_cr = fmt_idx > 0 && fmt_bytes[fmt_idx - 1] == b'\r';
|
||||
|
||||
if src_has_cr != fmt_has_cr {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if src_bytes[src_idx] != fmt_bytes[fmt_idx] {
|
||||
break;
|
||||
}
|
||||
|
||||
suffix_byte += 1;
|
||||
}
|
||||
|
||||
@@ -607,4 +624,23 @@ mod tests {
|
||||
let (start, end, replacement) = compute_minimal_text_edit(&src, &formatted);
|
||||
assert_eq!((start, end, replacement), (0, 0, "b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
fn test_replacing_line_breaks() {
|
||||
let src_lf = "line1\nline2\nline3";
|
||||
let src_crlf = "line1\r\nline2\r\nline3";
|
||||
|
||||
// from LF to CRLF
|
||||
{
|
||||
let (start, end, replacement) = compute_minimal_text_edit(src_lf, src_crlf);
|
||||
assert_eq!((start, end, replacement), (5, src_lf.len() as u32 - 5, "\r\nline2\r\n"));
|
||||
}
|
||||
|
||||
// from CRLF to LF
|
||||
{
|
||||
let (start, end, replacement) = compute_minimal_text_edit(src_crlf, src_lf);
|
||||
assert_eq!((start, end, replacement), (5, src_crlf.len() as u32 - 5, "\nline2\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user