8000 fix(cli): tab printing by ematipico · Pull Request #9371 · biomejs/biome · GitHub
[go: up one dir, main page]

Skip to content
8000

fix(cli): tab printing - #9371

Merged
ematipico merged 2 commits into
mainfrom
fix/diagnostic-printing-diff
Mar 6, 2026
Merged

ematipico merged 2 commits into
mainfrom
fix/diagnostic-printing-diff

Conversation

@ematipico
Copy link
Copy Markdown
Member

Summary

Closes #5279

Generated with AI Claude Code. I tried different approaches, and that's the one that seems correct to me. Leading tabs in code diffs are normalised to spaces that have the same width.

Test Plan

Added a new snapshot.

Updated the existing snapshots. They were broken, they showed a mix of tabs and spaces

Docs

@changeset-bot
changeset-bot Bot commented Mar 6, 2026 •
Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 23b0cd6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added A-CLI Area: CLI A-Linter Area: linter L-JavaScript Language: JavaScript and super languages L-JSON Language: JSON and super languages A-Diagnostic Area: diagnostocis labels Mar 6, 2026
@ematipico
ematipico requested review from a team March 6, 2026 17:18
@coderabbitai
coderabbitai Bot commented Mar 6, 2026 •
Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 394a5094-2b50-423d-85ce-a426abbddb4b

📥 Commits

Reviewing files that changed from the base of the PR and between 353b2d0 and 23b0cd6.

📒 Files selected for processing (1)
  • crates/biome_cli/tests/commands/check.rs

Walkthrough

Adds a changeset and regression tests, and updates diagnostic diff rendering to normalise leading tab characters on context lines so they match the display width used for changed lines. Introduces a helper to replace leading tabs with TAB_WIDTH-spaced spaces, propagates a normalize flag through diff formatting, and makes TAB_WIDTH visible to sibling modules (pub(super)). No public API signatures were changed.

