feat(lint): JSON wire contract — sort diagnostics, stdin label, name-anchored spans (#202, #203, #211) - #294
Open
dean0x wants to merge 7 commits into
Open
Conversation
…anchored spans (#202, #203, #211) **#211 — Uniform `<stdin>` sentinel across all CLI surfaces** Adds `STDIN_DISPLAY_LABEL: &str = "<stdin>"` to `output.rs` (AD-211-3) as the single definition of the stdin source-identity sentinel. Replaces five scattered conventions: `input.mds` (internal VFS key), bare `stdin`, `<source>` (resolver), and the hardcoded `"<stdin>"` in `build.rs`. - `set_diag_display_path` called after every `lint_str_with` to relabel `diag.file` from `STRING_SOURCE_MAP_LABEL` → `STDIN_DISPLAY_LABEL` at the CLI output boundary (AD-211-4). The internal constant is NOT changed. - `StdinRelabeledError` wrapper overrides `miette::Diagnostic::source_code()` to relabel `<source>` → `<stdin>` in analysis-failure rendered output (AD-211-5), working around the `with_source_code()` fallback semantics in miette-7.6.0. - `emit_analysis_failure_json_or_stderr` gains `stdin_source: Option<(&str, &str)>` parameter; file-mode callers pass `None`. - `build.rs` now references `crate::output::STDIN_DISPLAY_LABEL` instead of a hardcoded `"<stdin>"` literal. **#202 — Diagnostics sorted by byte offset (wire contract)** Adds `sort_diagnostics` (stable sort by `(file, span.offset)`) called in `LintResultBuilder::build` after truncation, before JSON emission (AD-202-1). No-span diagnostics sort to the end of their file group (AD-202-3). The sort is stable so equal-offset diagnostics preserve rule-insertion order (AD-202-2). Fixes the `to_canonical_json` rustdoc to document `fix_edits` in the schema and the ordering guarantee. **#203 — `unused-import` span anchors at the unused name for selective imports** `ImportDirective::Selective` gains `name_offsets: Vec<usize>` (AD-203-1 / PF-012). `parse_import_directive` computes per-name byte offsets in a single pass alongside name collection (no desync possible for sparse inputs like `{ a, , b }`). The corrected delta formula uses `trim_start` (not `trim`) to measure the byte distance from directive start to `{`. `ImportFact` threads `name_offsets` through to `unused_import::check`, which now passes the per-name offset and `name.len()` to `make_diag` for Selective forms. Alias forms are unchanged. Tests: AC-P1-01, AC-P1-14, AC-P1-15, AC-P1-19 — 13 new unit tests + 3 CLI tests. All 1992 nextest + 50 doctests pass. Zero clippy warnings. No control bytes.
…r relabel - `parse_import_directive`: drop `seg_byte_len` local (used only at the end of the loop body) and call `seg.len()` directly on the advance line. - `emit_analysis_failure_json_or_stderr`: inline `named` into the `StdinRelabeledError` struct initialiser; it was created and consumed on consecutive lines with no intervening use. Behaviour is unchanged; fmt and clippy pass; source-hygiene gate clean.
P0 — the AC-P1-04 / §0a leg that was never implemented. `mds check -` and `mds build -` still rendered `[<source>:1:1]` for stdin; reproduced per plan §5 step 1a, then fixed. `StdinRelabeledError` + `relabel_stdin_error` move from `lint.rs` to `output.rs` and are applied at `run_check` and both `compile_str_with_deps_opts` stdin sites. `build::exit_code` unwraps the wrapper so a render-only label swap cannot change a process exit code. P0 — the #202 wire-ordering test passed vacuously. Its fixture produced exactly one diagnostic, so `offsets == sorted` held with the sort deleted. Replaced with a fixture whose offset order inverts `run_rules` dispatch order, plus a non-vacuity guard and an explicit rule-position assertion. P0 — #203's span anchoring had no test for the desync and robustness cases the plan flagged as blocker-class. Added slice-based positive controls (AC-P1-14/15/16) covering empty and trailing comma segments, prefix and path name collisions, irregular whitespace, CRLF and multi-byte prefixes. All verified non-vacuous against planted bugs. P1 — sort key no longer uses a `\u{FFFF}` sentinel for a missing file, which mis-ordered against astral-plane filenames; AD-203-3's `debug_assert_eq!` added at the construction site; the desync fallback now degrades to the whole `@import` span instead of `name.len()` bytes of the keyword. Added: AC-P1-07 (analysis-failure label, both channels), AC-P1-03 (fix-preview sentinel), AC-P1-09/11 (cross-surface order, determinism), AC-P1-10 (files[] path order), AC-P1-12 (truncation is not offset-ranked), AC-P1-18 (formatter safety gate ignores name_offsets), AC-P1-20 (WIRE escaping positive control). P2 — AD-202-x rustdoc IDs and AC-P1-xx test citations corrected against the plan; `main.rs` / `fmt.rs` stdin literals point at `STDIN_DISPLAY_LABEL` (AD-211-3); CHANGELOG rewritten as the wave's single wire-change ledger with a before/after snippet and the AD-211-5 leg.
Scope stdin relabel to the "<source>" sentinel so imported-file errors keep their real path (PF-012). Move the AC-P1-18 name_offsets pin from formatter.rs to structural_eq.rs where imports_eq lives, with a positive control. Add AC-P1-26 clean-stdin, AC-P1-03(c) partial-fix, AC-P1-27 core-label, and AC-P1-24 cross-surface tests.
…arkers BLOCKING-1: cargo fmt --all fixes two rustfmt violations from commit 331a8d3: - crates/mds-cli/tests/cli_lint.rs: two lines merged to one per rustfmt - crates/mds-core/src/lint/rules/structural_eq.rs: comment alignment BLOCKING-2: replace map_or(false, ...) with is_some_and(...) in error.rs:1066 (clippy::unnecessary_map_or on the StdinRelabeledError is_stdin_source method) BLOCKING-3: rebuilt WASM (packages/mds-wasm/dist/node/mds_wasm_bg.wasm, 833763 bytes — within 850000 budget) and NAPI (crates/mds-napi/mds-napi.node) from current branch source to pick up the LintResultBuilder::build sort. Binaries are gitignored; CI builds them from source before testing. npm test --workspaces --if-present now passes; U-L10 confirmed green. BLOCKING-4: resolve AC-P1-26 zero-diagnostic contradiction. AC-P1-26 stated the <stdin> sentinel must appear even in the zero-diagnostic case; the implementation and test emit files:[] for clean stdin (consistent with binding surfaces per AC-P1-06). Chosen fix: amend the criterion (plan doc is gitignored) and document in CHANGELOG that files:[] is the correct output when stdin has zero diagnostics. Updated test comment to remove the ambiguous "scope-out" label and state this as the defined wire contract. All AC-P1-25 gates pass: cargo nextest run --workspace EXIT=0 cargo test --doc 50 passed 0 failed cargo fmt --all --check clean cargo clippy --workspace -- -D warnings EXIT=0 npm test --workspaces --if-present EXIT=0 (U-L10 confirmed green) WASM size: 833763 bytes (budget: 850000, 1.94% headroom). Co-Authored-By: Claude <noreply@anthropic.com>
…c carve-out
AC-P1-26 was amended 2026-08-14 with a zero-diagnostic carve-out: a clean
stdin lint emits {"files":[],...} with no file entry. Test-plan entry 27
(case b) still asserted files[0].file == "<stdin>" including in the
zero-diagnostic case, contradicting the amended AC.
Propagate the carve-out into entry 27: case (b) now asserts files is an
empty array and that "<stdin>" does NOT appear in files[], consistent with
the shipped behaviour verified in stdin_json_clean_source_emits_empty_files_array.
Force-adds the plan file (gitignored by ADR-023) so the amendment is
traceable on this branch.
…e_name The previously-added `pub fn source_label_is_stdin_sentinel() -> bool` encoded CLI presentation vocabulary into the domain crate and introduced a fourth copy of the `"<source>"` sentinel literal (all five findings). Changes: - `resolver.rs:199` SOURCE_LABEL: const -> pub(crate) so siblings can reference the canonical definition instead of copying the literal. - `error.rs`: replace source_label_is_stdin_sentinel() -> bool with the neutral source_name() -> Option<&str> that returns the embedded NamedSource name without any CLI vocabulary. - `sourcemap.rs` map_source_label: reference crate::resolver::SOURCE_LABEL directly, eliminating the third literal copy; future changes to the sentinel are now caught at compile time. - `output.rs` (mds-cli): update relabel_stdin_error to use source_name() and compare "<source>" at the CLI boundary (correct layer). - `api_surface.rs`: add mds_error_source_name_accessor test pinning the new method under ADR-010. - `CHANGELOG.md`: document the renamed method in [Unreleased] public-API section. Verified: clippy -D warnings clean, 79/79 api_surface tests pass, 15/15 stdin cli_lint tests pass, source-hygiene gate exits 0. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes three related issues that all mutate the same JSON wire object, batched into one PR per the wave plan:
--format jsonwire format #211: Uniform `` sentinel across all CLI surfaces — five inconsistent conventions collapsed to oneChanges
#211 — Stdin source-identity sentinel
Plan §5 step 1a — verified build/check `` emission sites (AD-211-5)
These were located empirically (reproducing the `[:1:1]` frame before the relabel landed) and traced to the CLI boundary:
The AD-211-5 rustdoc block in `output.rs` records these with the same verified line numbers.
#202 — Diagnostic ordering
#203 — Span anchoring at name
Alignment fixes (AD-### rustdoc at call sites, AC-P1-18, AC-P1-24 cross-surface, additional test coverage)
Breaking Changes
Reviewer Focus Areas
Test Plan
Tests through the full alignment wave: