Skip to content

Comments

Add format command with prettier-rs and taplo integration#10

Open
macalinao wants to merge 34 commits intomasterfrom
lintel-wt3
Open

Add format command with prettier-rs and taplo integration#10
macalinao wants to merge 34 commits intomasterfrom
lintel-wt3

Conversation

@macalinao
Copy link
Contributor

Summary

  • Introduces lintel fmt / lintel format command for formatting JSON, JSONC, JSON5, YAML, and TOML files
  • Adds prettier-rs crate: Rust-native prettier implementation with Wadler-Lindig document IR, config resolution, and comment preservation for JSON/JSONC/JSON5/YAML
  • Adds lintel-cli-common crate: shared CLI global options (--colors, --verbose, --log-level) reused across binaries
  • Adds lintel-format crate: format command with file routing to prettier-rs or taplo, --check mode, --exclude patterns, and .gitignore-respecting directory walking
  • Updates lintel CLI to use CliGlobalOptions across all subcommands and wires --log-level to tracing and --colors to miette

Test plan

  • cargo check --workspace compiles cleanly
  • cargo clippy --workspace passes with no warnings
  • cargo test --workspace — 183 tests pass
  • cargo run -- fmt --help shows help with global options
  • cargo run -- fmt . formats files
  • cargo run -- fmt --check . check mode works
  • cargo run -p lintel-format -- --help standalone binary works
  • CLI parser tests cover format subcommand, fmt alias, verbose, excludes, log-level, and colors

Introduce three new crates:
- `lintel-cli-common`: shared CLI global options (--colors, --verbose, --log-level)
- `prettier-rs`: Rust-native prettier implementation for JSON/JSONC/JSON5/YAML
  with config resolution, Wadler-Lindig document IR, and comment preservation
- `lintel-format`: format command routing to prettier-rs or taplo by file type,
  with --check mode, --exclude patterns, and directory walking

Update the lintel CLI to use CliGlobalOptions from lintel-cli-common across all
subcommands, add `format`/`fmt` command, and wire up --log-level and --colors
to tracing and miette respectively.
Replace needless raw string hashes and wildcard match arms with
specific variant patterns.
…ication

Import test fixture files from prettier/prettier repository to serve as a
comprehensive test suite for verifying prettier-rs formatting correctness.
Includes .prettierignore to prevent pre-commit hooks from reformatting fixtures.
…ness

Add a comprehensive test harness (prettier_compat.rs, snapshot_parser.rs) that
parses Jest snapshot files from prettier's test fixtures and validates our
formatter output against prettier's expected output.

Major improvements to YAML comment placement and indentation:
- Track per-comment source line, column, and blank_line_before in Comment struct
- Add depth-filtered comment collection to prevent child nodes from capturing
  parent-scope comments (collect_comments_between_lines_at_depth)
- Use content_end instead of event_end for prev_end_line so comments skipped
  by depth filtering remain available for sibling nodes
- Add comment_indent_capped to prevent trailing comments from rendering deeper
  than their structural context allows
- Add comment and blank line support to nested sequence inline formatting
- Fix double-counted blank lines with has_blank_line_immediately_before

Test results: yaml/comment 4→14/17, yaml/spec 289→291/384, all 42 unit tests pass.
Split the monolithic prettier-rs/src/yaml.rs (3,412 lines) into a new
crates/prettier-yaml/ crate with well-organized modules:
- ast.rs: AST node types
- parser.rs: saphyr event-based parser and AST builder
- comments.rs: comment extraction from source text
- printer.rs: main formatting dispatch and scalar formatting
- print/block.rs: block scalar formatting
- print/flow.rs: flow collection formatting
- print/mapping_item.rs: block mapping formatting
- print/misc.rs: indentation and comment helpers
- utilities.rs: node inspection utilities

prettier-rs now delegates YAML formatting to prettier-yaml via a thin
options conversion layer. All test scores are preserved exactly.
Key fixes:
- Implement prettier-ignore support (emit raw source for ignored nodes)
- Fix proseWrap:always for block-folded scalars with explicit indent
- Add splitWithSingleSpace for preserving multi-space runs in prose wrap
- Fix sequence item blank_line_before with block scalars (marker_line)
- Fix leading comment indentation for first entries vs subsequent
- Fix block scalar body detection when tag is on separate line from indicator
- Fix trim() vs trim_start() in folded block rewrap (trailing whitespace)
- Improve explicit key handling with between-comments detection
- Fix flow collection formatting with prettier-ignore and anchors
- Add comment_indent_capped for flow collection comments
# Conflicts:
#	Cargo.lock
#	crates/lintel-cli-common/Cargo.toml
#	crates/lintel-cli-common/src/lib.rs
#	crates/lintel/Cargo.toml
#	crates/lintel/src/main.rs
Improve prettier-yaml compat from 682/712 (95.8%) to 698/712 (98.0%):
- Fix flow sequence trailing comment attachment to last item per line
- Fix closing_comment capture for outermost flow sequences only
- Track block scalar body end for accurate comment positioning
- Restore comment_indent_capped for mapping trailing_comments
- Fix clippy warnings across prettier-yaml and prettier-rs crates
- Add preserve_order feature to serde_json for correct key ordering
- Add useTabs option parsing to snapshot parser
- Add trailing newline normalization to snapshot parser (matching prettier-yaml)
- Fall back to JSONC parser when JSON parsing fails (handles comments)
- Fix clippy warnings in examples and tests
Add `has_explicit_braces` field to MappingNode, detected by checking
whether the saphyr MappingStart span has non-zero length (start != end
indicates explicit `{`). This reliably distinguishes:

