Repository navigation
fix(cli): tab printing - #9371
Conversation
🦋 Changeset detectedLatest commit: 23b0cd6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds 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 Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (45)
crates/biome_cli/tests/snapshots/main_commands_check/check_tab_alignment_in_diff_output.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/a11y/noPositiveTabindex/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/complexity/noUselessCatch/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/complexity/noUselessContinue/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/complexity/noUselessSwitchCase/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noSwitchDeclarations/invalid.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUnusedPrivateClassMembers/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUnusedPrivateClassMembers/invalid.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUnusedPrivateClassMembers/invalid_issue_7101.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noVueDataObjectDeclaration/invalid-createapp-mount-object-data.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noVueDataObjectDeclaration/invalid-createapp-object-data.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noVueDataObjectDeclaration/invalid-export-default-object-data.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noVueDataObjectDeclaration/invalid-export-default-parenthesized-object-data.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noVueArrowFuncInWatch/invalid-define.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useExhaustiveSwitchCases/invalid.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/sorted.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/templateLiteralSpace.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/unsorted.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/performance/useGoogleFontPreconnect/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/source/useSortedAttributes/with-comments.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/source/useSortedInterfaceMembers/mixed_literals.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/source/useSortedInterfaceMembers/numeric_literals.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/source/useSortedInterfaceMembers/sortable_after_non_sortable.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/source/useSortedKeys/unsorted.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/noShoutyConstants/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/useCollapsedElseIf/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/useCollapsedIf/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/useConsistentCurlyBraces/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/useEnumInitializers/invalid.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/useNumberNamespace/invalid_properties_value_shorthand.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/useSingleVarDeclarator/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/useTrimStartEnd/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/useUnifiedTypeSignatures/invalidJsdoc.ts.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/suspicious/noApproximativeNumericConstant/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalid.cjs.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalidClass.cjs.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalidFunction.cjs.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/suspicious/noRedundantUseStrict/invalidFunction.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/suppression/a11y/noPositiveTabindex/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/suppression/suspicious/noArrayIndexKey/noArrayIndexKey.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/suppression/suspicious/noAssignInExpressions/noAssignInExpressions.ts.snapis excluded by!**/*.snapand included by**crates/biome_json_analyze/tests/specs/suspicious/noBiomeFirstException/invalid.biome.jsonc.snapis excluded by!**/*.snapand included by**crates/biome_json_analyze/tests/specs/suspicious/noBiomeFirstException/realExtends/invalid.biome.jsonc.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (4)
.changeset/fix-tab-alignment-diff-output.mdcrates/biome_cli/tests/commands/check.rscrates/biome_diagnostics/src/display/diff.rscrates/biome_diagnostics/src/display/frame.rs
| 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')) | ||
| }); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@coderabbitai This is an extreme edge case. It adds a lot of complexity to the code. We can skip it for now
There was a problem hiding this comment.
@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.
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