Suggested reviewers

  • Netail
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(cli): tab printing' directly relates to the changeset's main purpose of fixing tab alignment in CLI diagnostic diff output.
Description check ✅ Passed The description clearly references issue #5279 and explains the fix normalises leading tabs in code diffs to spaces with equivalent visual width.
Linked Issues check ✅ Passed The PR successfully addresses issue #5279 by normalising leading tabs in diff output to consistent spacing, preventing misalignment between lines with different tab counts.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing tab alignment: a changeset document, regression tests for the fix, and modifications to diff rendering logic with TAB_WIDTH visibility adjustment.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/diagnostic-printing-diff

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_diagnostics/src/display/diff.rs`:
- Around line 484-494: The check in has_changed_lines_with_leading_tabs only
tests the first byte of the diff text (starts_with('\t')) and misses mixed
indentation like "  \tfoo"; change the predicate that examines
line.diffs.first() to inspect the entire leading indentation run and detect any
'\t' inside it (e.g., take the leading chars while they are ' ' or '\t' and
check if any == '\t'), then use that boolean where currently starts_with('\t')
is used; apply the same change to the analogous check around the 616-626 block
so mixed space+tab prefixes are caught and normalized consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 97c85fdf-bff6-4330-8510-04db06422d1e

📥 Commits

Reviewing files that changed from the base of the PR and between 93ea495 and 353b2d0.

⛔ Files ignored due to path filters (45)
  • crates/biome_cli/tests/snapshots/main_commands_check/check_tab_alignment_in_diff_output.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/a11y/noPositiveTabindex/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/noUselessCatch/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/noUselessContinue/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/complexity/noUselessSwitchCase/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noSwitchDeclarations/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedPrivateClassMembers/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedPrivateClassMembers/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedPrivateClassMembers/invalid_issue_7101.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noVueDataObjectDeclaration/invalid-createapp-mount-object-data.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noVueDataObjectDeclaration/invalid-createapp-object-data.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noVueDataObjectDeclaration/invalid-export-default-object-data.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noVueDataObjectDeclaration/invalid-export-default-parenthesized-object-data.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noVueArrowFuncInWatch/invalid-define.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useExhaustiveSwitchCases/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/templateLiteralSpace.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/performance/useGoogleFontPreconnect/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/source/useSortedAttributes/with-comments.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/source/useSortedInterfaceMembers/mixed_literals.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/source/useSortedInterfaceMembers/numeric_literals.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/source/useSortedInterfaceMembers/sortable_after_non_sortable.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/source/useSortedKeys/unsorted.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/noShoutyConstants/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useCollapsedElseIf/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useCollapsedIf/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useConsistentCurlyBraces/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useEnumInitializers/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useNumberNamespace/invalid_properties_value_shorthand.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useSingleVarDeclarator/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useTrimStartEnd/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useUnifiedTypeSignatures/invalidJsdoc.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/suspicious/noApproximativeNumericConstant/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalid.cjs.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalidClass.cjs.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalidFunction.cjs.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalidFunction.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/suppression/a11y/noPositiveTabindex/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/suppression/suspicious/noArrayIndexKey/noArrayIndexKey.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/suppression/suspicious/noAssignInExpressions/noAssignInExpressions.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_json_analyze/tests/specs/suspicious/noBiomeFirstException/invalid.biome.jsonc.snap is excluded by !**/*.snap and included by **
  • crates/biome_json_analyze/tests/specs/suspicious/noBiomeFirstException/realExtends/invalid.biome.jsonc.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • .changeset/fix-tab-alignment-diff-output.md
  • crates/biome_cli/tests/commands/check.rs
  • crates/biome_diagnostics/src/display/diff.rs
  • crates/biome_diagnostics/src/display/frame.rs

Comment on lines +484 to +494
let has_changed_lines_with_leading_tabs = diffs_by_line.iter().enumerate().any(|(i, line)| {
if !shown_line_indexes.contains(&i) {
return false;
}
let is_changed = line.before_line.is_none() || line.after_line.is_none();
is_changed
&& line
.diffs
.first()
.is_some_and(|(_, text)| text.starts_with('\t'))
});
@coderabbitai coderabbitai Bot Mar 6, 2026 •
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mixed space+tab indentation can still bypass this fix.

Current checks only handle tabs at byte 0 (starts_with('\t') / trim_start_matches('\t')). Lines like " \tfoo" remain unnormalised, so alignment can still drift in mixed-indent files.

Suggested patch (normalise tabs in the whole leading indentation prefix)
-                .is_some_and(|(_, text)| text.starts_with('\t'))
+                .is_some_and(|(_, text)| has_tabs_in_leading_indent(text))
     });
 
+fn has_tabs_in_leading_indent(text: &str) -> bool {
+    let (indent, _) = split_leading_indent(text);
+    indent.contains('\t')
+}
+
+fn split_leading_indent(text: &str) -> (&str, &str) {
+    let mut end = 0;
+    for (idx, ch) in text.char_indices() {
+        if ch == ' ' || ch == '\t' {
+            end = idx + ch.len_utf8();
+        } else {
+            break;
+        }
+    }
+    text.split_at(end)
+}
+
 fn replace_leading_tabs(text: &str) -> String {
-    let leading_tabs = text.len() - text.trim_start_matches('\t').len();
-    if leading_tabs == 0 {
+    let (indent, rest) = split_leading_indent(text);
+    if !indent.contains('\t') {
         return text.to_string();
     }
-    let spaces = " ".repeat(TAB_WIDTH * leading_tabs);
-    format!("{spaces}{}", &text[leading_tabs..])
+
+    let mut out = String::with_capacity(text.len() + indent.matches('\t').count() * (TAB_WIDTH - 1));
+    for ch in indent.chars() {
+        if ch == '\t' {
+            out.push_str(&" ".repeat(TAB_WIDTH));
+        } else {
+            out.push(ch);
+        }
+    }
+    out.push_str(rest);
+    out
 }

Also applies to: 616-626

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_diagnostics/src/display/diff.rs` around lines 484 - 494, The
check in has_changed_lines_with_leading_tabs only tests the first byte of the
diff text (starts_with('\t')) and misses mixed indentation like "  \tfoo";
change the predicate that examines line.diffs.first() to inspect the entire
leading indentation run and detect any '\t' inside it (e.g., take the leading
chars while they are ' ' or '\t' and check if any == '\t'), then use that
boolean where currently starts_with('\t') is used; apply the same change to the
analogous check around the 616-626 block so mixed space+tab prefixes are caught
and normalized consistently.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai This is an extreme edge case. It adds a lot of complexity to the code. We can skip it for now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico Fair enough — mixed space+tab indentation is genuinely rare, and keeping the fix focused makes sense. I'll leave it as-is. 🐇

(╥_╥)


🧠 Learnings used
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: For formatter changes in changesets, show formatting changes using diff code blocks

Learnt from: dyc3
Repo: biomejs/biome PR: 8901
File: crates/biome_analyze_macros/src/group_macro.rs:9-9
Timestamp: 2026-01-29T22:10:30.888Z
Learning: Do not import xtask inside biome crates. In the biomeps repository, ensure that biome crates (e.g., crates/biome_*/...) do not have imports like use xtask_glue::... or other xtask-related crates. The correct dependency direction is for xtask to depend on and import biome crates, not the reverse, since xtask contains heavier utilities (e.g., git2) that can inflate build times when included in biome crates. When reviewing, verify dependency declarations (Cargo.toml) and any use statements to confirm xtask crates are not pulled into biome crates; prefer having xtask depend on biome crates and export needed functionality through stable interfaces.

@dyc3 dyc3 left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests look good

@ematipico
ematipico merged commit 29cac17 into main Mar 6, 2026
13 checks passed
@ematipico
ematipico deleted the fix/diagnostic-printing-diff branch March 6, 2026 17:41
@github-actions github-actions Bot mentioned this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter L-JavaScript Language: JavaScript and super languages L-JSON Language: JSON and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Alignment of tabbed text in biome check output is wrong

2 participants

0