- `{ &e e: f }` → explicit braces (MappingStart span covers `{`)
- `a: b` → implicit mapping (zero-length MappingStart span)
- `{ JSON: like }: adjacent` → outer mapping is implicit (no braces)

Use this field in format_flow_sequence_flat/broken to keep braces on
flow mappings that had explicit `{...}` in the source, fixing the
`various-location-of-anchors-in-flow-sequence` test cases.

Also tightens flow_source extraction to only set when has_explicit_braces
is true, avoiding false positives when a mapping key starts with `{`.
Use has_explicit_braces (span.start != span.end) to determine if a
mapping is flow-style, instead of checking source[span.start] == '{'.
The old check had false positives: when a block mapping's key is a
flow mapping like `{ first: Sammy }: value`, the outer mapping's
span.start coincides with the key's `{`, causing the block mapping
to be incorrectly treated as flow.

Fixes spec-example-6-12 where `{ first: Sammy, last: Sosa }:` was
converted from block to flow format.
Use group-min floor snapping for leading comments after scalar values
to normalize off-grid comments (col 1→0 in spec-8-5). After collection
values, use individual ceil-rounding to preserve scope-aligned comments
(col 1→2 in end-comment.yml). Apply same heuristic to mapping trailing
comments after block scalar values at top level.
…nuation (707/712 compat)

Two fixes:
- Collect trailing comments on flow collection opening brackets as
  key_trailing_comment (spec-6-1: `key: [  # comment` now renders as
  `key: # comment\n  [...]`)
- Preserve line structure for segments with backslash continuation in
  double-quoted strings under proseWrap:always (spec-7-5)
# Conflicts:
#	Cargo.lock
#	crates/lintel/Cargo.toml
…/712 compat)

Only capture a trailing comment as key_trailing_comment when the flow
collection opening bracket has no items on the same line. Previously,
`key: [ item, # comment` would incorrectly steal the item's trailing
comment as a key comment. Now checks the source line between the bracket
and the # to verify there's no content.
Move JSON/JSONC/JSON5 formatting from prettier-rs into a standalone
prettier-jsonc crate. Add support for JSON6 features (backtick strings,
octal/binary numbers, numeric separators, sparse arrays, undefined),
numeric key normalization, hex lowercasing, source breakpoint
preservation, and forced trailing commas for array holes.
Moves config types and resolution logic into a standalone `prettier-config`
crate that all formatters share. This eliminates duplicate ProseWrap enums
and manual field-by-field config conversion between prettier-jsonc and
prettier-yaml. Override merging now uses JSON Value merge instead of
typed `apply_to()`. All compat tests pass (101/101 JSON, 702/712 YAML).
…coverage

Split prettier-jsonc into three focused crates:
- wadler-lindig: Doc IR + Wadler-Lindig pretty-printing algorithm
- prettier-json5: standalone JSON5 formatter with hand-written parser
- prettier-jsonc: now handles only JSON and JSONC formats

Added json-test-suite snapshot (444 RFC 8259 compliance test cases) to both
prettier-jsonc (248/296 json/jsonc entries pass) and prettier-json5 (144/148
json5 entries pass). The json5 fallback for Format::Json moved from
prettier-jsonc to prettier-rs umbrella crate.
Implements markdown formatting using comrak for parsing and the shared
wadler-lindig Doc IR for pretty-printing. Supports paragraphs with prose
wrapping (always/never/preserve), ATX and setext headings, fenced and
indented code blocks, ordered/unordered lists with alignment and
renumbering, blockquotes, tables, emphasis, links, images, YAML
frontmatter formatting, and CJK-aware wrapping.

Extends wadler-lindig with Align(n) for fixed-space alignment and
whitespace-only line trimming. Integrates into prettier-rs and
lintel-format for .md/.markdown file support.
Adds language-specific formatting for fenced code blocks with supported
info strings (json, jsonc, json5, yaml, yml, toml). Falls back to
pass-through when the formatter fails or the language is unsupported.

Makes compat tests CI-safe with an explicit KNOWN_FAILURES exclusion list
that documents why each test fails. The harness now fails on unexpected
failures (regressions) and stale exclusions (tests that start passing).

