From 362d9e9dbe10621eb5c7b93919dd35166566ed60 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 4 Feb 2023 20:10:54 -0500 Subject: [PATCH] Gate logical line behaviors under a feature --- .github/workflows/ci.yaml | 4 +- Cargo.toml | 4 ++ README.md | 14 ------ ruff.schema.json | 14 ------ ruff_macros/Cargo.toml | 2 +- ruff_macros/src/define_rule_mapping.rs | 43 ++++++++++------ ruff_macros/src/rule_code_prefix.rs | 49 +++++++++++++------ src/registry.rs | 45 +++++++++++------ src/rules/pycodestyle/mod.rs | 39 +++++++++------ .../rules/extraneous_whitespace.rs | 8 +++ src/rules/pycodestyle/rules/indentation.rs | 15 ++++++ .../rules/space_around_operator.rs | 8 +++ 12 files changed, 155 insertions(+), 90 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 32ab1bef3a1130..117b8d41333add 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -69,13 +69,13 @@ jobs: - name: "Run tests (Ubuntu)" if: ${{ matrix.os == 'ubuntu-latest' }} run: | - cargo insta test --all --delete-unreferenced-snapshots + cargo insta test --all --all-features --delete-unreferenced-snapshots git diff --exit-code - name: "Run tests (Windows)" if: ${{ matrix.os == 'windows-latest' }} shell: bash run: | - cargo insta test --all + cargo insta test --all --all-features git diff --exit-code - run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored # Check for broken links in the documentation. diff --git a/Cargo.toml b/Cargo.toml index f68b8412551000..f9a69585c72fee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,10 @@ thiserror = { version = "1.0" } titlecase = { version = "2.2.1" } toml = { version = "0.6.0" } +[features] +default = [] +logical_lines = [] + # https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support # For (future) wasm-pack support [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] diff --git a/README.md b/README.md index 4e9c7ae0ef2ad6..485035dd611edb 100644 --- a/README.md +++ b/README.md @@ -701,20 +701,6 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/) on PyPI. | Code | Name | Message | Fix | | ---- | ---- | ------- | --- | | E101 | mixed-spaces-and-tabs | Indentation contains mixed spaces and tabs | | -| E111 | indentation-with-invalid-multiple | Indentation is not a multiple of {indent_size} | | -| E112 | no-indented-block | Expected an indented block | | -| E113 | unexpected-indentation | Unexpected indentation | | -| E114 | indentation-with-invalid-multiple-comment | Indentation is not a multiple of {indent_size} (comment) | | -| E115 | no-indented-block-comment | Expected an indented block (comment) | | -| E116 | unexpected-indentation-comment | Unexpected indentation (comment) | | -| E117 | over-indented | Over-indented | | -| E201 | whitespace-after-open-bracket | Whitespace after '(' | | -| E202 | whitespace-before-close-bracket | Whitespace before ')' | | -| E203 | whitespace-before-punctuation | Whitespace before ',', ';', or ':' | | -| E221 | multiple-spaces-before-operator | Multiple spaces before operator | | -| E222 | multiple-spaces-after-operator | Multiple spaces after operator | | -| E223 | tab-before-operator | Tab before operator | | -| E224 | tab-after-operator | Tab after operator | | | E401 | multiple-imports-on-one-line | Multiple imports on one line | | | E402 | module-import-not-at-top-of-file | Module level import not at top of file | | | E501 | line-too-long | Line too long ({length} > {limit} characters) | | diff --git a/ruff.schema.json b/ruff.schema.json index ab15d297c51653..d8a9d61e5bc640 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1447,23 +1447,9 @@ "E10", "E101", "E11", - "E111", - "E112", - "E113", - "E114", - "E115", - "E116", - "E117", "E2", "E20", - "E201", - "E202", - "E203", "E22", - "E221", - "E222", - "E223", - "E224", "E4", "E40", "E401", diff --git a/ruff_macros/Cargo.toml b/ruff_macros/Cargo.toml index 8e46d3ab338474..5655d6c19d81c4 100644 --- a/ruff_macros/Cargo.toml +++ b/ruff_macros/Cargo.toml @@ -11,5 +11,5 @@ doctest = false once_cell = { version = "1.17.0" } proc-macro2 = { version = "1.0.47" } quote = { version = "1.0.21" } -syn = { version = "1.0.103", features = ["derive", "parsing"] } +syn = { version = "1.0.103", features = ["derive", "parsing", "extra-traits"] } textwrap = { version = "0.16.0" } diff --git a/ruff_macros/src/define_rule_mapping.rs b/ruff_macros/src/define_rule_mapping.rs index b2e70dbca9c3d5..58a5036fac82cb 100644 --- a/ruff_macros/src/define_rule_mapping.rs +++ b/ruff_macros/src/define_rule_mapping.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use proc_macro2::Span; use quote::quote; use syn::parse::Parse; -use syn::{Ident, LitStr, Path, Token}; +use syn::{Attribute, Ident, LitStr, Path, Token}; pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { let mut rule_variants = quote!(); @@ -18,25 +18,32 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { let mut diagkind_commit_match_arms = quote!(); let mut from_impls_for_diagkind = quote!(); - for (code, path, name) in &mapping.entries { + for (code, path, name, attr) in &mapping.entries { let code_str = LitStr::new(&code.to_string(), Span::call_site()); rule_variants.extend(quote! { #[doc = #code_str] + #(#attr)* #name, }); - diagkind_variants.extend(quote! {#name(#path),}); + diagkind_variants.extend(quote! {#(#attr)* #name(#path),}); + + // Apply the `attrs` to each arm, like `[cfg(feature = "foo")]`. rule_message_formats_match_arms - .extend(quote! {Self::#name => <#path as Violation>::message_formats(),}); - rule_autofixable_match_arms.extend(quote! {Self::#name => <#path as Violation>::AUTOFIX,}); - rule_code_match_arms.extend(quote! {Self::#name => #code_str,}); - rule_from_code_match_arms.extend(quote! {#code_str => Ok(Rule::#name), }); - diagkind_code_match_arms.extend(quote! {Self::#name(..) => &Rule::#name, }); - diagkind_body_match_arms.extend(quote! {Self::#name(x) => Violation::message(x), }); + .extend(quote! {#(#attr)* Self::#name => <#path as Violation>::message_formats(),}); + rule_autofixable_match_arms + .extend(quote! {#(#attr)* Self::#name => <#path as Violation>::AUTOFIX,}); + rule_code_match_arms.extend(quote! {#(#attr)* Self::#name => #code_str,}); + rule_from_code_match_arms.extend(quote! {#(#attr)* #code_str => Ok(Rule::#name), }); + diagkind_code_match_arms.extend(quote! {#(#attr)* Self::#name(..) => &Rule::#name, }); + diagkind_body_match_arms + .extend(quote! {#(#attr)* Self::#name(x) => Violation::message(x), }); diagkind_fixable_match_arms - .extend(quote! {Self::#name(x) => x.autofix_title_formatter().is_some(),}); - diagkind_commit_match_arms - .extend(quote! {Self::#name(x) => x.autofix_title_formatter().map(|f| f(x)), }); + .extend(quote! {#(#attr)* Self::#name(x) => x.autofix_title_formatter().is_some(),}); + diagkind_commit_match_arms.extend( + quote! {#(#attr)* Self::#name(x) => x.autofix_title_formatter().map(|f| f(x)), }, + ); from_impls_for_diagkind.extend(quote! { + #(#attr)* impl From<#path> for DiagnosticKind { fn from(x: #path) -> Self { DiagnosticKind::#name(x) @@ -48,7 +55,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { let code_to_name: HashMap<_, _> = mapping .entries .iter() - .map(|(code, _, name)| (code.to_string(), name)) + .map(|(code, _, name, _)| (code.to_string(), name)) .collect(); let rulecodeprefix = super::rule_code_prefix::expand( @@ -56,6 +63,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { &Ident::new("RuleCodePrefix", Span::call_site()), mapping.entries.iter().map(|(code, ..)| code), |code| code_to_name[code], + mapping.entries.iter().map(|(.., attr)| attr), ); quote! { @@ -104,7 +112,6 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { } } - impl DiagnosticKind { /// The rule of the diagnostic. pub fn rule(&self) -> &'static Rule { @@ -134,19 +141,23 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { } pub struct Mapping { - entries: Vec<(Ident, Path, Ident)>, + entries: Vec<(Ident, Path, Ident, Vec)>, } impl Parse for Mapping { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut entries = Vec::new(); while !input.is_empty() { + // Grab the `#[cfg(...)]` attributes. + let attrs = input.call(Attribute::parse_outer)?; + + // Parse the `RuleCodePrefix::... => ...` part. let code: Ident = input.parse()?; let _: Token![=>] = input.parse()?; let path: Path = input.parse()?; let name = path.segments.last().unwrap().ident.clone(); let _: Token![,] = input.parse()?; - entries.push((code, path, name)); + entries.push((code, path, name, attrs)); } Ok(Self { entries }) } diff --git a/ruff_macros/src/rule_code_prefix.rs b/ruff_macros/src/rule_code_prefix.rs index 93c939a98eb03a..484cc5115976b2 100644 --- a/ruff_macros/src/rule_code_prefix.rs +++ b/ruff_macros/src/rule_code_prefix.rs @@ -1,22 +1,23 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use proc_macro2::Span; use quote::quote; -use syn::Ident; +use syn::{Attribute, Ident}; pub fn expand<'a>( rule_type: &Ident, prefix_ident: &Ident, variants: impl Iterator, variant_name: impl Fn(&str) -> &'a Ident, + attr: impl Iterator>, ) -> proc_macro2::TokenStream { // Build up a map from prefix to matching RuleCodes. - let mut prefix_to_codes: BTreeMap> = BTreeMap::default(); + let mut prefix_to_codes: BTreeMap>> = + BTreeMap::default(); - let mut all_codes = BTreeSet::new(); - let mut pl_codes = BTreeSet::new(); + let mut pl_codes = BTreeMap::new(); - for variant in variants { + for (variant, attr) in variants.zip(attr) { let code_str = variant.to_string(); let code_prefix_len = code_str .chars() @@ -28,19 +29,25 @@ pub fn expand<'a>( prefix_to_codes .entry(prefix) .or_default() - .insert(code_str.clone()); + .entry(code_str.clone()) + .or_insert_with(|| attr.clone()); } if code_str.starts_with("PL") { - pl_codes.insert(code_str.to_string()); + pl_codes.insert(code_str, attr.clone()); } - all_codes.insert(code_str); } prefix_to_codes.insert("PL".to_string(), pl_codes); - let prefix_variants = prefix_to_codes.keys().map(|prefix| { + let prefix_variants = prefix_to_codes.iter().map(|(prefix, codes)| { let prefix = Ident::new(prefix, Span::call_site()); + let attr = if codes.len() == 1 { + codes.values().next().unwrap().clone() + } else { + vec![] + }; quote! { + #(#attr)* #prefix } }); @@ -74,24 +81,32 @@ pub fn expand<'a>( fn generate_impls<'a>( rule_type: &Ident, prefix_ident: &Ident, - prefix_to_codes: &BTreeMap>, + prefix_to_codes: &BTreeMap>>, variant_name: impl Fn(&str) -> &'a Ident, ) -> proc_macro2::TokenStream { let into_iter_match_arms = prefix_to_codes.iter().map(|(prefix_str, codes)| { - let codes = codes.iter().map(|code| { + let prefix = Ident::new(prefix_str, Span::call_site()); + let attr = if codes.len() == 1 { + codes.values().next().unwrap().clone() + } else { + vec![] + }; + + let codes = codes.iter().map(|(code, attr)| { let rule_variant = variant_name(code); quote! { + #(#attr)* #rule_type::#rule_variant } }); - let prefix = Ident::new(prefix_str, Span::call_site()); quote! { + #(#attr)* #prefix_ident::#prefix => vec![#(#codes),*].into_iter(), } }); - let specificity_match_arms = prefix_to_codes.keys().map(|prefix_str| { + let specificity_match_arms = prefix_to_codes.iter().map(|(prefix_str, codes)| { let prefix = Ident::new(prefix_str, Span::call_site()); let mut num_numeric = prefix_str.chars().filter(|char| char.is_numeric()).count(); if prefix_str != "PL" && prefix_str.starts_with("PL") { @@ -106,7 +121,13 @@ fn generate_impls<'a>( 5 => quote! { Specificity::Code5Chars }, _ => panic!("Invalid prefix: {prefix}"), }; + let attr = if codes.len() == 1 { + codes.values().next().unwrap().clone() + } else { + vec![] + }; quote! { + #(#attr)* #prefix_ident::#prefix => #suffix_len, } }); diff --git a/src/registry.rs b/src/registry.rs index 41f61f07df2512..294c5602378921 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -13,19 +13,33 @@ use crate::violation::Violation; ruff_macros::define_rule_mapping!( // pycodestyle errors E101 => rules::pycodestyle::rules::MixedSpacesAndTabs, + #[cfg(feature = "logical_lines")] E111 => rules::pycodestyle::rules::IndentationWithInvalidMultiple, + #[cfg(feature = "logical_lines")] E112 => rules::pycodestyle::rules::NoIndentedBlock, + #[cfg(feature = "logical_lines")] E113 => rules::pycodestyle::rules::UnexpectedIndentation, - E114 => rules::pycodestyle::rules::IndentationWithInvalidMultipleComment, + #[cfg(feature = "logical_lines")] + E114 => rules::pycodestyle::rules::IndentationWithInvalidMultipleComment, + #[cfg(feature = "logical_lines")] E115 => rules::pycodestyle::rules::NoIndentedBlockComment, + #[cfg(feature = "logical_lines")] E116 => rules::pycodestyle::rules::UnexpectedIndentationComment, + #[cfg(feature = "logical_lines")] E117 => rules::pycodestyle::rules::OverIndented, + #[cfg(feature = "logical_lines")] E201 => rules::pycodestyle::rules::WhitespaceAfterOpenBracket, + #[cfg(feature = "logical_lines")] E202 => rules::pycodestyle::rules::WhitespaceBeforeCloseBracket, + #[cfg(feature = "logical_lines")] E203 => rules::pycodestyle::rules::WhitespaceBeforePunctuation, + #[cfg(feature = "logical_lines")] E221 => rules::pycodestyle::rules::MultipleSpacesBeforeOperator, + #[cfg(feature = "logical_lines")] E222 => rules::pycodestyle::rules::MultipleSpacesAfterOperator, + #[cfg(feature = "logical_lines")] E223 => rules::pycodestyle::rules::TabBeforeOperator, + #[cfg(feature = "logical_lines")] E224 => rules::pycodestyle::rules::TabAfterOperator, E401 => rules::pycodestyle::rules::MultipleImportsOnOneLine, E402 => rules::pycodestyle::rules::ModuleImportNotAtTopOfFile, @@ -698,20 +712,6 @@ impl Rule { pub const fn lint_source(&self) -> &'static LintSource { match self { Rule::UnusedNOQA => &LintSource::NoQa, - Rule::IndentationWithInvalidMultiple - | Rule::IndentationWithInvalidMultipleComment - | Rule::MultipleSpacesAfterOperator - | Rule::MultipleSpacesBeforeOperator - | Rule::NoIndentedBlock - | Rule::NoIndentedBlockComment - | Rule::OverIndented - | Rule::TabAfterOperator - | Rule::TabBeforeOperator - | Rule::UnexpectedIndentation - | Rule::UnexpectedIndentationComment - | Rule::WhitespaceAfterOpenBracket - | Rule::WhitespaceBeforeCloseBracket - | Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines, Rule::BlanketNOQA | Rule::BlanketTypeIgnore | Rule::DocLineTooLong @@ -742,6 +742,21 @@ impl Rule { Rule::IOError => &LintSource::Io, Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports, Rule::ImplicitNamespacePackage => &LintSource::Filesystem, + #[cfg(feature = "logical_lines")] + Rule::IndentationWithInvalidMultiple + | Rule::IndentationWithInvalidMultipleComment + | Rule::MultipleSpacesAfterOperator + | Rule::MultipleSpacesBeforeOperator + | Rule::NoIndentedBlock + | Rule::NoIndentedBlockComment + | Rule::OverIndented + | Rule::TabAfterOperator + | Rule::TabBeforeOperator + | Rule::UnexpectedIndentation + | Rule::UnexpectedIndentationComment + | Rule::WhitespaceAfterOpenBracket + | Rule::WhitespaceBeforeCloseBracket + | Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines, _ => &LintSource::Ast, } } diff --git a/src/rules/pycodestyle/mod.rs b/src/rules/pycodestyle/mod.rs index 9c74c96e30a853..37168d233d2690 100644 --- a/src/rules/pycodestyle/mod.rs +++ b/src/rules/pycodestyle/mod.rs @@ -17,20 +17,6 @@ mod tests { use crate::test::test_path; use crate::{assert_yaml_snapshot, settings}; - #[test_case(Rule::IndentationWithInvalidMultiple, Path::new("E11.py"))] - #[test_case(Rule::IndentationWithInvalidMultipleComment, Path::new("E11.py"))] - #[test_case(Rule::NoIndentedBlock, Path::new("E11.py"))] - #[test_case(Rule::NoIndentedBlockComment, Path::new("E11.py"))] - #[test_case(Rule::UnexpectedIndentation, Path::new("E11.py"))] - #[test_case(Rule::UnexpectedIndentationComment, Path::new("E11.py"))] - #[test_case(Rule::OverIndented, Path::new("E11.py"))] - #[test_case(Rule::WhitespaceAfterOpenBracket, Path::new("E20.py"))] - #[test_case(Rule::WhitespaceBeforeCloseBracket, Path::new("E20.py"))] - #[test_case(Rule::WhitespaceBeforePunctuation, Path::new("E20.py"))] - #[test_case(Rule::TabBeforeOperator, Path::new("E22.py"))] - #[test_case(Rule::MultipleSpacesBeforeOperator, Path::new("E22.py"))] - #[test_case(Rule::TabAfterOperator, Path::new("E22.py"))] - #[test_case(Rule::MultipleSpacesAfterOperator, Path::new("E22.py"))] #[test_case(Rule::MultipleImportsOnOneLine, Path::new("E40.py"))] #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E40.py"))] #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.py"))] @@ -64,6 +50,31 @@ mod tests { Ok(()) } + #[cfg(feature = "logical_lines")] + #[test_case(Rule::IndentationWithInvalidMultiple, Path::new("E11.py"))] + #[test_case(Rule::IndentationWithInvalidMultipleComment, Path::new("E11.py"))] + #[test_case(Rule::MultipleSpacesAfterOperator, Path::new("E22.py"))] + #[test_case(Rule::MultipleSpacesBeforeOperator, Path::new("E22.py"))] + #[test_case(Rule::NoIndentedBlock, Path::new("E11.py"))] + #[test_case(Rule::NoIndentedBlockComment, Path::new("E11.py"))] + #[test_case(Rule::OverIndented, Path::new("E11.py"))] + #[test_case(Rule::TabAfterOperator, Path::new("E22.py"))] + #[test_case(Rule::TabBeforeOperator, Path::new("E22.py"))] + #[test_case(Rule::UnexpectedIndentation, Path::new("E11.py"))] + #[test_case(Rule::UnexpectedIndentationComment, Path::new("E11.py"))] + #[test_case(Rule::WhitespaceAfterOpenBracket, Path::new("E20.py"))] + #[test_case(Rule::WhitespaceBeforeCloseBracket, Path::new("E20.py"))] + #[test_case(Rule::WhitespaceBeforePunctuation, Path::new("E20.py"))] + fn logical(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy()); + let diagnostics = test_path( + Path::new("pycodestyle").join(path).as_path(), + &settings::Settings::for_rule(rule_code), + )?; + assert_yaml_snapshot!(snapshot, diagnostics); + Ok(()) + } + #[test] fn constant_literals() -> Result<()> { let diagnostics = test_path( diff --git a/src/rules/pycodestyle/rules/extraneous_whitespace.rs b/src/rules/pycodestyle/rules/extraneous_whitespace.rs index b8dd2d26513049..de75f3d7bc073d 100644 --- a/src/rules/pycodestyle/rules/extraneous_whitespace.rs +++ b/src/rules/pycodestyle/rules/extraneous_whitespace.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use once_cell::sync::Lazy; use regex::Regex; @@ -42,6 +44,7 @@ static EXTRANEOUS_WHITESPACE_REGEX: Lazy = Lazy::new(|| Regex::new(r"([\[({][ \t]|[ \t][]}),;:])").unwrap()); /// E201, E202, E203 +#[cfg(feature = "logical_lines")] pub fn extraneous_whitespace(line: &str) -> Vec<(usize, DiagnosticKind)> { let mut diagnostics = vec![]; for line_match in EXTRANEOUS_WHITESPACE_REGEX.captures_iter(line) { @@ -61,3 +64,8 @@ pub fn extraneous_whitespace(line: &str) -> Vec<(usize, DiagnosticKind)> { } diagnostics } + +#[cfg(not(feature = "logical_lines"))] +pub fn extraneous_whitespace(_line: &str) -> Vec<(usize, DiagnosticKind)> { + vec![] +} diff --git a/src/rules/pycodestyle/rules/indentation.rs b/src/rules/pycodestyle/rules/indentation.rs index d79ed6e4084645..acb3df89ac24bd 100644 --- a/src/rules/pycodestyle/rules/indentation.rs +++ b/src/rules/pycodestyle/rules/indentation.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use crate::define_violation; use crate::registry::DiagnosticKind; use crate::rules::pycodestyle::logical_lines::LogicalLine; @@ -81,6 +83,7 @@ impl Violation for OverIndented { } /// E111 +#[cfg(feature = "logical_lines")] pub fn indentation( logical_line: &LogicalLine, prev_logical_line: Option<&LogicalLine>, @@ -133,3 +136,15 @@ pub fn indentation( } diagnostics } + +#[cfg(not(feature = "logical_lines"))] +pub fn indentation( + _logical_line: &LogicalLine, + _prev_logical_line: Option<&LogicalLine>, + _indent_char: char, + _indent_level: usize, + _prev_indent_level: Option, + _indent_size: usize, +) -> Vec<(usize, DiagnosticKind)> { + vec![] +} diff --git a/src/rules/pycodestyle/rules/space_around_operator.rs b/src/rules/pycodestyle/rules/space_around_operator.rs index 6ea8d1575f9f48..9e215f903293c7 100644 --- a/src/rules/pycodestyle/rules/space_around_operator.rs +++ b/src/rules/pycodestyle/rules/space_around_operator.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use once_cell::sync::Lazy; use regex::Regex; @@ -51,6 +53,7 @@ static OPERATOR_REGEX: Lazy = Lazy::new(|| Regex::new(r"[^,\s](\s*)(?:[-+*/|!<=>%&^]+|:=)(\s*)").unwrap()); /// E221, E222, E223, E224 +#[cfg(feature = "logical_lines")] pub fn space_around_operator(line: &str) -> Vec<(usize, DiagnosticKind)> { let mut diagnostics = vec![]; for line_match in OPERATOR_REGEX.captures_iter(line) { @@ -71,3 +74,8 @@ pub fn space_around_operator(line: &str) -> Vec<(usize, DiagnosticKind)> { } diagnostics } + +#[cfg(not(feature = "logical_lines"))] +pub fn space_around_operator(_line: &str) -> Vec<(usize, DiagnosticKind)> { + vec![] +}