docs(formatter,formatter_graphql,formatter_yaml): annotate own-line comment inlining as known policy violation (#26131)

This commit is contained in:
leaysgur committed 2026-08-27 06:29:50 +00:00
1 parent f94b22296c
commit b8796080d5
5 files changed
+53 -6

No files matched your search

+1
View File
@@ -126,6 +126,7 @@ JS-side mechanics of the shared "never cross" invariants:
- Line boundary: line comments are printed via `line_suffix`, and own-line comments stay own-line (they become the next node's leading comments)
- Both are structural guarantees, keep them
- One known violation, kept for Prettier compat: an own-line comment claimed mid-line inlines onto that line (`const // c` + break), see the NOTE in `FormatLeadingComments` (`formatter/trivia.rs`)
- User content: e.g. Prettier relocates a comment after a trailing array hole backward across commas to the last real element — an attachment artifact, we keep the comment in place and diverge intentionally
- Suppression: when hiding comments from a node (`limit_comments_up_to`), check `has_trailing_suppression_comment` first, or the node loses its suppression
@@ -104,6 +104,20 @@ pub enum FormatLeadingComments<'a> {
impl<'a> Format<'a, JsFormatContext<'a>> for FormatLeadingComments<'a> {
fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
// NOTE: Known FORMATTER_POLICY violation ("own-line comments stay own-line"):
// the line break BEFORE the first comment is never reproduced,
// so an own-line comment claimed mid-line inlines onto that line
// ```js
// const
// // c
// a = 1
//
// // ->
//
// const // c
// a = 1
// ```
// Kept for now: Prettier byte-compat outweighs the invariant.
fn format_leading_comments_impl<'a>(
comments: impl IntoIterator<Item = &'a Comment>,
f: &mut JsFormatter<'_, 'a>,
+3 -6
View File
@@ -44,13 +44,10 @@ This crate instead collects comment spans into a positional cursor, drained in s
The shared placement invariants apply: a comment stays between the source tokens it sat between, and a same-line trailing comment stays on its line.
Two bounded exceptions:
Two known deviations, kept deliberately (details live on the helpers' doc comments):
- an own-line comment claimed right after a printed literal (`type`, `:`, `=`) inlines on that literal's line
- `type` + break + `# c` + break + `A` prints as `type # c` + break + `A`
- identical to Prettier and to `oxc_formatter`'s `const // c` + break + `a = 1`; keeping it own-line would need a column-conditional break the IR does not have
- positions no printer claims fall back to an own-line trailing comment after the node, which may cross remaining in-span tokens (e.g. a type's `!`)
- `flush_overlooked_inside_comments`
- an own-line comment claimed right after a printed literal inlines on that literal's line (`write_leading_comments`)
- positions no printer claims fall back to a trailing comment after the node, which may cross remaining in-span tokens (`flush_overlooked_inside_comments`)
Where Prettier relocates a comment across tokens instead, we diverge, see DIVERGENCES.md.
@@ -39,6 +39,24 @@ fn write_gap(gap: &[u8], f: &mut GraphqlFormatter<'_, '_>) {
/// Emit comments that precede a node,
/// preserving the source's vertical spacing (0/1/blank) between each comment and the next position.
///
/// NOTE: Known FORMATTER_POLICY violation ("own-line comments stay own-line"):
/// the line break BEFORE the first comment is never reproduced,
/// so an own-line comment claimed mid-line (right after `type`, `:`, `=`) inlines onto that line
/// ```graphql
/// type
/// # c
/// A { f: Int }
///
/// # ->
///
/// type # c
/// A {
/// f: Int
/// }
/// ```
/// Kept for now: Prettier byte-compat outweighs the invariant
/// (fixable in the existing IR: hardline before flushing at the claim points).
fn write_leading_comments(comments: &[Span], value_start: u32, f: &mut GraphqlFormatter<'_, '_>) {
let source = f.context().source_text();
for (i, &span) in comments.iter().enumerate() {
@@ -199,6 +217,9 @@ fn write_trailing_inside_comments<'a>(
/// positions no printer claims (e.g. between a type and its `!`, or after the last argument's directives).
/// Draining them here keeps the positional cursor monotonic,
/// a later flush point would sit AFTER these comments in the source, making its gap range inverted (start > end).
///
/// The cost: the comments land after the node, possibly crossing remaining in-span tokens (e.g. a type's `!`).
/// Known "never crosses user content" violation, accepted for monotonicity.
pub fn flush_overlooked_inside_comments(upper_bound: u32, f: &mut GraphqlFormatter<'_, '_>) {
let leftover = f.context().comments().take_before(upper_bound);
write_trailing_inside_comments(leftover, None, f);
@@ -117,6 +117,20 @@ pub fn write_node<'a>(node: &'a Node<'a>, f: &mut YamlFormatter<'_, 'a>) {
write_props(&node.props, is_block_collection && !has_middle_comments, f);
// Prettier: `[middles.len() == 1 ? "" : hardline, join(hardline, middles), hardline]`.
//
// NOTE: The single-middle case is a known FORMATTER_POLICY violation
// ("own-line comments stay own-line"): an own-line `# c` inlines after the props
// ```yaml
// key: &a
// # c
// a: 1
//
// # ->
//
// key: &a # c
// a: 1
// ```
// Kept for now: Prettier byte-compat outweighs the invariant.
// A block collection's trailing suppression marker is NOT a middle comment:
// it stays pending so the first item's check can claim it.
let middles_bound = suppression_flush_bound(is_block_collection, content_start, f);