mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(formatter_core): report error position releative to CRLF source (#27777)
e.g. CSS, YAML formatter replace CRLF beforehand, so when reports error, need to restore positon.
This commit is contained in:
1 parent
c78e80edc5
commit
5b2acb0427
7 files changed
+58
-9
No files matched your search
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { format } from "../../dist/index.js";
|
||||
|
||||
describe("Line ending handling", () => {
|
||||
// Some entries parse a newline-normalized copy, but error labels must point into the original source
|
||||
it.each([
|
||||
["a.css", "a {}\nb {"],
|
||||
["a.css", "\uFEFFa {}\nb {"],
|
||||
["a.yaml", "a: 1\nb: ["],
|
||||
])("reports error labels in %s against the CRLF source", async (filename, source) => {
|
||||
const lf = await format(filename, source);
|
||||
const crlf = await format(filename, source.replaceAll("\n", "\r\n"));
|
||||
// Each input has 1 line break before the error
|
||||
expect(crlf.errors[0].labels).toStrictEqual(
|
||||
// oxlint-disable no-map-spread
|
||||
lf.errors[0].labels.map((l) => ({ ...l, start: l.start + 1, end: l.end + 1 })),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -112,7 +112,7 @@ A type/fn that fits none of them belongs in a consumer crate.
|
||||
|
||||
Admission: the printing phase consumes it. "Shared by all languages" is NOT a reason on its own.
|
||||
|
||||
- (2) `source/`: Source-side access mechanics (`SourceText`, `SpanCursor`, `rebase_labels`)
|
||||
- (2) `source/`: Source-side access mechanics (`SourceText`, `SpanCursor`, `rebase_labels`, `restore_crlf_labels`)
|
||||
|
||||
Admission: the structure makes no output decision by itself; language differences arrive as data (offsets, the item type), never as parameters encoding grammar or policy.
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ pub use session::embedded::{
|
||||
dispatch_fragment_ir, dispatch_ir, map_text_in_ir, push_text_with_literal_lines,
|
||||
};
|
||||
pub use session::{FormatSession, InputKind, SessionServices, StringEmbedder, TailwindSorter};
|
||||
pub use source::{SourceText, SpanCursor, rebase_labels};
|
||||
pub use source::{SourceText, SpanCursor, rebase_labels, restore_crlf_labels};
|
||||
pub use write::Format;
|
||||
pub use write::arguments::{Argument, Arguments};
|
||||
pub use write::buffer::ScratchBuffer;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use oxc_span::LabeledSpan;
|
||||
use oxc_span::{LabeledSpan, Span};
|
||||
|
||||
/// Moves `labels` pointing into `tail`, a suffix of `source`, onto `source`.
|
||||
///
|
||||
@@ -11,3 +11,30 @@ pub fn rebase_labels(labels: &mut [LabeledSpan], source: &str, tail: &str) {
|
||||
label.set_span_offset(label.offset().saturating_add(offset));
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves `labels` pointing into `normalize_newlines(source, ['\r'])` onto `source`.
|
||||
///
|
||||
/// Entries that normalize line endings before parsing collapse each `\r\n` into one byte,
|
||||
/// so a label shifts right by the CRLFs before it.
|
||||
pub fn restore_crlf_labels(labels: &mut [LabeledSpan], source: &str) {
|
||||
let restore = |offset: u32| {
|
||||
let mut offset = offset as usize;
|
||||
for (pos, _) in source.match_indices("\r\n") {
|
||||
if pos >= offset {
|
||||
break;
|
||||
}
|
||||
offset += 1;
|
||||
}
|
||||
u32::try_from(offset).unwrap_or(u32::MAX)
|
||||
};
|
||||
for label in labels {
|
||||
let span = label.span();
|
||||
let span = Span::new(restore(span.start), restore(span.end));
|
||||
let text = label.label().map(ToString::to_string);
|
||||
*label = if label.primary() {
|
||||
LabeledSpan::new_primary_with_span(text, span)
|
||||
} else {
|
||||
LabeledSpan::new_with_span(text, span)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
//!
|
||||
//! - [`SourceText`]: offset-keyed byte/slice access (WHERE)
|
||||
//! - [`SpanCursor`]: span-ordered drain over a sorted item slice (WHO is next)
|
||||
//! - [`rebase_labels`]: diagnostic labels moved from a trimmed tail back onto the source
|
||||
//! - [`rebase_labels`] / [`restore_crlf_labels`]: diagnostic labels moved from a trimmed / newline-normalized copy back onto the source
|
||||
//!
|
||||
//! Admission: the structure decides nothing about the output.
|
||||
//! Unlike `spec/`, which encodes shared formatting behavior as pure functions.
|
||||
@@ -17,5 +17,5 @@ mod labels;
|
||||
mod text;
|
||||
|
||||
pub use cursor::SpanCursor;
|
||||
pub use labels::rebase_labels;
|
||||
pub use labels::{rebase_labels, restore_crlf_labels};
|
||||
pub use text::SourceText;
|
||||
@@ -117,6 +117,7 @@ pub fn parse_for_format<'a>(
|
||||
let PreparedSource { source, parse_source, front_matter } = prepare_source(allocator, stripped);
|
||||
let (stylesheet, comments) =
|
||||
parse_stylesheet(allocator, parse_source, options, root).map_err(|mut err| {
|
||||
oxc_formatter_core::restore_crlf_labels(&mut err.labels, stripped);
|
||||
oxc_formatter_core::rebase_labels(&mut err.labels, source_text, stripped);
|
||||
err
|
||||
})?;
|
||||
|
||||
@@ -117,12 +117,14 @@ fn parse_root<'a>(
|
||||
// NOTE: Normalize line endings BEFORE parsing, unlike other `oxc_formatter_xxx`.
|
||||
// For YAML formatter, the printer slices verbatim text from the source in many places.
|
||||
// YAML is also unusual in that line breaks and whitespace have meaning.
|
||||
let source_text = oxc_formatter_core::normalize_newlines(source_text, ['\r']);
|
||||
let source: &'a str = allocator.alloc_str(&source_text);
|
||||
let normalized = oxc_formatter_core::normalize_newlines(source_text, ['\r']);
|
||||
let source: &'a str = allocator.alloc_str(&normalized);
|
||||
|
||||
let root = Parser::new(allocator, source).parse().map_err(|error| {
|
||||
OxcDiagnostic::error(format!("Syntax error: {}", error.kind))
|
||||
.with_label(to_span(error.span))
|
||||
let mut err = OxcDiagnostic::error(format!("Syntax error: {}", error.kind))
|
||||
.with_label(to_span(error.span));
|
||||
oxc_formatter_core::restore_crlf_labels(&mut err.labels, source_text);
|
||||
err
|
||||
})?;
|
||||
|
||||
let root = allocator.alloc(root);
|
||||
|
||||
Reference in new issue
Block a user