diff --git a/LICENSE b/LICENSE index 8ffd09c5f2b5c..7711c47e852c1 100644 --- a/LICENSE +++ b/LICENSE @@ -1224,6 +1224,31 @@ are: SOFTWARE. """ +- flake8-logging, licensed as follows: + """ + MIT License + + Copyright (c) 2023 Adam Johnson + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + """ + - Pyright, licensed as follows: """ MIT License diff --git a/README.md b/README.md index 98a5d8a3c788c..b13322de1d262 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,7 @@ quality tools, including: - [flake8-gettext](https://pypi.org/project/flake8-gettext/) - [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/) - [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions) +- [flake8-logging](https://pypi.org/project/flake8-logging/) - [flake8-logging-format](https://pypi.org/project/flake8-logging-format/) - [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420) - [flake8-pie](https://pypi.org/project/flake8-pie/) diff --git a/crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py b/crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py new file mode 100644 index 0000000000000..9740486265e72 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py @@ -0,0 +1,9 @@ +import logging + +logging.WARN # LOG009 +logging.WARNING # OK + +from logging import WARN, WARNING + +WARN # LOG009 +WARNING # OK diff --git a/crates/ruff/src/checkers/ast/analyze/expression.rs b/crates/ruff/src/checkers/ast/analyze/expression.rs index cc29448125165..321cd4fcdb5df 100644 --- a/crates/ruff/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff/src/checkers/ast/analyze/expression.rs @@ -13,10 +13,10 @@ use crate::registry::Rule; use crate::rules::{ flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django, - flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging_format, - flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify, - flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle, - pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, + flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging, + flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, + flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, + pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff, }; use crate::settings::types::PythonVersion; @@ -260,6 +260,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::SixPY3) { flake8_2020::rules::name_or_attribute(checker, expr); } + if checker.enabled(Rule::UndocumentedWarn) { + flake8_logging::rules::undocumented_warn(checker, expr); + } if checker.enabled(Rule::LoadBeforeGlobalDeclaration) { pylint::rules::load_before_global_declaration(checker, id, expr); } @@ -326,6 +329,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::CollectionsNamedTuple) { flake8_pyi::rules::collections_named_tuple(checker, expr); } + if checker.enabled(Rule::UndocumentedWarn) { + flake8_logging::rules::undocumented_warn(checker, expr); + } pandas_vet::rules::attr(checker, attribute); } Expr::Call( diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 45e8a63188f93..f38bdb7a6d4f2 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -920,6 +920,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet), (Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy), + // flake8-logging + (Flake8Logging, "009") => (RuleGroup::Preview, rules::flake8_logging::rules::UndocumentedWarn), + _ => return None, }) } diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 23b54dc9b25fd..55f128b38d0e1 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -199,6 +199,9 @@ pub enum Linter { /// [refurb](https://pypi.org/project/refurb/) #[prefix = "FURB"] Refurb, + /// [flake8-logging](https://pypi.org/project/flake8-logging/) + #[prefix = "LOG"] + Flake8Logging, /// Ruff-specific rules #[prefix = "RUF"] Ruff, diff --git a/crates/ruff/src/rules/flake8_logging/mod.rs b/crates/ruff/src/rules/flake8_logging/mod.rs new file mode 100644 index 0000000000000..05700213f993f --- /dev/null +++ b/crates/ruff/src/rules/flake8_logging/mod.rs @@ -0,0 +1,26 @@ +//! Rules from [flake8-logging](https://pypi.org/project/flake8-logging/). +pub(crate) mod rules; + +#[cfg(test)] +mod tests { + use std::path::Path; + + use anyhow::Result; + use test_case::test_case; + + use crate::assert_messages; + use crate::registry::Rule; + use crate::settings::Settings; + use crate::test::test_path; + + #[test_case(Rule::UndocumentedWarn, Path::new("LOG009.py"))] + fn rules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); + let diagnostics = test_path( + Path::new("flake8_logging").join(path).as_path(), + &Settings::for_rule(rule_code), + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } +} diff --git a/crates/ruff/src/rules/flake8_logging/rules/mod.rs b/crates/ruff/src/rules/flake8_logging/rules/mod.rs new file mode 100644 index 0000000000000..07b712a4f4239 --- /dev/null +++ b/crates/ruff/src/rules/flake8_logging/rules/mod.rs @@ -0,0 +1,3 @@ +pub(crate) use undocumented_warn::*; + +mod undocumented_warn; diff --git a/crates/ruff/src/rules/flake8_logging/rules/undocumented_warn.rs b/crates/ruff/src/rules/flake8_logging/rules/undocumented_warn.rs new file mode 100644 index 0000000000000..abbdf81c38ab1 --- /dev/null +++ b/crates/ruff/src/rules/flake8_logging/rules/undocumented_warn.rs @@ -0,0 +1,71 @@ +use ruff_python_ast::Expr; + +use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; +use crate::importer::ImportRequest; +use crate::registry::AsRule; + +/// ## What it does +/// Checks for uses of `logging.WARN`. +/// +/// ## Why is this bad? +/// The `logging.WARN` constant is an undocumented alias for `logging.WARNING`. +/// +/// Although it’s not explicitly deprecated, `logging.WARN` is not mentioned +/// in the `logging` documentation. Prefer `logging.WARNING` instead. +/// +/// ## Example +/// ```python +/// import logging +/// +/// +/// logging.basicConfig(level=logging.WARN) +/// ``` +/// +/// Use instead: +/// ```python +/// import logging +/// +/// +/// logging.basicConfig(level=logging.WARNING) +/// ``` +#[violation] +pub struct UndocumentedWarn; + +impl Violation for UndocumentedWarn { + const AUTOFIX: AutofixKind = AutofixKind::Sometimes; + #[derive_message_formats] + fn message(&self) -> String { + format!("Use of undocumented `logging.WARN` constant") + } + + fn autofix_title(&self) -> Option { + Some(format!("Replace `logging.WARN` with `logging.WARNING`")) + } +} + +/// LOG009 +pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) { + if checker + .semantic() + .resolve_call_path(expr) + .is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "WARN"])) + { + let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range()); + if checker.patch(diagnostic.kind.rule()) { + diagnostic.try_set_fix(|| { + let (import_edit, binding) = checker.importer().get_or_import_symbol( + &ImportRequest::import("logging", "WARNING"), + expr.range().start(), + checker.semantic(), + )?; + let reference_edit = Edit::range_replacement(binding, expr.range()); + Ok(Fix::suggested_edits(import_edit, [reference_edit])) + }); + } + checker.diagnostics.push(diagnostic); + } +} diff --git a/crates/ruff/src/rules/flake8_logging/snapshots/ruff__rules__flake8_logging__tests__LOG009_LOG009.py.snap b/crates/ruff/src/rules/flake8_logging/snapshots/ruff__rules__flake8_logging__tests__LOG009_LOG009.py.snap new file mode 100644 index 0000000000000..3332f318d569b --- /dev/null +++ b/crates/ruff/src/rules/flake8_logging/snapshots/ruff__rules__flake8_logging__tests__LOG009_LOG009.py.snap @@ -0,0 +1,41 @@ +--- +source: crates/ruff/src/rules/flake8_logging/mod.rs +--- +LOG009.py:3:1: LOG009 [*] Use of undocumented `logging.WARN` constant + | +1 | import logging +2 | +3 | logging.WARN # LOG009 + | ^^^^^^^^^^^^ LOG009 +4 | logging.WARNING # OK + | + = help: Replace `logging.WARN` with `logging.WARNING` + +ℹ Suggested fix +1 1 | import logging +2 2 | +3 |-logging.WARN # LOG009 + 3 |+logging.WARNING # LOG009 +4 4 | logging.WARNING # OK +5 5 | +6 6 | from logging import WARN, WARNING + +LOG009.py:8:1: LOG009 [*] Use of undocumented `logging.WARN` constant + | +6 | from logging import WARN, WARNING +7 | +8 | WARN # LOG009 + | ^^^^ LOG009 +9 | WARNING # OK + | + = help: Replace `logging.WARN` with `logging.WARNING` + +ℹ Suggested fix +5 5 | +6 6 | from logging import WARN, WARNING +7 7 | +8 |-WARN # LOG009 + 8 |+logging.WARNING # LOG009 +9 9 | WARNING # OK + + diff --git a/crates/ruff/src/rules/mod.rs b/crates/ruff/src/rules/mod.rs index 225acb00e9e77..6240d93d12719 100644 --- a/crates/ruff/src/rules/mod.rs +++ b/crates/ruff/src/rules/mod.rs @@ -22,6 +22,7 @@ pub mod flake8_future_annotations; pub mod flake8_gettext; pub mod flake8_implicit_str_concat; pub mod flake8_import_conventions; +pub mod flake8_logging; pub mod flake8_logging_format; pub mod flake8_no_pep420; pub mod flake8_pie; diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 8e98012327659..0f7cc75e8e269 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -852,7 +852,11 @@ mod tests { Rule::QuadraticListSummation, ]; - const PREVIEW_RULES: &[Rule] = &[Rule::TooManyPublicMethods, Rule::SliceCopy]; + const PREVIEW_RULES: &[Rule] = &[ + Rule::TooManyPublicMethods, + Rule::SliceCopy, + Rule::UndocumentedWarn, + ]; #[allow(clippy::needless_pass_by_value)] fn resolve_rules( diff --git a/docs/faq.md b/docs/faq.md index 6002a2fc45a38..62f67d73ad766 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -52,6 +52,7 @@ natively, including: - [flake8-gettext](https://pypi.org/project/flake8-gettext/) - [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/) - [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions) +- [flake8-logging](https://pypi.org/project/flake8-logging-format/) - [flake8-logging-format](https://pypi.org/project/flake8-logging-format/) - [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420) - [flake8-pie](https://pypi.org/project/flake8-pie/) @@ -156,6 +157,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [flake8-gettext](https://pypi.org/project/flake8-gettext/) - [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/) - [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions) +- [flake8-logging](https://pypi.org/project/flake8-logging/) - [flake8-logging-format](https://pypi.org/project/flake8-logging-format/) - [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420) - [flake8-pie](https://pypi.org/project/flake8-pie/) diff --git a/ruff.schema.json b/ruff.schema.json index c672b5b592f25..9b99604d3f554 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2133,6 +2133,10 @@ "ISC001", "ISC002", "ISC003", + "LOG", + "LOG0", + "LOG00", + "LOG009", "N", "N8", "N80",