Known failure categories:
- Embedded CSS/JS/TS/Angular formatting (no formatters available)
- JSON object collapsing differences vs prettier's JSON formatter
- prettier-ignore semantics not implemented
- Link reference definitions removed from AST by comrak
- Link/image escape and alt text wrapping differences
- Hard break + prose wrap interaction
- Add `<!-- prettier-ignore -->` support: skips formatting the next block
  node and outputs it as raw source (works in all contexts: top-level,
  blockquotes, list items)
- Add `<!-- prettier-ignore-start/end -->` range support: outputs entire
  range as raw source (top-level only, matching prettier's behavior)
- Fix wadler-lindig `fits()`: separate Hardline (returns true, line ends)
  from BreakParent (returns false, forces group to break). This was making
  BreakParent a no-op, improving JSONC with-comment 12→24/24 and JSON5
  json 20→26/28
- Suppress blank lines between consecutive HTML blocks (prettier behavior)
- Download prettier's markdown/ignore test fixtures (3/3 pass)
Adopt the KNOWN_FAILURES pattern (from prettier-markdown) across
prettier-json5, prettier-jsonc, prettier-yaml, and prettier-rs. Each
known failure is documented with a reason explaining why it differs
from prettier's output.

The test harness now asserts on both unexpected failures (must be added
to KNOWN_FAILURES or fixed) and stale exclusions (must be removed when
tests start passing), replacing the old PRETTIER_STRICT env var gating.
Move the duplicated snapshot parser and test harness code from 5
separate snapshot_parser.rs + prettier_compat.rs files into a shared
prettier-test-harness crate. Each consumer now only contains its
KNOWN_FAILURES list, a thin format closure, and #[test] functions.

Reduces total test infrastructure from ~3000 lines (across 10 files)
to ~1600 lines (5 slim compat files + 1 shared crate).
…ify deps

- Move force_group_break and trim_trailing_whitespace to wadler-lindig
- Delete dead prettier-jsonc json module and its serde deps
- Remove duplicate test fixtures and compat tests from prettier-rs
- Delete debug example and debug_spec_88_ast test
- Hoist resolve_config call in lintel-format format_single_file
- Unify taplo version via workspace dependency
- Remove stale PrettierOptions/YamlFormatOptions type aliases
- Add Cargo.toml metadata to all 8 prettier crates
Bug fixes:
- Fix tab-width column tracking in wadler-lindig printer (pos was counting
  bytes instead of visual columns when using tab indentation)
- Fix escaped quote handling in prettier-yaml flow source extraction
  (backslash-escaped double quotes inside flow scalars were prematurely
  closing the string)
- Handle empty .prettierrc files gracefully (treat as "use all defaults"
  instead of erroring)

Quick wins:
- Strip UTF-8 BOM in prettier-jsonc and prettier-json5 formatters
- Remove dead code: format_tag, line_preceded_by_blank, duplicate
  has_node_props, extern crate alloc, unused _base_value param
- Remove unused tracing dependency from lintel-format
- Add PartialEq/Eq derives to PrettierConfig
- Fix json_merge doc comment (deep -> shallow)
- Simplify identity match in lintel/src/main.rs
- Add prettier-json5 fixtures to .prettierignore
Take higher dependency versions from master while keeping lintel-format
dependency from the feature branch.
Furnish lintel-format, prettier-config, prettier-json5, prettier-jsonc,
prettier-markdown, prettier-rs, prettier-test-harness, prettier-yaml,
and wadler-lindig with descriptions, keywords, categories, READMEs,
and #![doc] attributes.
Swap out the custom JSON/JSONC formatter (jsonc-parser + wadler-lindig
doc IR) for biome's JSON formatter, reducing jsonc.rs from 712 to 60
lines (-871 net lines across the crate).

Dependencies changed:
- Removed: jsonc-parser, wadler-lindig
- Added: biome_json_parser, biome_json_formatter, biome_formatter (0.5.7)
- Pinned: biome_rowan, biome_parser, biome_diagnostics, biome_unicode_table
  to =0.5.7 to avoid cross-version incompatibilities

Known behavioral changes vs the custom formatter:
- Single-quoted strings rejected (biome requires double quotes)
- Unquoted property keys rejected
- \/ escape not preserved (stripped to /)
- No number normalization (verbatim output)
- Bracket spacing always enabled (0.5.7 limitation)

Updated KNOWN_FAILURES: removed 46 stale exclusions that biome now
handles correctly, added 11 new exclusions for the above limitations.
JSON5 is effectively dead — downloads are transitive from Babel and JSONC
covers the only feature people actually use (comments). Remove the dedicated
formatter and all JSON5-only test fixtures/known-failures to reduce maintenance
surface.
Resolve conflicts in Cargo.toml (version bumps + lintel-format dep)
and Cargo.lock (regenerated).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant