diff --git a/CHANGELOG.md b/CHANGELOG.md index dee41da2..f71e7a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,23 +11,16 @@ non-empty and carry a `` marker; `deno task publ ## Unreleased +- formatting is now non-configurable by design - tsv has no config files, CLI flags, + or runtime options, and none will be added + (this has no observable API changes because options had been deferred) +- support `format-ignore` as an alias to `prettier-ignore` + (along with `format-ignore-start` and `format-ignore-end` for templates) - various conformance fixes to the formatter and parser - numerous new Prettier divergences including uniform indentation on continuations - object destructuring patterns in Svelte blocks now hug their braces, - consistent with `bracketSpacing: false` (which is not - respected by prettier-plugin-svelte even when set) + consistent with `bracketSpacing: false` (which is not respected by prettier-plugin-svelte) - reduce allocations using `SmallVec` and memoizations -- formatting is now **non-configurable by design** — no config files, CLI flags, - or runtime options, and none are planned (opinionated like `gofmt` and Black). - Policy only — no change to formatter output or the published API - (`format_*` / `parse_*` / the `tsv` bin). -- parse AST: each comment now appears **once** in the public AST everywhere. tsv - previously replicated acorn-typescript's backtrack-and-reparse comment - duplication for type-space constructs (type literals, mapped/function types, - type assertions, type-member index/computed signatures, typed-param arrows); - it now corrects the duplication uniformly, matching the existing class-body - behavior. The set of distinct comments is unchanged; only the duplicate - entries are gone. Formatter output is unaffected. ## 0.1.0 diff --git a/README.md b/README.md index 6e136475..917c4ca5 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ Future features (unknown order): - **[CLAUDE.md](CLAUDE.md)** - development guide (commands, structure, conventions) - **[docs/architecture.md](docs/architecture.md)** - the major design decisions +- **[docs/directives.md](docs/directives.md)** - `format-ignore` / `prettier-ignore` formatting directives - **[docs/conformance_prettier.md](docs/conformance_prettier.md)** - where formatting diverges from Prettier (and why) - **[docs/conformance_svelte.md](docs/conformance_svelte.md)** - where the parser diverges from Svelte (and why) - **[docs/conformance_test262.md](docs/conformance_test262.md)** - ECMAScript parser conformance diff --git a/crates/tsv_css/src/printer/atrules.rs b/crates/tsv_css/src/printer/atrules.rs index b103b243..ac3ab224 100644 --- a/crates/tsv_css/src/printer/atrules.rs +++ b/crates/tsv_css/src/printer/atrules.rs @@ -12,9 +12,9 @@ use super::Printer; use super::value_normalization; use crate::ast::internal; -use tsv_lang::comments_in_range; use tsv_lang::doc::{self, Mode, arena::DocId}; use tsv_lang::{PRINT_WIDTH, TAB_WIDTH}; +use tsv_lang::{comments_in_range, is_format_ignore_directive}; /// Convert a supports connector to its string representation fn connector_str(conn: internal::SupportsConnector) -> &'static str { @@ -177,7 +177,7 @@ impl<'a> Printer<'a> { self.indent_level += 1; let mut i = 0; - let mut prettier_ignore_next = false; + let mut format_ignore_next = false; while i < block.children.len() { let child = &block.children[i]; @@ -218,9 +218,9 @@ impl<'a> Printer<'a> { internal::CssBlockChild::Rule(_) | internal::CssBlockChild::Atrule(_) => { // Rules and at-rules need indentation self.write_indent(); - if prettier_ignore_next { + if format_ignore_next { self.write(child.span().extract(self.source)); - prettier_ignore_next = false; + format_ignore_next = false; } else { self.print_atrule_block_child(child); } @@ -231,9 +231,9 @@ impl<'a> Printer<'a> { i += inline_count; } internal::CssBlockChild::Comment(comment) => { - // Check for prettier-ignore - if comment.content.trim() == "prettier-ignore" { - prettier_ignore_next = true; + // Check for a format-ignore directive + if is_format_ignore_directive(&comment.content) { + format_ignore_next = true; } // Standalone comment self.write_indent(); @@ -295,7 +295,7 @@ impl<'a> Printer<'a> { // Format declarations and comments with proper indentation self.indent_level += 1; let mut i = start_index; - let mut prettier_ignore_next = false; + let mut format_ignore_next = false; while i < rule.declarations.len() { let block_child = &rule.declarations[i]; match block_child { @@ -306,14 +306,14 @@ impl<'a> Printer<'a> { { self.write("\n"); } - if prettier_ignore_next { + if format_ignore_next { self.write_indent(); self.write(decl.span.extract(self.source)); if decl.is_important() { self.write(" !important"); } self.write(";\n"); - prettier_ignore_next = false; + format_ignore_next = false; } else { self.print_css_declaration(decl); } @@ -338,9 +338,9 @@ impl<'a> Printer<'a> { // Note: Previous element already ended with \n, so one more \n gives blank line self.write("\n"); } - // Check for prettier-ignore - if comment.content.trim() == "prettier-ignore" { - prettier_ignore_next = true; + // Check for a format-ignore directive + if is_format_ignore_directive(&comment.content) { + format_ignore_next = true; } self.write_indent(); self.print_css_comment(comment); @@ -352,9 +352,9 @@ impl<'a> Printer<'a> { self.write("\n"); } self.write_indent(); - if prettier_ignore_next { + if format_ignore_next { self.write(nested_rule.span.extract(self.source)); - prettier_ignore_next = false; + format_ignore_next = false; } else { self.print_css_rule(nested_rule); } @@ -375,9 +375,9 @@ impl<'a> Printer<'a> { self.write("\n"); } self.write_indent(); - if prettier_ignore_next { + if format_ignore_next { self.write(nested_atrule.span.extract(self.source)); - prettier_ignore_next = false; + format_ignore_next = false; } else { self.print_css_atrule(nested_atrule); } diff --git a/crates/tsv_css/src/printer/mod.rs b/crates/tsv_css/src/printer/mod.rs index 553d1648..1c4d113b 100644 --- a/crates/tsv_css/src/printer/mod.rs +++ b/crates/tsv_css/src/printer/mod.rs @@ -33,7 +33,7 @@ use tsv_lang::{ self, arena::{DocArena, DocId}, }, - printing, + is_format_ignore_directive, printing, }; /// Check if function args have wrappable content (break points) @@ -224,10 +224,10 @@ impl<'a> Printer<'a> { let comments_before = self.print_leading_comments(prev_end, node_start, &mut comment_idx); - // Check printed comments for prettier-ignore (O(k) where k = comments_before) + // Check printed comments for a format-ignore directive (O(k) where k = comments_before) let has_ignore = self.comments[idx_before..comment_idx] .iter() - .any(|c| c.content.trim() == "prettier-ignore"); + .any(|c| is_format_ignore_directive(&c.content)); // Add separator before node if printed_any || comments_before > 0 { @@ -247,7 +247,7 @@ impl<'a> Printer<'a> { } } - // prettier-ignore: emit raw source instead of formatting + // format-ignore: emit raw source instead of formatting if has_ignore { self.write(node.span().extract(self.source)); } else { diff --git a/crates/tsv_css/src/printer/rules.rs b/crates/tsv_css/src/printer/rules.rs index dc666738..0aede4ac 100644 --- a/crates/tsv_css/src/printer/rules.rs +++ b/crates/tsv_css/src/printer/rules.rs @@ -15,6 +15,7 @@ use super::Printer; use crate::ast::internal; +use tsv_lang::is_format_ignore_directive; impl<'a> Printer<'a> { /// Format a CSS rule (selector + declarations block) @@ -49,7 +50,7 @@ impl<'a> Printer<'a> { // Format declarations and comments with indentation self.indent_level += 1; let mut i = start_index; - let mut prettier_ignore_next = false; + let mut format_ignore_next = false; while i < rule.declarations.len() { let child = &rule.declarations[i]; match child { @@ -58,7 +59,7 @@ impl<'a> Printer<'a> { if i > start_index && self.has_blank_line_before_child(&rule.declarations, i) { self.write("\n"); } - if prettier_ignore_next { + if format_ignore_next { // Emit raw source instead of formatting // Span doesn't include semicolon — add it like write_declaration_end self.write_indent(); @@ -67,7 +68,7 @@ impl<'a> Printer<'a> { self.write(" !important"); } self.write(";\n"); - prettier_ignore_next = false; + format_ignore_next = false; } else { self.print_css_declaration(decl); } @@ -89,9 +90,9 @@ impl<'a> Printer<'a> { self.write("\n"); } - // Check for prettier-ignore - if comment.content.trim() == "prettier-ignore" { - prettier_ignore_next = true; + // Check for a format-ignore directive + if is_format_ignore_directive(&comment.content) { + format_ignore_next = true; } self.write_indent(); @@ -105,9 +106,9 @@ impl<'a> Printer<'a> { self.write("\n"); } self.write_indent(); - if prettier_ignore_next { + if format_ignore_next { self.write(nested_rule.span.extract(self.source)); - prettier_ignore_next = false; + format_ignore_next = false; } else { self.print_css_rule(nested_rule); } @@ -127,9 +128,9 @@ impl<'a> Printer<'a> { self.write("\n"); } self.write_indent(); - if prettier_ignore_next { + if format_ignore_next { self.write(nested_atrule.span.extract(self.source)); - prettier_ignore_next = false; + format_ignore_next = false; } else { self.print_css_atrule(nested_atrule); } diff --git a/crates/tsv_debug/src/cli/commands/ts_fixture_audit.rs b/crates/tsv_debug/src/cli/commands/ts_fixture_audit.rs index 88a97c15..7a0ab94f 100644 --- a/crates/tsv_debug/src/cli/commands/ts_fixture_audit.rs +++ b/crates/tsv_debug/src/cli/commands/ts_fixture_audit.rs @@ -66,6 +66,10 @@ const INTENTIONAL_TS: &[(&str, &str)] = &[ "typescript/syntax/unicode_line_terminators", "pins U+2028/U+2029 line counting on the standalone tsv_ts loc path (LocationTracker::new_ecmascript, acorn's LineTerminator set) — formatting is context-invariant but the .svelte path tracks LF-only locations (Svelte's locate-character), so embedding would pin different locs", ), + ( + "typescript/syntax/comments/format_ignore_prettier_divergence", + "format-convertible (the directive works the same embedded), but kept .ts on purpose to pin the standalone tsv_ts + prettier-typescript-parser path for the format-ignore directive — the Svelte-embedded coverage lives in svelte/syntax/format_ignore/", + ), ]; /// Look up a fixture in `INTENTIONAL_TS` by relative-path suffix. diff --git a/crates/tsv_lang/CLAUDE.md b/crates/tsv_lang/CLAUDE.md index f5cea807..8dfc508a 100644 --- a/crates/tsv_lang/CLAUDE.md +++ b/crates/tsv_lang/CLAUDE.md @@ -138,6 +138,10 @@ See [../../CLAUDE.md §Comment Handling](../../CLAUDE.md#comment-handling-detach | `has_line_comments_in_range()` | Existence check restricted to line comments | | `has_multiline_block_comments_in_range()` | Existence check for multi-line block comments (force expansion) | +### Directive Recognition + +`is_format_ignore_directive()` / `is_format_ignore_range_start()` / `is_format_ignore_range_end()` are the single source of truth for the format-suppression directive set — the tsv-native `format-ignore` family plus prettier's `prettier-ignore` family (drop-in compat). Each operates on trimmed comment text and is called by all three language printers (`tsv_ts`, `tsv_css`, `tsv_svelte`), since the comment types differ across crates. See [docs/directives.md](../../docs/directives.md) and [docs/conformance_prettier.md §Format-ignore directive](../../docs/conformance_prettier.md#format-ignore-directive). + ## Interner Traits String interning deduplicates identifiers across all languages in a file. Symbols flow from parser through doc builder to renderer: diff --git a/crates/tsv_lang/src/comment.rs b/crates/tsv_lang/src/comment.rs index 24877dfb..25993338 100644 --- a/crates/tsv_lang/src/comment.rs +++ b/crates/tsv_lang/src/comment.rs @@ -21,6 +21,42 @@ pub struct Comment { pub emit_character_field: bool, } +// +// Format-Ignore Directive Recognition +// +// +// A comment can suppress formatting of the construct that follows it. tsv +// recognizes its own tool-neutral `format-ignore` family as canonical and +// prettier's `prettier-ignore` family as a drop-in-compatible alias — both +// spellings are honored everywhere. These predicates are the single source of +// truth for the directive set, called by each language printer (the comment +// types differ across crates, so the shared atom operates on the trimmed text). + +/// Whether `content` is a `format-ignore` / `prettier-ignore` directive — emit +/// the following construct as raw source instead of formatting it. +#[inline] +pub fn is_format_ignore_directive(content: &str) -> bool { + matches!(content.trim(), "format-ignore" | "prettier-ignore") +} + +/// Whether `content` opens an ignore range (`format-ignore-start` / +/// `prettier-ignore-start`). Everything through the matching range-end marker is +/// emitted as raw source. +#[inline] +pub fn is_format_ignore_range_start(content: &str) -> bool { + matches!( + content.trim(), + "format-ignore-start" | "prettier-ignore-start" + ) +} + +/// Whether `content` closes an ignore range (`format-ignore-end` / +/// `prettier-ignore-end`). See `is_format_ignore_range_start`. +#[inline] +pub fn is_format_ignore_range_end(content: &str) -> bool { + matches!(content.trim(), "format-ignore-end" | "prettier-ignore-end") +} + // // Comment Classification // @@ -251,6 +287,28 @@ mod tests { } } + #[test] + fn format_ignore_directives_recognize_both_spellings() { + // The tsv-native `format-ignore` family and prettier's `prettier-ignore` + // family are both honored, with surrounding whitespace trimmed (block + // comments arrive as ` format-ignore `). + assert!(is_format_ignore_directive("format-ignore")); + assert!(is_format_ignore_directive("prettier-ignore")); + assert!(is_format_ignore_directive(" format-ignore ")); + assert!(!is_format_ignore_directive("format-ignore-start")); + assert!(!is_format_ignore_directive("eslint-disable")); + + assert!(is_format_ignore_range_start("format-ignore-start")); + assert!(is_format_ignore_range_start("prettier-ignore-start")); + assert!(!is_format_ignore_range_start("format-ignore")); + assert!(!is_format_ignore_range_start("format-ignore-end")); + + assert!(is_format_ignore_range_end("format-ignore-end")); + assert!(is_format_ignore_range_end("prettier-ignore-end")); + assert!(!is_format_ignore_range_end("format-ignore")); + assert!(!is_format_ignore_range_end("format-ignore-start")); + } + #[test] fn comments_in_range_respects_start_and_end_boundaries() { let comments = vec![ diff --git a/crates/tsv_lang/src/lib.rs b/crates/tsv_lang/src/lib.rs index e1fb9ad0..bba5fdcd 100644 --- a/crates/tsv_lang/src/lib.rs +++ b/crates/tsv_lang/src/lib.rs @@ -31,7 +31,8 @@ mod span; pub use comment::{ ClassifiedComments, Comment, CommentPosition, classify_comment, classify_comment_fast, comments_after, comments_in_range, find_first_comment_from, has_comments_in_range, - has_line_comments_in_range, has_multiline_block_comments_in_range, + has_line_comments_in_range, has_multiline_block_comments_in_range, is_format_ignore_directive, + is_format_ignore_range_end, is_format_ignore_range_start, }; pub use config::{EmbedContext, INDENT, LayoutMode, PRINT_WIDTH, TAB_WIDTH}; pub use error::{ErrorContext, ParseError, Result}; diff --git a/crates/tsv_svelte/src/printer/mod.rs b/crates/tsv_svelte/src/printer/mod.rs index ff5ac3cb..e59a2790 100644 --- a/crates/tsv_svelte/src/printer/mod.rs +++ b/crates/tsv_svelte/src/printer/mod.rs @@ -33,6 +33,7 @@ use std::rc::Rc; use tsv_lang::doc::arena::{DocArena, DocId}; use tsv_lang::{ Comment, EmbedContext, INDENT, OutputBuffer, SharedInterner, SymbolResolver, TAB_WIDTH, + is_format_ignore_directive, is_format_ignore_range_end, is_format_ignore_range_start, }; /// Pending whitespace state - buffers whitespace decisions until next node is known @@ -302,8 +303,8 @@ pub(crate) fn format_svelte(root: &internal::Root, source: &str) -> String { impl<'a> Printer<'a> { /// Check if the last non-whitespace fragment node before `target_start` is - /// a `` comment. - fn has_prettier_ignore_before(&self, fragment: &internal::Fragment, target_start: u32) -> bool { + /// a `` (or `prettier-ignore`) comment. + fn has_format_ignore_before(&self, fragment: &internal::Fragment, target_start: u32) -> bool { let mut last_comment = None; for node in &fragment.nodes { let node_end = node.span().end; @@ -323,7 +324,7 @@ impl<'a> Printer<'a> { } } } - last_comment.is_some_and(|c| c.content.trim() == "prettier-ignore") + last_comment.is_some_and(|c| is_format_ignore_directive(&c.content)) } /// Classify which section a fragment comment should travel with during @@ -335,10 +336,11 @@ impl<'a> Printer<'a> { comment_idx: usize, root: &internal::Root, ) -> CommentSection { - // prettier-ignore-start/end mark ranges within the template — + // format-ignore-start/end mark ranges within the template — // they must stay in the fragment so the range preservation logic sees them - let trimmed = comment.content.trim(); - if trimmed == "prettier-ignore-start" || trimmed == "prettier-ignore-end" { + if is_format_ignore_range_start(&comment.content) + || is_format_ignore_range_end(&comment.content) + { return CommentSection::Template; } @@ -487,7 +489,7 @@ impl<'a> Printer<'a> { self.write("\n"); // Blank line between sections } self.print_section_comments(comments, &root.fragment, script.span.start); - if self.has_prettier_ignore_before(&root.fragment, script.span.start) { + if self.has_format_ignore_before(&root.fragment, script.span.start) { self.write(script.span.extract(self.source)); self.write("\n"); } else { @@ -516,7 +518,7 @@ impl<'a> Printer<'a> { // Format style (if present) if let Some(style) = &root.css { - let ignore_style = self.has_prettier_ignore_before(&root.fragment, style.span.start); + let ignore_style = self.has_format_ignore_before(&root.fragment, style.span.start); if has_previous_section { self.write("\n"); // Blank line between sections } @@ -802,16 +804,14 @@ impl<'a> Printer<'a> { self.print_comment(comment); - let trimmed = comment.content.trim(); - - // prettier-ignore-start/end: preserve all nodes between as raw source + // format-ignore-start/end: preserve all nodes between as raw source // Only active at root level (nested ranges are treated as regular comments) - if trimmed == "prettier-ignore-start" { - // Find the matching prettier-ignore-end + if is_format_ignore_range_start(&comment.content) { + // Find the matching range-end marker let mut end_idx = None; for j in (i + 1)..fragment.nodes.len() { if let FragmentNode::Comment(end_comment) = &fragment.nodes[j] - && end_comment.content.trim() == "prettier-ignore-end" + && is_format_ignore_range_end(&end_comment.content) { end_idx = Some(j); break; @@ -831,8 +831,8 @@ impl<'a> Printer<'a> { } } - // prettier-ignore: preserve next non-whitespace node as raw source - if trimmed == "prettier-ignore" { + // format-ignore: preserve next non-whitespace node as raw source + if is_format_ignore_directive(&comment.content) { let mut next_idx = i + 1; while next_idx < fragment.nodes.len() { if let FragmentNode::Text(text) = &fragment.nodes[next_idx] diff --git a/crates/tsv_svelte/src/printer/nodes/fragment_doc.rs b/crates/tsv_svelte/src/printer/nodes/fragment_doc.rs index 0353f4b0..90fae95d 100644 --- a/crates/tsv_svelte/src/printer/nodes/fragment_doc.rs +++ b/crates/tsv_svelte/src/printer/nodes/fragment_doc.rs @@ -16,6 +16,7 @@ use crate::printer::Printer; use crate::printer::text::TextAnalysis; use smallvec::SmallVec; use tsv_lang::doc::arena::DocId; +use tsv_lang::is_format_ignore_directive; /// Inline buffer for one output line's docs. Most lines hold only a few, so /// `SmallVec` keeps the common case off the heap. @@ -104,10 +105,10 @@ impl<'a> Printer<'a> { trim_text: bool, ) -> DocId { let mut docs: Vec = Vec::new(); - let mut prettier_ignore_next = false; + let mut format_ignore_next = false; for (i, node) in nodes.iter().enumerate() { - // prettier-ignore: skip whitespace, emit raw source for ignored node - if prettier_ignore_next { + // format-ignore: skip whitespace, emit raw source for ignored node + if format_ignore_next { if let FragmentNode::Text(text) = node && text.raw.is_whitespace_only() { @@ -115,14 +116,14 @@ impl<'a> Printer<'a> { } let raw = node.span().extract(self.source); docs.push(self.d().text_owned(raw.to_string())); - prettier_ignore_next = false; + format_ignore_next = false; continue; } if Self::is_format_ignore_comment(node) { if let Some(doc) = self.build_fragment_node_doc_with_context(node, trim_text) { docs.push(doc); } - prettier_ignore_next = true; + format_ignore_next = true; continue; } @@ -227,13 +228,13 @@ impl<'a> Printer<'a> { let mut child_docs: Vec = Vec::new(); let mut handle_whitespace_of_prev_text = false; - let mut prettier_ignore_next = false; + let mut format_ignore_next = false; for (i, node) in trimmed_nodes.iter().enumerate() { let is_first = i == 0; let is_last = i == trimmed_len - 1; - // prettier-ignore: skip whitespace, emit raw source for ignored node - if prettier_ignore_next { + // format-ignore: skip whitespace, emit raw source for ignored node + if format_ignore_next { if let FragmentNode::Text(text) = node && text.raw.is_whitespace_only() { @@ -242,11 +243,11 @@ impl<'a> Printer<'a> { let raw = node.span().extract(self.source); child_docs.push(d.text_owned(raw.to_string())); handle_whitespace_of_prev_text = false; - prettier_ignore_next = false; + format_ignore_next = false; continue; } if Self::is_format_ignore_comment(node) { - prettier_ignore_next = true; + format_ignore_next = true; } if let FragmentNode::Text(text) = node { @@ -342,16 +343,10 @@ impl<'a> Printer<'a> { /// raw source instead of formatting it. Single recognition point for the three /// `build_nodes_doc_*` accumulation loops. /// - // TODO: also recognize a native `format-ignore` directive (tool-neutral, simpler than - // borrowing prettier's spelling) alongside `prettier-ignore`. This is the one spot to - // extend *for the fragment loops*, but the `"prettier-ignore"` literal is matched in - // several other places that must move in lockstep — `printer/mod.rs` checks it on - // `&HtmlComment` (`has_prettier_ignore_before`, the script/style ignore path) plus the - // `prettier-ignore` / `prettier-ignore-start` / `prettier-ignore-end` range handling. - // When adding the second spelling, hoist the recognition into one shared primitive over - // the trimmed comment text rather than threading a new literal through each site. + // Recognition lives in `tsv_lang::is_format_ignore_directive` — the single source of + // truth for the directive set, shared across all three language printers. fn is_format_ignore_comment(node: &FragmentNode) -> bool { - matches!(node, FragmentNode::Comment(c) if c.content.trim() == "prettier-ignore") + matches!(node, FragmentNode::Comment(c) if is_format_ignore_directive(&c.content)) } /// Handle a text child node - matches prettier-plugin-svelte's handleTextChild @@ -575,10 +570,10 @@ impl<'a> Printer<'a> { // Track if previous text ended with space (for inline-before-block pattern) let mut prev_text_has_trailing_space = false; - let mut prettier_ignore_next = false; + let mut format_ignore_next = false; for (i, node) in trimmed_nodes.iter().enumerate() { - // prettier-ignore: skip whitespace, emit raw source for ignored node - if prettier_ignore_next { + // format-ignore: skip whitespace, emit raw source for ignored node + if format_ignore_next { if let FragmentNode::Text(text) = node && text.raw.is_whitespace_only() { @@ -592,11 +587,11 @@ impl<'a> Printer<'a> { current_line.push(raw_doc); // Don't close the line — let subsequent inline content stay on same line prev_text_has_trailing_space = false; - prettier_ignore_next = false; + format_ignore_next = false; continue; } if Self::is_format_ignore_comment(node) { - prettier_ignore_next = true; + format_ignore_next = true; } let is_block = self.is_block_fragment_node(node); diff --git a/crates/tsv_ts/src/printer/calls/mod.rs b/crates/tsv_ts/src/printer/calls/mod.rs index f7511c10..3acda861 100644 --- a/crates/tsv_ts/src/printer/calls/mod.rs +++ b/crates/tsv_ts/src/printer/calls/mod.rs @@ -192,13 +192,12 @@ impl<'a> Printer<'a> { /// 2. Group nodes by natural break points /// 3. Build doc with conditionalGroup for oneLine/expanded alternatives pub(super) fn build_member_doc(&self, member: &internal::MemberExpression) -> DocId { - // A `// prettier-ignore` attached to this member access (in the gap between + // A format-ignore directive attached to this member access (in the gap between // the object and the property) makes prettier print the entire member // expression verbatim from source — preserving inner call args (numbers, // etc.) that the chain formatter would otherwise reformat. Mirrors // prettier's `hasPrettierIgnore` → verbatim-print behavior. - if self.has_prettier_ignore_in_range(member.object.span().end, member.property.span().start) - { + if self.has_format_ignore_in_range(member.object.span().end, member.property.span().start) { return self.raw_source_doc(member.span); } diff --git a/crates/tsv_ts/src/printer/expressions/blocks.rs b/crates/tsv_ts/src/printer/expressions/blocks.rs index 66c5465b..5f5c820c 100644 --- a/crates/tsv_ts/src/printer/expressions/blocks.rs +++ b/crates/tsv_ts/src/printer/expressions/blocks.rs @@ -140,7 +140,7 @@ impl<'a> Printer<'a> { }; // Build statements (leading comments, blank-line separators, - // prettier-ignore, trailing same-line comments) via the shared walk. + // format-ignore, trailing same-line comments) via the shared walk. let (mut body_parts, _prev_end, prev_stmt_end) = self.build_statement_list_docs( &block.body, block_start, @@ -181,7 +181,7 @@ impl<'a> Printer<'a> { /// walk for block-statement bodies and `namespace`/`module` bodies. /// /// For each statement, appends (in order): blank-line separators, leading - /// comments, the statement doc (or raw source under `prettier-ignore`), and + /// comments, the statement doc (or raw source under format-ignore), and /// trailing same-line comments. `leading_content` (outer comments hoisted into /// the body) is emitted first. /// @@ -248,8 +248,8 @@ impl<'a> Printer<'a> { self.build_leading_comments_with_blank_lines(&leading_comments, stmt_start), ); - // prettier-ignore: emit raw source instead of formatting - if self.has_prettier_ignore_in_range(prev_end, stmt_start) { + // format-ignore: emit raw source instead of formatting + if self.has_format_ignore_in_range(prev_end, stmt_start) { body_parts.push(self.raw_source_doc(stmt.span())); } else { body_parts.push(self.build_statement_doc(stmt)); diff --git a/crates/tsv_ts/src/printer/expressions/objects.rs b/crates/tsv_ts/src/printer/expressions/objects.rs index 910ac33c..b15e7da8 100644 --- a/crates/tsv_ts/src/printer/expressions/objects.rs +++ b/crates/tsv_ts/src/printer/expressions/objects.rs @@ -178,9 +178,9 @@ impl<'a> Printer<'a> { parts.push(d.hardline()); } - // Build property doc — a preceding `// prettier-ignore` keeps the + // Build property doc — a preceding format-ignore directive keeps the // property's source verbatim (trailing comment/comma handled normally) - let prop_doc = if self.has_prettier_ignore_in_range(search_start, prop_start) { + let prop_doc = if self.has_format_ignore_in_range(search_start, prop_start) { self.raw_source_doc(prop.span()) } else { self.build_object_property_doc(prop) diff --git a/crates/tsv_ts/src/printer/expressions/patterns.rs b/crates/tsv_ts/src/printer/expressions/patterns.rs index 3938e03b..08abb5b5 100644 --- a/crates/tsv_ts/src/printer/expressions/patterns.rs +++ b/crates/tsv_ts/src/printer/expressions/patterns.rs @@ -599,9 +599,9 @@ impl<'a> Printer<'a> { self.build_leading_comments_with_blank_lines(&leading_comments, prop_start), ); - // A preceding `// prettier-ignore` keeps the property's source verbatim + // A preceding format-ignore directive keeps the property's source verbatim // (trailing comment/comma handled normally) - if self.has_prettier_ignore_in_range(prev_end, prop_start) { + if self.has_format_ignore_in_range(prev_end, prop_start) { prop_parts.push(self.raw_source_doc(prop.span())); } else { prop_parts.push(self.build_object_pattern_property_doc(prop)); diff --git a/crates/tsv_ts/src/printer/mod.rs b/crates/tsv_ts/src/printer/mod.rs index 7e913b4b..46f25ccd 100644 --- a/crates/tsv_ts/src/printer/mod.rs +++ b/crates/tsv_ts/src/printer/mod.rs @@ -67,7 +67,7 @@ use tsv_lang::{ self, arena::{DocArena, DocId}, }, - has_comments_in_range, has_line_comments_in_range, printing, + has_comments_in_range, has_line_comments_in_range, is_format_ignore_directive, printing, }; /// The parent context that routes a curried arrow chain (`(a) => (b) => …`) @@ -850,14 +850,14 @@ impl<'a> Printer<'a> { None } - /// Check if any comment in the range has content "prettier-ignore". + /// Check if any comment in the range is a format-ignore directive. /// Used to emit the next node as raw source text instead of formatting. - fn has_prettier_ignore_in_range(&self, start: u32, end: u32) -> bool { - comments_in_range(self.comments, start, end).any(|c| c.content.trim() == "prettier-ignore") + fn has_format_ignore_in_range(&self, start: u32, end: u32) -> bool { + comments_in_range(self.comments, start, end).any(|c| is_format_ignore_directive(&c.content)) } /// Emit a node's source span verbatim. Used to round-trip the source of a - /// `// prettier-ignore`d node (statement, block statement, object/pattern + /// format-ignored node (statement, block statement, object/pattern /// property, class/enum/interface/type-literal member) instead of /// reformatting it. /// Trailing whitespace is trimmed: a node's significant tokens never end in @@ -869,7 +869,7 @@ impl<'a> Printer<'a> { } /// Emit `[start, end)` of the source verbatim. Like `raw_source_doc` but for a - /// `// prettier-ignore`d member whose verbatim slice must exclude a separator + /// format-ignored member whose verbatim slice must exclude a separator /// the surrounding loop emits itself (e.g. a type-literal member's `;`), so /// the terminator isn't duplicated. fn raw_source_range(&self, start: u32, end: u32) -> DocId { diff --git a/crates/tsv_ts/src/printer/program.rs b/crates/tsv_ts/src/printer/program.rs index db7e63c8..8310e0cc 100644 --- a/crates/tsv_ts/src/printer/program.rs +++ b/crates/tsv_ts/src/printer/program.rs @@ -1,7 +1,7 @@ // Program-level printing for TypeScript // // Top-level orchestration: statement iteration with blank-line preservation, -// leading/trailing comment placement, and `// prettier-ignore` raw emission. +// leading/trailing comment placement, and format-ignore raw emission. use crate::ast::internal; use tsv_lang::{ @@ -98,7 +98,7 @@ impl<'a> Printer<'a> { } // Leading comments (allow inline comments since statement will be printed) - let has_ignore = self.has_prettier_ignore_in_range(prev_end, statement.span().start); + let has_ignore = self.has_format_ignore_in_range(prev_end, statement.span().start); if let Some(leading_doc) = self.build_leading_comments_doc( prev_end, statement.span().start, @@ -108,7 +108,7 @@ impl<'a> Printer<'a> { parts.push(leading_doc); } - // Statement — if preceded by prettier-ignore, emit raw source + // Statement — if preceded by a format-ignore directive, emit raw source if has_ignore { parts.push(self.raw_source_doc(statement.span())); } else { diff --git a/crates/tsv_ts/src/printer/statements/class.rs b/crates/tsv_ts/src/printer/statements/class.rs index 0407ece1..e62e1bc8 100644 --- a/crates/tsv_ts/src/printer/statements/class.rs +++ b/crates/tsv_ts/src/printer/statements/class.rs @@ -289,9 +289,9 @@ impl<'a> Printer<'a> { member_parts .extend(self.build_leading_comments_with_blank_lines(&comments, member_start)); - // A preceding `// prettier-ignore` keeps the member's source verbatim - // (matches prettier). The member span includes its trailing `;`. - let member_doc = if self.has_prettier_ignore_in_range(prev_end, member_start) { + // A preceding format-ignore directive keeps the member's source verbatim. + // The member span includes its trailing `;`. + let member_doc = if self.has_format_ignore_in_range(prev_end, member_start) { self.raw_source_doc(member.span()) } else { self.build_class_member_doc(member) diff --git a/crates/tsv_ts/src/printer/statements/type_declarations.rs b/crates/tsv_ts/src/printer/statements/type_declarations.rs index 121ccc7c..5b46cd85 100644 --- a/crates/tsv_ts/src/printer/statements/type_declarations.rs +++ b/crates/tsv_ts/src/printer/statements/type_declarations.rs @@ -945,9 +945,9 @@ impl<'a> Printer<'a> { self.build_leading_comments_with_blank_lines(&leading_comments, member_start), ); - // A preceding `// prettier-ignore` keeps the member's source verbatim - // (matches prettier). The member span includes its trailing `;`. - let member_doc = if self.has_prettier_ignore_in_range(prev_end, member_start) { + // A preceding format-ignore directive keeps the member's source verbatim. + // The member span includes its trailing `;`. + let member_doc = if self.has_format_ignore_in_range(prev_end, member_start) { self.raw_source_doc(member.span()) } else { self.build_type_element_doc(member) @@ -1123,10 +1123,10 @@ impl<'a> Printer<'a> { } } - // A preceding `// prettier-ignore` keeps the member's source - // verbatim (matches prettier). The member span excludes the + // A preceding format-ignore directive keeps the member's source + // verbatim. The member span excludes the // trailing `,`, which the loop still appends below. - let member_doc = if self.has_prettier_ignore_in_range(prev_end, member_start) { + let member_doc = if self.has_format_ignore_in_range(prev_end, member_start) { self.raw_source_doc(member.span) } else { self.build_enum_member_doc(member) @@ -1350,7 +1350,7 @@ impl<'a> Printer<'a> { parts.push(d.concat(&brace_line_prefix)); // Shared per-statement walk (leading comments, blank-line - // separators, prettier-ignore, trailing same-line comments) — + // separators, format-ignore, trailing same-line comments) — // same as block-statement bodies. let body_start = block.span.start + 1; // After opening '{' let body_end = block.span.end.saturating_sub(1); // Before '}' diff --git a/crates/tsv_ts/src/printer/types/type_literal.rs b/crates/tsv_ts/src/printer/types/type_literal.rs index 0dda6e47..efe52994 100644 --- a/crates/tsv_ts/src/printer/types/type_literal.rs +++ b/crates/tsv_ts/src/printer/types/type_literal.rs @@ -566,10 +566,10 @@ impl<'a> Printer<'a> { is_first, delimiter_pull_pos, )); - // A preceding `// prettier-ignore` keeps the member's source - // verbatim (matches prettier). Use the content span (no trailing + // A preceding format-ignore directive keeps the member's source + // verbatim. Use the content span (no trailing // `;`); the loop's semicolon handling below re-adds the `;`. - let member_doc = if self.has_prettier_ignore_in_range(prev_end, m.span().start) { + let member_doc = if self.has_format_ignore_in_range(prev_end, m.span().start) { self.raw_source_range(m.span().start, member_content_end) } else { self.build_type_member_doc_inner(m) diff --git a/docs/architecture.md b/docs/architecture.md index 22bff239..642a9f16 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -591,6 +591,10 @@ for comment in comments { Higher-level comment attachment helpers were evaluated for extraction to tsv_lang. The current primitives (binary search + classification) are the right abstraction. Per-printer comment handling is language-specific — each language has different rules for where comments attach relative to node types. Re-evaluate if genuine duplication emerges across multiple tools. +### Format-Ignore Directives + +A `format-ignore` / `prettier-ignore` comment suppresses formatting of the construct that follows it (single directive), or — in Svelte templates — a `format-ignore-start` … `format-ignore-end` pair suppresses a range. Recognition is a thin string-level layer over this detached model: `tsv_lang::is_format_ignore_directive` (and `is_format_ignore_range_start` / `_end`) match the trimmed comment text and are the single source of truth for the directive set. Each printer checks them via `comments_in_range()` in the gap before a node and emits the node's raw source span (`span.extract(source)`) instead of a formatted doc. The tsv-native `format-ignore` family is canonical; the `prettier-ignore` family is honored as a drop-in alias. See [directives.md](./directives.md) and [conformance_prettier.md §Format-ignore directive](./conformance_prettier.md#format-ignore-directive). + ## Allocation & Memory tsv runs on the system allocator — no `#[global_allocator]`, no alternative-allocator dependency. The performance posture is structural: each layer avoids allocation by design rather than allocating faster. (An allocator swap remains an open lever; it is a dependency decision, not an architectural one.) diff --git a/docs/checklist_css.md b/docs/checklist_css.md index f4a4edeb..e965fa96 100644 --- a/docs/checklist_css.md +++ b/docs/checklist_css.md @@ -55,6 +55,7 @@ Foundation for all CSS parsing. Spec: `css-syntax-3` (REC) - Comments in at-rules - Consecutive comments - Nested comment closing (spec-compliant) +- `format-ignore` / `prettier-ignore` directive (`/* format-ignore */` emits the next rule/declaration verbatim — see [directives.md](./directives.md)) ### Escapes diff --git a/docs/checklist_svelte.md b/docs/checklist_svelte.md index 162c941c..c9237bdd 100644 --- a/docs/checklist_svelte.md +++ b/docs/checklist_svelte.md @@ -605,6 +605,8 @@ All Svelte 5.x template syntax features are supported, as enumerated below; pars - `svelte-ignore` warnings (``) - Multiple ignores (``) - `@component` JSDoc (``) +- `format-ignore` / `prettier-ignore` directive (`` emits the next node verbatim — see [directives.md](./directives.md)) +- `format-ignore-start` / `-end` range (`` … `` preserves a top-level range) --- diff --git a/docs/checklist_typescript.md b/docs/checklist_typescript.md index 2a452b3b..96f0107e 100644 --- a/docs/checklist_typescript.md +++ b/docs/checklist_typescript.md @@ -35,6 +35,7 @@ Foundation for all parsing. - Nested comment handling - Comment preservation in AST - Leading/trailing comment attachment +- `format-ignore` / `prettier-ignore` directive (`// format-ignore` emits the next construct verbatim — see [directives.md](./directives.md)) ### Identifiers diff --git a/docs/conformance_prettier.md b/docs/conformance_prettier.md index b4e6d0e5..c1fb9a80 100644 --- a/docs/conformance_prettier.md +++ b/docs/conformance_prettier.md @@ -694,6 +694,28 @@ Prettier has multiple stable forms for comment positioning. tsv normalizes to a **Property signature leading line comment**: For a line comment between `:` and an inline-renderable type in a property signature (`{ prop: // c\n X }` — covers identifiers, optional `?:`, readonly, computed keys, generics like `Array`, tuples, function types, `typeof`, etc.), Prettier moves the comment past the implicit `;` to end-of-line (`prop: X; // c`); tsv keeps the comment after `:` and drops the type to a continuation line indented one level (`prop: // c\n\t X;`, the [Uniform Forced-Continuation Indent](#uniform-forced-continuation-indent)). Both forms are stable under their own formatter. A multi-member **union** in the same position is a **match** — both formatters indent the continuation (the non-divergent [annotation](../tests/fixtures/typescript/types/comments/annotation/) fixture); a multi-member **intersection** instead **diverges** (prettier keeps it flush, tsv indents — see [annotation_continuation_indent](../tests/fixtures/typescript/types/comments/annotation_continuation_indent_prettier_divergence/)). Notably, prettier's end-of-line motion is information-destructive when more than one comment touches the property: leading line + trailing line collapses to `f: X; // leading // trailing` (second `//` becomes text inside the first comment); two leading lines merge **and reverse** order (`g: // c1\n // c2\n X;` → `g: // c2 // c1\n X;`); leading line + trailing block reorders to `h: X; /* trailing */ // leading`. tsv preserves each comment at its authored position as a separate comment node. The end-of-line **relocation** is property-signature-only — prettier keeps variable declarations (`const e: // c\n X = ...`) and class properties (`class C { prop: // c\n X }`) in place — but tsv's continuation **indent** is universal across all these contexts, so those keep-in-place cases become an indent-only divergence too (the same [annotation_continuation_indent](../tests/fixtures/typescript/types/comments/annotation_continuation_indent_prettier_divergence/) fixture). +### Format-ignore directive + +A comment can suppress formatting of the construct that follows it. tsv honors its own tool-neutral `format-ignore` family — ``, `// format-ignore`, `/* format-ignore */`, and the range markers `format-ignore-start` / `format-ignore-end` — **in addition to** prettier's `prettier-ignore` family, which tsv keeps for drop-in compatibility (corpus files use it). Recognition is centralized in `tsv_lang::is_format_ignore_directive` and the two range predicates, shared across the TypeScript, CSS, and Svelte printers. + +The `prettier-ignore` family matches prettier exactly (both emit the construct raw), so it needs no divergence fixture of its own. The `format-ignore` family is tsv-native: prettier doesn't recognize it, so prettier reformats the construct while tsv preserves it — that difference is the divergence. Most fixtures pair the spellings in one input: a `prettier-ignore`d construct (preserved by both tools, so unchanged in `output_prettier`) sits beside a `format-ignore`d one (reformatted only by prettier), making the `format-ignore` construct the sole divergence and doubling as a drop-in-compat check. The `basic` (template node) and `js_css` (embedded ` + + + + +
+``` + +The comment delimiters follow the host language — `//` or `/* … */` in +TypeScript, `/* … */` in CSS, and `` in Svelte templates. + +## `format-ignore-start` / `format-ignore-end` + +In Svelte templates, a pair of range markers preserves every node between them: + +```svelte + +
hand laid out
+ and this too + +``` + +A range only takes effect at the top level of the template; markers nested inside +an element are treated as ordinary comments. + +## `prettier-ignore` compatibility + +Because tsv is a drop-in replacement for prettier, it also honors the +`prettier-ignore` family — `prettier-ignore`, `prettier-ignore-start`, and +`prettier-ignore-end` — identically. `format-ignore` is the canonical tsv +spelling; `prettier-ignore` is kept so existing codebases keep working unchanged. +Either spelling works in any position. + +## See also + +- [conformance_prettier.md §Format-ignore directive](./conformance_prettier.md#format-ignore-directive) — why the `format-ignore` spelling diverges from prettier diff --git a/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/README.md b/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/README.md new file mode 100644 index 00000000..d5bffa5b --- /dev/null +++ b/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/README.md @@ -0,0 +1,30 @@ +# format_ignore_prettier_divergence + +`/* format-ignore */` suppresses formatting of the next rule — tsv's +tool-neutral spelling of the directive, honored alongside `prettier-ignore`. +This is the **standalone** CSS path (`.css`, parsed by Svelte's `parseCss` and +formatted by `tsv_css` directly), the companion to the Svelte-embedded coverage +in +[svelte/syntax/format_ignore/css_nested](../../../../svelte/syntax/format_ignore/css_nested_prettier_divergence/). + +This fixture pairs a `format-ignore` case with a `prettier-ignore` control to +show the divergence precisely. tsv honors **both** spellings, so `input.css` +keeps both rules verbatim (idempotent). Prettier honors only its own +`prettier-ignore`, so in `output_prettier.css` the `prettier-ignore`d rule is +preserved unchanged while the `format-ignore`d one is expanded and normalized — +that one rule is the entire divergence: + +```css +/* format-ignore */ +.a { color: red; } /* tsv keeps; prettier expands + normalizes */ + +/* prettier-ignore */ +.b { color: blue; } /* tsv keeps; prettier keeps (recognized) */ +``` + +## Reason + +`format-ignore` is a tsv-native directive (the `prettier-ignore` family is also +honored for drop-in compatibility). See +[conformance_prettier.md §Format-ignore directive](../../../../../../docs/conformance_prettier.md#format-ignore-directive) +and [directives.md](../../../../../../docs/directives.md). diff --git a/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/expected.json b/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/expected.json new file mode 100644 index 00000000..fff56bb0 --- /dev/null +++ b/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/expected.json @@ -0,0 +1,196 @@ +{ + "type": "StyleSheetFile", + "start": 0, + "end": 121, + "children": [ + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 20, + "end": 22, + "children": [ + { + "type": "ComplexSelector", + "start": 20, + "end": 22, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "ClassSelector", + "name": "a", + "start": 20, + "end": 22 + } + ], + "start": 20, + "end": 22, + "metadata": { + "is_global": false, + "is_global_like": false, + "scoped": false + } + } + ], + "metadata": { + "rule": null, + "is_global": false, + "used": false + } + } + ] + }, + "block": { + "type": "Block", + "start": 25, + "end": 46, + "children": [ + { + "type": "Declaration", + "start": 29, + "end": 41, + "property": "color", + "value": "red" + } + ] + }, + "start": 20, + "end": 46, + "metadata": { + "parent_rule": null, + "has_local_selectors": false, + "has_global_selectors": false, + "is_global_block": false + } + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 70, + "end": 72, + "children": [ + { + "type": "ComplexSelector", + "start": 70, + "end": 72, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "ClassSelector", + "name": "b", + "start": 70, + "end": 72 + } + ], + "start": 70, + "end": 72, + "metadata": { + "is_global": false, + "is_global_like": false, + "scoped": false + } + } + ], + "metadata": { + "rule": null, + "is_global": false, + "used": false + } + } + ] + }, + "block": { + "type": "Block", + "start": 75, + "end": 97, + "children": [ + { + "type": "Declaration", + "start": 79, + "end": 92, + "property": "color", + "value": "blue" + } + ] + }, + "start": 70, + "end": 97, + "metadata": { + "parent_rule": null, + "has_local_selectors": false, + "has_global_selectors": false, + "is_global_block": false + } + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 99, + "end": 101, + "children": [ + { + "type": "ComplexSelector", + "start": 99, + "end": 101, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "ClassSelector", + "name": "c", + "start": 99, + "end": 101 + } + ], + "start": 99, + "end": 101, + "metadata": { + "is_global": false, + "is_global_like": false, + "scoped": false + } + } + ], + "metadata": { + "rule": null, + "is_global": false, + "used": false + } + } + ] + }, + "block": { + "type": "Block", + "start": 102, + "end": 120, + "children": [ + { + "type": "Declaration", + "start": 105, + "end": 117, + "property": "color", + "value": "green" + } + ] + }, + "start": 99, + "end": 120, + "metadata": { + "parent_rule": null, + "has_local_selectors": false, + "has_global_selectors": false, + "is_global_block": false + } + } + ] +} diff --git a/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/input.css b/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/input.css new file mode 100644 index 00000000..33dae362 --- /dev/null +++ b/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/input.css @@ -0,0 +1,9 @@ +/* format-ignore */ +.a { color: red; } + +/* prettier-ignore */ +.b { color: blue; } + +.c { + color: green; +} diff --git a/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/output_prettier.css b/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/output_prettier.css new file mode 100644 index 00000000..a5d0fc60 --- /dev/null +++ b/tests/fixtures/css/syntax/comments/format_ignore_prettier_divergence/output_prettier.css @@ -0,0 +1,11 @@ +/* format-ignore */ +.a { + color: red; +} + +/* prettier-ignore */ +.b { color: blue; } + +.c { + color: green; +} diff --git a/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/README.md b/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/README.md new file mode 100644 index 00000000..ecb0814a --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/README.md @@ -0,0 +1,31 @@ +# basic_format_ignore_prettier_divergence + +`` suppresses formatting of the next template node — tsv's +tool-neutral spelling of the directive, honored alongside `prettier-ignore`. +Prettier doesn't recognize `format-ignore`, so it reformats the element anyway. + +This fixture pairs a `format-ignore`d element with a `prettier-ignore` control to +show the divergence precisely. tsv honors **both** spellings, so `input.svelte` +keeps both elements verbatim (idempotent). Prettier honors only its own +`prettier-ignore`, so in `output_prettier.svelte` the `prettier-ignore`d element +is preserved unchanged while the `format-ignore`d one is collapsed onto one line — +that one element is the entire divergence: + +```svelte + +
+ text +
+ + +
+ text +
+``` + +## Reason + +`format-ignore` is a tsv-native directive (the `prettier-ignore` family is also +honored for drop-in compatibility). See +[conformance_prettier.md §Format-ignore directive](../../../../../../docs/conformance_prettier.md#format-ignore-directive) +and [directives.md](../../../../../../docs/directives.md). diff --git a/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/expected.json b/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/expected.json new file mode 100644 index 00000000..247aa3ec --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/expected.json @@ -0,0 +1,285 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 194, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Comment", + "start": 0, + "end": 22, + "data": " format-ignore " + }, + { + "type": "Text", + "start": 22, + "end": 23, + "raw": "\n", + "data": "\n" + }, + { + "type": "RegularElement", + "start": 23, + "end": 81, + "name": "div", + "name_loc": { + "start": { + "line": 2, + "column": 1, + "character": 24 + }, + "end": { + "line": 2, + "column": 4, + "character": 27 + } + }, + "attributes": [ + { + "type": "Attribute", + "start": 31, + "end": 40, + "name": "class", + "name_loc": { + "start": { + "line": 2, + "column": 8, + "character": 31 + }, + "end": { + "line": 2, + "column": 13, + "character": 36 + } + }, + "value": [ + { + "start": 38, + "end": 39, + "type": "Text", + "raw": "a", + "data": "a" + } + ] + }, + { + "type": "Attribute", + "start": 45, + "end": 62, + "name": "data-attr", + "name_loc": { + "start": { + "line": 2, + "column": 22, + "character": 45 + }, + "end": { + "line": 2, + "column": 31, + "character": 54 + } + }, + "value": [ + { + "start": 56, + "end": 61, + "type": "Text", + "raw": "value", + "data": "value" + } + ] + } + ], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 67, + "end": 75, + "raw": "\n text\n", + "data": "\n text\n" + } + ] + } + }, + { + "type": "Text", + "start": 81, + "end": 83, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Comment", + "start": 83, + "end": 107, + "data": " prettier-ignore " + }, + { + "type": "Text", + "start": 107, + "end": 108, + "raw": "\n", + "data": "\n" + }, + { + "type": "RegularElement", + "start": 108, + "end": 166, + "name": "div", + "name_loc": { + "start": { + "line": 7, + "column": 1, + "character": 109 + }, + "end": { + "line": 7, + "column": 4, + "character": 112 + } + }, + "attributes": [ + { + "type": "Attribute", + "start": 116, + "end": 125, + "name": "class", + "name_loc": { + "start": { + "line": 7, + "column": 8, + "character": 116 + }, + "end": { + "line": 7, + "column": 13, + "character": 121 + } + }, + "value": [ + { + "start": 123, + "end": 124, + "type": "Text", + "raw": "b", + "data": "b" + } + ] + }, + { + "type": "Attribute", + "start": 130, + "end": 147, + "name": "data-attr", + "name_loc": { + "start": { + "line": 7, + "column": 22, + "character": 130 + }, + "end": { + "line": 7, + "column": 31, + "character": 139 + } + }, + "value": [ + { + "start": 141, + "end": 146, + "type": "Text", + "raw": "value", + "data": "value" + } + ] + } + ], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 152, + "end": 160, + "raw": "\n text\n", + "data": "\n text\n" + } + ] + } + }, + { + "type": "Text", + "start": 166, + "end": 168, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 168, + "end": 193, + "name": "div", + "name_loc": { + "start": { + "line": 11, + "column": 1, + "character": 169 + }, + "end": { + "line": 11, + "column": 4, + "character": 172 + } + }, + "attributes": [ + { + "type": "Attribute", + "start": 173, + "end": 182, + "name": "class", + "name_loc": { + "start": { + "line": 11, + "column": 5, + "character": 173 + }, + "end": { + "line": 11, + "column": 10, + "character": 178 + } + }, + "value": [ + { + "start": 180, + "end": 181, + "type": "Text", + "raw": "c", + "data": "c" + } + ] + } + ], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 183, + "end": 187, + "raw": "text", + "data": "text" + } + ] + } + } + ] + }, + "options": null, + "comments": [] +} diff --git a/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/input.svelte b/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/input.svelte new file mode 100644 index 00000000..a3b92ba0 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/input.svelte @@ -0,0 +1,11 @@ + +
+ text +
+ + +
+ text +
+ +
text
diff --git a/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/output_prettier.svelte b/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..b6775901 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/basic_prettier_divergence/output_prettier.svelte @@ -0,0 +1,9 @@ + +
text
+ + +
+ text +
+ +
text
diff --git a/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/README.md b/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/README.md new file mode 100644 index 00000000..fe7c6746 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/README.md @@ -0,0 +1,34 @@ +# css_nested_format_ignore_prettier_divergence + +`/* format-ignore */` works at every CSS nesting level — tsv's tool-neutral +spelling of the directive, honored alongside `prettier-ignore`. This fixture +covers a `format-ignore`d **rule** nested inside `@media` (the at-rule block +path) and a `format-ignore`d **declaration** inside a nested rule. Prettier +doesn't recognize `format-ignore`, so it reformats both anyway. + +tsv preserves the marked rule / declaration verbatim: + +```svelte + +``` + +Prettier (see `output_prettier.svelte`) expands the rule and collapses the +declaration's spacing, as if the directive weren't there. + +## Reason + +`format-ignore` is a tsv-native directive (the `prettier-ignore` family is also +honored for drop-in compatibility). See +[conformance_prettier.md §Format-ignore directive](../../../../../../docs/conformance_prettier.md#format-ignore-directive) +and [directives.md](../../../../../../docs/directives.md). diff --git a/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/expected.json b/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/expected.json new file mode 100644 index 00000000..e391b11d --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/expected.json @@ -0,0 +1,141 @@ +{ + "css": { + "type": "StyleSheet", + "start": 0, + "end": 163, + "attributes": [], + "children": [ + { + "type": "Atrule", + "start": 9, + "end": 154, + "name": "media", + "prelude": "screen", + "block": { + "type": "Block", + "start": 23, + "end": 154, + "children": [ + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 49, + "end": 52, + "children": [ + { + "type": "ComplexSelector", + "start": 49, + "end": 52, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "TypeSelector", + "name": "div", + "start": 49, + "end": 52 + } + ], + "start": 49, + "end": 52 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 55, + "end": 76, + "children": [ + { + "type": "Declaration", + "start": 59, + "end": 71, + "property": "color", + "value": "red" + } + ] + }, + "start": 49, + "end": 76 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 80, + "end": 84, + "children": [ + { + "type": "ComplexSelector", + "start": 80, + "end": 84, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "TypeSelector", + "name": "span", + "start": 80, + "end": 84 + } + ], + "start": 80, + "end": 84 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 85, + "end": 151, + "children": [ + { + "type": "Declaration", + "start": 113, + "end": 131, + "property": "background", + "value": "blue" + }, + { + "type": "Declaration", + "start": 136, + "end": 146, + "property": "color", + "value": "red" + } + ] + }, + "start": 80, + "end": 151 + } + ] + } + } + ], + "content": { + "start": 7, + "end": 155, + "styles": "\n\t@media screen {\n\t\t/* format-ignore */\n\t\tdiv { color: red; }\n\n\t\tspan {\n\t\t\t/* format-ignore */\n\t\t\tbackground: blue;\n\t\t\tcolor: red;\n\t\t}\n\t}\n", + "comment": null + } + }, + "js": [], + "start": 0, + "end": 164, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [] +} diff --git a/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/input.svelte b/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/input.svelte new file mode 100644 index 00000000..cc402334 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/input.svelte @@ -0,0 +1,12 @@ + diff --git a/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/output_prettier.svelte b/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..dbb2db8e --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/css_nested_prettier_divergence/output_prettier.svelte @@ -0,0 +1,14 @@ + diff --git a/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/README.md b/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/README.md new file mode 100644 index 00000000..fd59d0bd --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/README.md @@ -0,0 +1,39 @@ +# js_css_format_ignore_prettier_divergence + +`// format-ignore` / `/* format-ignore */` suppress formatting of the next +construct — tsv's tool-neutral spelling of the directive, honored alongside +`prettier-ignore`. The directive works in ` + + +``` + +## Reason + +`format-ignore` is a tsv-native directive (the `prettier-ignore` family is also +honored for drop-in compatibility). See +[conformance_prettier.md §Format-ignore directive](../../../../../../docs/conformance_prettier.md#format-ignore-directive) +and [directives.md](../../../../../../docs/directives.md). diff --git a/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/expected.json b/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/expected.json new file mode 100644 index 00000000..5c11073f --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/expected.json @@ -0,0 +1,914 @@ +{ + "css": { + "type": "StyleSheet", + "start": 228, + "end": 410, + "attributes": [], + "children": [ + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 258, + "end": 261, + "children": [ + { + "type": "ComplexSelector", + "start": 258, + "end": 261, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "TypeSelector", + "name": "div", + "start": 258, + "end": 261 + } + ], + "start": 258, + "end": 261 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 264, + "end": 307, + "children": [ + { + "type": "Declaration", + "start": 268, + "end": 280, + "property": "color", + "value": "red" + }, + { + "type": "Declaration", + "start": 284, + "end": 302, + "property": "background", + "value": "blue" + } + ] + }, + "start": 258, + "end": 307 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 333, + "end": 334, + "children": [ + { + "type": "ComplexSelector", + "start": 333, + "end": 334, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "TypeSelector", + "name": "p", + "start": 333, + "end": 334 + } + ], + "start": 333, + "end": 334 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 337, + "end": 375, + "children": [ + { + "type": "Declaration", + "start": 341, + "end": 355, + "property": "color", + "value": "green" + }, + { + "type": "Declaration", + "start": 359, + "end": 370, + "property": "margin", + "value": "0" + } + ] + }, + "start": 333, + "end": 375 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 378, + "end": 382, + "children": [ + { + "type": "ComplexSelector", + "start": 378, + "end": 382, + "children": [ + { + "type": "RelativeSelector", + "combinator": null, + "selectors": [ + { + "type": "TypeSelector", + "name": "span", + "start": 378, + "end": 382 + } + ], + "start": 378, + "end": 382 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 383, + "end": 401, + "children": [ + { + "type": "Declaration", + "start": 387, + "end": 397, + "property": "color", + "value": "red" + } + ] + }, + "start": 378, + "end": 401 + } + ], + "content": { + "start": 235, + "end": 402, + "styles": "\n\t/* format-ignore */\n\tdiv { color: red; background: blue; }\n\n\t/* prettier-ignore */\n\tp { color: green; margin: 0; }\n\n\tspan {\n\t\tcolor: red;\n\t}\n", + "comment": null + } + }, + "js": [], + "start": 0, + "end": 411, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 226, + "end": 228, + "raw": "\n\n", + "data": "\n\n" + } + ] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " format-ignore", + "start": 10, + "end": 26, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 17 + } + } + }, + { + "type": "Block", + "value": " format-ignore ", + "start": 71, + "end": 90, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 20 + } + } + }, + { + "type": "Line", + "value": " prettier-ignore", + "start": 130, + "end": 148, + "loc": { + "start": { + "line": 8, + "column": 1 + }, + "end": { + "line": 8, + "column": 19 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 226, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 217, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 12, + "column": 9 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 28, + "end": 68, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 41 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 36, + "end": 67, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 40 + } + }, + "id": { + "type": "Identifier", + "start": 36, + "end": 37, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 10 + } + }, + "name": "a" + }, + "init": { + "type": "ObjectExpression", + "start": 43, + "end": 67, + "loc": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 3, + "column": 40 + } + }, + "properties": [ + { + "type": "Property", + "start": 47, + "end": 53, + "loc": { + "start": { + "line": 3, + "column": 20 + }, + "end": { + "line": 3, + "column": 26 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 47, + "end": 48, + "loc": { + "start": { + "line": 3, + "column": 20 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "name": "b" + }, + "value": { + "type": "Literal", + "start": 52, + "end": 53, + "loc": { + "start": { + "line": 3, + "column": 25 + }, + "end": { + "line": 3, + "column": 26 + } + }, + "value": 1, + "raw": "1" + }, + "kind": "init" + }, + { + "type": "Property", + "start": 57, + "end": 63, + "loc": { + "start": { + "line": 3, + "column": 30 + }, + "end": { + "line": 3, + "column": 36 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 57, + "end": 58, + "loc": { + "start": { + "line": 3, + "column": 30 + }, + "end": { + "line": 3, + "column": 31 + } + }, + "name": "c" + }, + "value": { + "type": "Literal", + "start": 62, + "end": 63, + "loc": { + "start": { + "line": 3, + "column": 35 + }, + "end": { + "line": 3, + "column": 36 + } + }, + "value": 2, + "raw": "2" + }, + "kind": "init" + } + ] + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " format-ignore", + "start": 10, + "end": 26 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 92, + "end": 127, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 6, + "column": 36 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 100, + "end": 126, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 35 + } + }, + "id": { + "type": "Identifier", + "start": 100, + "end": 101, + "loc": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 10 + } + }, + "name": "d" + }, + "init": { + "type": "ArrayExpression", + "start": 107, + "end": 126, + "loc": { + "start": { + "line": 6, + "column": 16 + }, + "end": { + "line": 6, + "column": 35 + } + }, + "elements": [ + { + "type": "Literal", + "start": 111, + "end": 112, + "loc": { + "start": { + "line": 6, + "column": 20 + }, + "end": { + "line": 6, + "column": 21 + } + }, + "value": 1, + "raw": "1" + }, + { + "type": "Literal", + "start": 116, + "end": 117, + "loc": { + "start": { + "line": 6, + "column": 25 + }, + "end": { + "line": 6, + "column": 26 + } + }, + "value": 2, + "raw": "2" + }, + { + "type": "Literal", + "start": 121, + "end": 122, + "loc": { + "start": { + "line": 6, + "column": 30 + }, + "end": { + "line": 6, + "column": 31 + } + }, + "value": 3, + "raw": "3" + } + ] + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Block", + "value": " format-ignore ", + "start": 71, + "end": 90 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 150, + "end": 190, + "loc": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 41 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 158, + "end": 189, + "loc": { + "start": { + "line": 9, + "column": 9 + }, + "end": { + "line": 9, + "column": 40 + } + }, + "id": { + "type": "Identifier", + "start": 158, + "end": 159, + "loc": { + "start": { + "line": 9, + "column": 9 + }, + "end": { + "line": 9, + "column": 10 + } + }, + "name": "h" + }, + "init": { + "type": "ObjectExpression", + "start": 165, + "end": 189, + "loc": { + "start": { + "line": 9, + "column": 16 + }, + "end": { + "line": 9, + "column": 40 + } + }, + "properties": [ + { + "type": "Property", + "start": 169, + "end": 175, + "loc": { + "start": { + "line": 9, + "column": 20 + }, + "end": { + "line": 9, + "column": 26 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 169, + "end": 170, + "loc": { + "start": { + "line": 9, + "column": 20 + }, + "end": { + "line": 9, + "column": 21 + } + }, + "name": "i" + }, + "value": { + "type": "Literal", + "start": 174, + "end": 175, + "loc": { + "start": { + "line": 9, + "column": 25 + }, + "end": { + "line": 9, + "column": 26 + } + }, + "value": 5, + "raw": "5" + }, + "kind": "init" + }, + { + "type": "Property", + "start": 179, + "end": 185, + "loc": { + "start": { + "line": 9, + "column": 30 + }, + "end": { + "line": 9, + "column": 36 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 179, + "end": 180, + "loc": { + "start": { + "line": 9, + "column": 30 + }, + "end": { + "line": 9, + "column": 31 + } + }, + "name": "j" + }, + "value": { + "type": "Literal", + "start": 184, + "end": 185, + "loc": { + "start": { + "line": 9, + "column": 35 + }, + "end": { + "line": 9, + "column": 36 + } + }, + "value": 6, + "raw": "6" + }, + "kind": "init" + } + ] + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " prettier-ignore", + "start": 130, + "end": 148 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 193, + "end": 216, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 24 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 199, + "end": 215, + "loc": { + "start": { + "line": 11, + "column": 7 + }, + "end": { + "line": 11, + "column": 23 + } + }, + "id": { + "type": "Identifier", + "start": 199, + "end": 200, + "loc": { + "start": { + "line": 11, + "column": 7 + }, + "end": { + "line": 11, + "column": 8 + } + }, + "name": "e" + }, + "init": { + "type": "ObjectExpression", + "start": 203, + "end": 215, + "loc": { + "start": { + "line": 11, + "column": 11 + }, + "end": { + "line": 11, + "column": 23 + } + }, + "properties": [ + { + "type": "Property", + "start": 204, + "end": 208, + "loc": { + "start": { + "line": 11, + "column": 12 + }, + "end": { + "line": 11, + "column": 16 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 204, + "end": 205, + "loc": { + "start": { + "line": 11, + "column": 12 + }, + "end": { + "line": 11, + "column": 13 + } + }, + "name": "f" + }, + "value": { + "type": "Literal", + "start": 207, + "end": 208, + "loc": { + "start": { + "line": 11, + "column": 15 + }, + "end": { + "line": 11, + "column": 16 + } + }, + "value": 1, + "raw": "1" + }, + "kind": "init" + }, + { + "type": "Property", + "start": 210, + "end": 214, + "loc": { + "start": { + "line": 11, + "column": 18 + }, + "end": { + "line": 11, + "column": 22 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 210, + "end": 211, + "loc": { + "start": { + "line": 11, + "column": 18 + }, + "end": { + "line": 11, + "column": 19 + } + }, + "name": "g" + }, + "value": { + "type": "Literal", + "start": 213, + "end": 214, + "loc": { + "start": { + "line": 11, + "column": 21 + }, + "end": { + "line": 11, + "column": 22 + } + }, + "value": 2, + "raw": "2" + }, + "kind": "init" + } + ] + } + } + ], + "kind": "const" + } + ], + "sourceType": "module" + }, + "attributes": [] + } +} diff --git a/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/input.svelte b/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/input.svelte new file mode 100644 index 00000000..ecf2fd61 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/input.svelte @@ -0,0 +1,24 @@ + + + diff --git a/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/output_prettier.svelte b/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..6cc28ce2 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/js_css_prettier_divergence/output_prettier.svelte @@ -0,0 +1,27 @@ + + + diff --git a/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/README.md b/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/README.md new file mode 100644 index 00000000..4265b458 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/README.md @@ -0,0 +1,27 @@ +# range_format_ignore_prettier_divergence + +`` … `` suppress formatting +of every template node between them — tsv's tool-neutral spelling of the range +markers, honored alongside `prettier-ignore-start` / `prettier-ignore-end`. +Prettier doesn't recognize the `format-ignore` family, so it reformats the range +anyway. + +tsv preserves the marked range verbatim: + +```svelte + + +
text1 text2
+ + +``` + +Prettier (see `output_prettier.svelte`) collapses the spacing, as if the markers +weren't there. + +## Reason + +`format-ignore` is a tsv-native directive (the `prettier-ignore` family is also +honored for drop-in compatibility). See +[conformance_prettier.md §Format-ignore directive](../../../../../../docs/conformance_prettier.md#format-ignore-directive) +and [directives.md](../../../../../../docs/directives.md). diff --git a/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/expected.json b/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/expected.json new file mode 100644 index 00000000..633e4231 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/expected.json @@ -0,0 +1,185 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 153, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "RegularElement", + "start": 0, + "end": 15, + "name": "div", + "name_loc": { + "start": { + "line": 1, + "column": 1, + "character": 1 + }, + "end": { + "line": 1, + "column": 4, + "character": 4 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 5, + "end": 9, + "raw": "text", + "data": "text" + } + ] + } + }, + { + "type": "Text", + "start": 15, + "end": 17, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Comment", + "start": 17, + "end": 45, + "data": " format-ignore-start " + }, + { + "type": "Text", + "start": 45, + "end": 47, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 47, + "end": 77, + "name": "div", + "name_loc": { + "start": { + "line": 5, + "column": 1, + "character": 48 + }, + "end": { + "line": 5, + "column": 4, + "character": 51 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 58, + "end": 71, + "raw": " text1 text2 ", + "data": " text1 text2 " + } + ] + } + }, + { + "type": "Text", + "start": 77, + "end": 79, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 79, + "end": 107, + "name": "p", + "name_loc": { + "start": { + "line": 7, + "column": 1, + "character": 80 + }, + "end": { + "line": 7, + "column": 2, + "character": 81 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 82, + "end": 103, + "raw": "\n text3\n text4\n", + "data": "\n text3\n text4\n" + } + ] + } + }, + { + "type": "Text", + "start": 107, + "end": 109, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Comment", + "start": 109, + "end": 135, + "data": " format-ignore-end " + }, + { + "type": "Text", + "start": 135, + "end": 137, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 137, + "end": 152, + "name": "div", + "name_loc": { + "start": { + "line": 14, + "column": 1, + "character": 138 + }, + "end": { + "line": 14, + "column": 4, + "character": 141 + } + }, + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 142, + "end": 146, + "raw": "text", + "data": "text" + } + ] + } + } + ] + }, + "options": null, + "comments": [] +} diff --git a/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/input.svelte b/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/input.svelte new file mode 100644 index 00000000..bec3d14a --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/input.svelte @@ -0,0 +1,14 @@ +
text
+ + + +
text1 text2
+ +

+ text3 + text4 +

+ + + +
text
diff --git a/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/output_prettier.svelte b/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/output_prettier.svelte new file mode 100644 index 00000000..dd99d6c5 --- /dev/null +++ b/tests/fixtures/svelte/syntax/format_ignore/range_prettier_divergence/output_prettier.svelte @@ -0,0 +1,11 @@ +
text
+ + + +
text1 text2
+ +

text3 text4

+ + + +
text
diff --git a/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/README.md b/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/README.md new file mode 100644 index 00000000..fe7cdcfe --- /dev/null +++ b/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/README.md @@ -0,0 +1,33 @@ +# format_ignore_prettier_divergence + +`// format-ignore` / `/* format-ignore */` suppress formatting of the next +statement — tsv's tool-neutral spelling of the directive, honored alongside +`prettier-ignore`. This is the **standalone** TypeScript path (`.ts`, parsed by +acorn-typescript and formatted by `tsv_ts` directly), the companion to the +Svelte-embedded coverage in +[svelte/syntax/format_ignore/js_css](../../../../svelte/syntax/format_ignore/js_css_prettier_divergence/). + +It covers both comment delimiters (`// format-ignore` and `/* format-ignore */`) +and pairs them with a `prettier-ignore` control to show the divergence precisely. +tsv honors **both** spellings, so `input.ts` keeps every marked statement verbatim +(idempotent). Prettier honors only its own `prettier-ignore`, so in +`output_prettier.ts` the `prettier-ignore`d statement is preserved unchanged while +both `format-ignore`d ones are reformatted — those are the entire divergence: + +```ts +// format-ignore +const a = {b: 1, c: 2}; // tsv keeps; prettier reformats → {b: 1, c: 2} + +/* format-ignore */ +const d = [1, 2, 3]; // tsv keeps; prettier reformats → [1, 2, 3] + +// prettier-ignore +const e = {f: 3, g: 4}; // tsv keeps; prettier keeps (recognized) +``` + +## Reason + +`format-ignore` is a tsv-native directive (the `prettier-ignore` family is also +honored for drop-in compatibility). See +[conformance_prettier.md §Format-ignore directive](../../../../../../docs/conformance_prettier.md#format-ignore-directive) +and [directives.md](../../../../../../docs/directives.md). diff --git a/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/expected.json b/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/expected.json new file mode 100644 index 00000000..317c0a9f --- /dev/null +++ b/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/expected.json @@ -0,0 +1,593 @@ +{ + "type": "Program", + "start": 0, + "end": 164, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 11, + "column": 0 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 17, + "end": 46, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 29 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 23, + "end": 45, + "loc": { + "start": { + "line": 2, + "column": 6 + }, + "end": { + "line": 2, + "column": 28 + } + }, + "id": { + "type": "Identifier", + "start": 23, + "end": 24, + "loc": { + "start": { + "line": 2, + "column": 6 + }, + "end": { + "line": 2, + "column": 7 + } + }, + "name": "a" + }, + "init": { + "type": "ObjectExpression", + "start": 27, + "end": 45, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 2, + "column": 28 + } + }, + "properties": [ + { + "type": "Property", + "start": 28, + "end": 34, + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 17 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 28, + "end": 29, + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "name": "b" + }, + "value": { + "type": "Literal", + "start": 33, + "end": 34, + "loc": { + "start": { + "line": 2, + "column": 16 + }, + "end": { + "line": 2, + "column": 17 + } + }, + "value": 1, + "raw": "1" + }, + "kind": "init" + }, + { + "type": "Property", + "start": 38, + "end": 44, + "loc": { + "start": { + "line": 2, + "column": 21 + }, + "end": { + "line": 2, + "column": 27 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 38, + "end": 39, + "loc": { + "start": { + "line": 2, + "column": 21 + }, + "end": { + "line": 2, + "column": 22 + } + }, + "name": "c" + }, + "value": { + "type": "Literal", + "start": 43, + "end": 44, + "loc": { + "start": { + "line": 2, + "column": 26 + }, + "end": { + "line": 2, + "column": 27 + } + }, + "value": 2, + "raw": "2" + }, + "kind": "init" + } + ] + } + } + ], + "kind": "const" + }, + { + "type": "VariableDeclaration", + "start": 68, + "end": 94, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 26 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 74, + "end": 93, + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 25 + } + }, + "id": { + "type": "Identifier", + "start": 74, + "end": 75, + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 7 + } + }, + "name": "d" + }, + "init": { + "type": "ArrayExpression", + "start": 78, + "end": 93, + "loc": { + "start": { + "line": 5, + "column": 10 + }, + "end": { + "line": 5, + "column": 25 + } + }, + "elements": [ + { + "type": "Literal", + "start": 79, + "end": 80, + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 12 + } + }, + "value": 1, + "raw": "1" + }, + { + "type": "Literal", + "start": 85, + "end": 86, + "loc": { + "start": { + "line": 5, + "column": 17 + }, + "end": { + "line": 5, + "column": 18 + } + }, + "value": 2, + "raw": "2" + }, + { + "type": "Literal", + "start": 91, + "end": 92, + "loc": { + "start": { + "line": 5, + "column": 23 + }, + "end": { + "line": 5, + "column": 24 + } + }, + "value": 3, + "raw": "3" + } + ] + } + } + ], + "kind": "const" + }, + { + "type": "VariableDeclaration", + "start": 115, + "end": 144, + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 29 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 121, + "end": 143, + "loc": { + "start": { + "line": 8, + "column": 6 + }, + "end": { + "line": 8, + "column": 28 + } + }, + "id": { + "type": "Identifier", + "start": 121, + "end": 122, + "loc": { + "start": { + "line": 8, + "column": 6 + }, + "end": { + "line": 8, + "column": 7 + } + }, + "name": "e" + }, + "init": { + "type": "ObjectExpression", + "start": 125, + "end": 143, + "loc": { + "start": { + "line": 8, + "column": 10 + }, + "end": { + "line": 8, + "column": 28 + } + }, + "properties": [ + { + "type": "Property", + "start": 126, + "end": 132, + "loc": { + "start": { + "line": 8, + "column": 11 + }, + "end": { + "line": 8, + "column": 17 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 126, + "end": 127, + "loc": { + "start": { + "line": 8, + "column": 11 + }, + "end": { + "line": 8, + "column": 12 + } + }, + "name": "f" + }, + "value": { + "type": "Literal", + "start": 131, + "end": 132, + "loc": { + "start": { + "line": 8, + "column": 16 + }, + "end": { + "line": 8, + "column": 17 + } + }, + "value": 3, + "raw": "3" + }, + "kind": "init" + }, + { + "type": "Property", + "start": 136, + "end": 142, + "loc": { + "start": { + "line": 8, + "column": 21 + }, + "end": { + "line": 8, + "column": 27 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 136, + "end": 137, + "loc": { + "start": { + "line": 8, + "column": 21 + }, + "end": { + "line": 8, + "column": 22 + } + }, + "name": "g" + }, + "value": { + "type": "Literal", + "start": 141, + "end": 142, + "loc": { + "start": { + "line": 8, + "column": 26 + }, + "end": { + "line": 8, + "column": 27 + } + }, + "value": 4, + "raw": "4" + }, + "kind": "init" + } + ] + } + } + ], + "kind": "const" + }, + { + "type": "VariableDeclaration", + "start": 146, + "end": 163, + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 17 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 152, + "end": 162, + "loc": { + "start": { + "line": 10, + "column": 6 + }, + "end": { + "line": 10, + "column": 16 + } + }, + "id": { + "type": "Identifier", + "start": 152, + "end": 153, + "loc": { + "start": { + "line": 10, + "column": 6 + }, + "end": { + "line": 10, + "column": 7 + } + }, + "name": "c" + }, + "init": { + "type": "ObjectExpression", + "start": 156, + "end": 162, + "loc": { + "start": { + "line": 10, + "column": 10 + }, + "end": { + "line": 10, + "column": 16 + } + }, + "properties": [ + { + "type": "Property", + "start": 157, + "end": 161, + "loc": { + "start": { + "line": 10, + "column": 11 + }, + "end": { + "line": 10, + "column": 15 + } + }, + "method": false, + "shorthand": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 157, + "end": 158, + "loc": { + "start": { + "line": 10, + "column": 11 + }, + "end": { + "line": 10, + "column": 12 + } + }, + "name": "h" + }, + "value": { + "type": "Literal", + "start": 160, + "end": 161, + "loc": { + "start": { + "line": 10, + "column": 14 + }, + "end": { + "line": 10, + "column": 15 + } + }, + "value": 1, + "raw": "1" + }, + "kind": "init" + } + ] + } + } + ], + "kind": "const" + } + ], + "sourceType": "module" +} diff --git a/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/input.ts b/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/input.ts new file mode 100644 index 00000000..d8b9e493 --- /dev/null +++ b/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/input.ts @@ -0,0 +1,10 @@ +// format-ignore +const a = {b: 1, c: 2}; + +/* format-ignore */ +const d = [1, 2, 3]; + +// prettier-ignore +const e = {f: 3, g: 4}; + +const c = {h: 1}; diff --git a/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/output_prettier.ts b/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/output_prettier.ts new file mode 100644 index 00000000..57e7c1ba --- /dev/null +++ b/tests/fixtures/typescript/syntax/comments/format_ignore_prettier_divergence/output_prettier.ts @@ -0,0 +1,10 @@ +// format-ignore +const a = {b: 1, c: 2}; + +/* format-ignore */ +const d = [1, 2, 3]; + +// prettier-ignore +const e = {f: 3, g: 4}; + +const c = {h: 1};