Skip to content

Commit 2d903b9

Browse files
committed
Avoid duplicating linter-formatter compatibility warnings
1 parent 5776ec1 commit 2d903b9

File tree

2 files changed

+51
-38
lines changed

2 files changed

+51
-38
lines changed

crates/ruff_cli/src/commands/format.rs

+45-36
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use itertools::Itertools;
1111
use log::{error, warn};
1212
use rayon::iter::Either::{Left, Right};
1313
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
14+
use rustc_hash::FxHashSet;
1415
use thiserror::Error;
1516
use tracing::debug;
1617

@@ -695,11 +696,11 @@ pub(super) fn warn_incompatible_formatter_settings(
695696
pyproject_config: &PyprojectConfig,
696697
resolver: Option<&Resolver>,
697698
) {
699+
// First, collect all rules that are incompatible regardless of the linter-specific settings.
700+
let mut incompatible_rules = FxHashSet::default();
698701
for setting in std::iter::once(&pyproject_config.settings)
699702
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
700703
{
701-
let mut incompatible_rules = Vec::new();
702-
703704
for rule in [
704705
// The formatter might collapse implicit string concatenation on a single line.
705706
Rule::SingleLineImplicitStringConcatenation,
@@ -713,41 +714,48 @@ pub(super) fn warn_incompatible_formatter_settings(
713714
Rule::MissingTrailingComma,
714715
] {
715716
if setting.linter.rules.enabled(rule) {
716-
incompatible_rules.push(rule);
717+
incompatible_rules.insert(rule);
717718
}
718719
}
720+
}
719721

720-
// Rules asserting for space indentation
721-
if setting.formatter.indent_style.is_tab() {
722-
for rule in [Rule::TabIndentation, Rule::IndentWithSpaces] {
723-
if setting.linter.rules.enabled(rule) {
724-
incompatible_rules.push(rule);
725-
}
726-
}
722+
if !incompatible_rules.is_empty() {
723+
let mut rule_names: Vec<_> = incompatible_rules
724+
.into_iter()
725+
.map(|rule| format!("`{}`", rule.noqa_code()))
726+
.collect();
727+
rule_names.sort();
728+
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
729+
}
730+
731+
// Next, validate settings-specific incompatibilities.
732+
for setting in std::iter::once(&pyproject_config.settings)
733+
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
734+
{
735+
// Validate all rules that rely on tab styles.
736+
if setting.linter.rules.enabled(Rule::TabIndentation)
737+
&& setting.formatter.indent_style.is_tab()
738+
{
739+
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
727740
}
728741

729-
// Rules asserting for indent-width=4
730-
if setting.formatter.indent_width.value() != 4 {
731-
for rule in [
732-
Rule::IndentationWithInvalidMultiple,
733-
Rule::IndentationWithInvalidMultipleComment,
734-
] {
735-
if setting.linter.rules.enabled(rule) {
736-
incompatible_rules.push(rule);
737-
}
738-
}
742+
// Validate all rules that rely on tab styles.
743+
if setting.linter.rules.enabled(Rule::IndentWithSpaces)
744+
&& setting.formatter.indent_style.is_tab()
745+
{
746+
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
739747
}
740748

741-
if !incompatible_rules.is_empty() {
742-
let mut rule_names: Vec<_> = incompatible_rules
743-
.into_iter()
744-
.map(|rule| format!("`{}`", rule.noqa_code()))
745-
.collect();
746-
rule_names.sort();
747-
warn!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
749+
// Validate all rules that rely on custom indent widths.
750+
if setting.linter.rules.any_enabled(&[
751+
Rule::IndentationWithInvalidMultiple,
752+
Rule::IndentationWithInvalidMultipleComment,
753+
]) && setting.formatter.indent_width.value() != 4
754+
{
755+
warn_user_once!("The `format.indent-width` option with a value other than 4 is incompatible with `E111` and `E114`. We recommend disabling these rules when using the formatter, which enforces a consistent indentation width. Alternatively, set the `format.indent-width` option to `4`.");
748756
}
749757

750-
// Rules with different quote styles.
758+
// Validate all rules that rely on quote styles.
751759
if setting
752760
.linter
753761
.rules
@@ -758,10 +766,10 @@ pub(super) fn warn_incompatible_formatter_settings(
758766
setting.formatter.quote_style,
759767
) {
760768
(Quote::Double, QuoteStyle::Single) => {
761-
warn!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
769+
warn_user_once!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
762770
}
763771
(Quote::Single, QuoteStyle::Double) => {
764-
warn!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
772+
warn_user_once!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
765773
}
766774
_ => {}
767775
}
@@ -770,25 +778,26 @@ pub(super) fn warn_incompatible_formatter_settings(
770778
if setting.linter.rules.enabled(Rule::BadQuotesMultilineString)
771779
&& setting.linter.flake8_quotes.multiline_quotes == Quote::Single
772780
{
773-
warn!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
781+
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
774782
}
775783

776784
if setting.linter.rules.enabled(Rule::BadQuotesDocstring)
777785
&& setting.linter.flake8_quotes.docstring_quotes == Quote::Single
778786
{
779-
warn!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`");
787+
warn_user_once!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`");
780788
}
781789

790+
// Validate all isort settings.
782791
if setting.linter.rules.enabled(Rule::UnsortedImports) {
783792
// The formatter removes empty lines if the value is larger than 2 but always inserts a empty line after imports.
784793
// Two empty lines are okay because `isort` only uses this setting for top-level imports (not in nested blocks).
785794
if !matches!(setting.linter.isort.lines_after_imports, 1 | 2 | -1) {
786-
warn!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).");
795+
warn_user_once!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).");
787796
}
788797

789798
// Values larger than two get reduced to one line by the formatter if the import is in a nested block.
790799
if setting.linter.isort.lines_between_types > 1 {
791-
warn!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).");
800+
warn_user_once!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).");
792801
}
793802

794803
// isort inserts a trailing comma which the formatter preserves, but only if `skip-magic-trailing-comma` isn't false.
@@ -797,11 +806,11 @@ pub(super) fn warn_incompatible_formatter_settings(
797806
&& !setting.linter.isort.force_single_line
798807
{
799808
if setting.linter.isort.force_wrap_aliases {
800-
warn!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.");
809+
warn_user_once!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.");
801810
}
802811

803812
if setting.linter.isort.split_on_trailing_comma {
804-
warn!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.");
813+
warn_user_once!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.");
805814
}
806815
}
807816
}

crates/ruff_cli/tests/format.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,9 @@ def say_hy(name: str):
403403
1 file reformatted
404404
405405
----- stderr -----
406-
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `D206`, `ISC001`, `W191`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
406+
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
407+
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
408+
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
407409
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
408410
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
409411
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
@@ -460,7 +462,9 @@ def say_hy(name: str):
460462
print(f"Hy {name}")
461463
462464
----- stderr -----
463-
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `D206`, `ISC001`, `W191`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
465+
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
466+
warning: The `format.indent-style="tab"` option is incompatible with `W191`, which lints against all uses of tabs. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
467+
warning: The `format.indent-style="tab"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `"space"`.
464468
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
465469
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
466470
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`

0 commit comments

Comments
 (0)