Implement `flake8-no-pep420` (#1942)

Closes https://github.com/charliermarsh/ruff/issues/1844.
This commit is contained in:
Edgar R. M 2023-01-17 21:10:32 -06:00 committed by GitHub
parent 84d1df08be
commit c880d744fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 200 additions and 1 deletions

View File

@ -59,7 +59,7 @@ There are four phases to adding a new lint rule:
1. Define the violation struct in `src/violations.rs` (e.g., `ModuleImportNotAtTopOfFile`).
2. Map the violation struct to a rule code in `src/registry.rs` (e.g., `E402`).
3. Define the logic for triggering the violation in `src/checkers/ast.rs` (for AST-based checks),
`src/checkers/tokens.rs` (for token-based checks), or `src/checkers/lines.rs` (for text-based checks).
`src/checkers/tokens.rs` (for token-based checks), `src/checkers/lines.rs` (for text-based checks) or `src/checkers/filesystem.rs` (for filesystem-based checks).
4. Add a test fixture.
5. Update the generated files (documentation and generated code).

View File

@ -137,6 +137,7 @@ developer of [Zulip](https://github.com/zulip/zulip):
1. [Pylint (PLC, PLE, PLR, PLW)](#pylint-plc-ple-plr-plw)
1. [flake8-pie (PIE)](#flake8-pie-pie)
1. [flake8-commas (COM)](#flake8-commas-com)
1. [flake8-no-pep420 (INP)](#flake8-no-pep420-inp)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
@ -1158,6 +1159,14 @@ For more, see [flake8-commas](https://pypi.org/project/flake8-commas/2.1.0/) on
| COM818 | TrailingCommaOnBareTupleProhibited | Trailing comma on bare tuple prohibited | |
| COM819 | TrailingCommaProhibited | Trailing comma prohibited | 🛠 |
### flake8-no-pep420 (INP)
For more, see [flake8-no-pep420](https://pypi.org/project/flake8-boolean-trap/2.3.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| INP001 | ImplicitNamespacePackage | File `...` is part of an implicit namespace package. Add an `__init__.py`. | |
### Ruff-specific rules (RUF)
| Code | Name | Message | Fix |
@ -1451,6 +1460,7 @@ natively, including:
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-no-pep420`](https://pypi.org/project/flake8-no-pep420)
- [`flake8-pie`](https://pypi.org/project/flake8-pie/) ([#1543](https://github.com/charliermarsh/ruff/issues/1543))
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
@ -1518,6 +1528,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-errmsg`](https://pypi.org/project/flake8-errmsg/)
- [`flake8-implicit-str-concat`](https://pypi.org/project/flake8-implicit-str-concat/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-no-pep420`](https://pypi.org/project/flake8-no-pep420)
- [`flake8-pie`](https://pypi.org/project/flake8-pie/) ([#1543](https://github.com/charliermarsh/ruff/issues/1543))
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 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.

View File

@ -0,0 +1 @@
print('hi')

View File

@ -0,0 +1,2 @@
#!/bin/env/python
print('hi')

View File

@ -0,0 +1 @@
import os # noqa: INP001

View File

@ -1356,6 +1356,10 @@
"ICN0",
"ICN00",
"ICN001",
"INP",
"INP0",
"INP00",
"INP001",
"ISC",
"ISC0",
"ISC00",

View File

@ -0,0 +1,18 @@
use std::path::Path;
use crate::registry::{Diagnostic, RuleCode};
use crate::rules::flake8_no_pep420::rules::implicit_namespace_package;
use crate::settings::Settings;
pub fn check_file_path(path: &Path, settings: &Settings) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
// flake8-no-pep420
if settings.rules.enabled(&RuleCode::INP001) {
if let Some(diagnostic) = implicit_namespace_package(path) {
diagnostics.push(diagnostic);
}
}
diagnostics
}

View File

@ -1,4 +1,5 @@
pub mod ast;
pub mod filesystem;
pub mod imports;
pub mod lines;
pub mod noqa;

View File

@ -7,6 +7,7 @@ use rustpython_parser::lexer::LexResult;
use crate::ast::types::Range;
use crate::autofix::fix_file;
use crate::checkers::ast::check_ast;
use crate::checkers::filesystem::check_file_path;
use crate::checkers::imports::check_imports;
use crate::checkers::lines::check_lines;
use crate::checkers::noqa::check_noqa;
@ -62,6 +63,15 @@ pub fn check_path(
diagnostics.extend(check_tokens(locator, &tokens, settings, autofix));
}
// Run the filesystem-based rules.
if settings
.rules
.iter_enabled()
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Filesystem))
{
diagnostics.extend(check_file_path(path, settings));
}
// Run the AST-based rules.
let use_ast = settings
.rules

View File

@ -416,6 +416,8 @@ ruff_macros::define_rule_mapping!(
COM812 => violations::TrailingCommaMissing,
COM818 => violations::TrailingCommaOnBareTupleProhibited,
COM819 => violations::TrailingCommaProhibited,
// flake8-no-pep420
INP001 => violations::ImplicitNamespacePackage,
// Ruff
RUF001 => violations::AmbiguousUnicodeCharacterString,
RUF002 => violations::AmbiguousUnicodeCharacterDocstring,
@ -459,6 +461,7 @@ pub enum RuleOrigin {
Pylint,
Flake8Pie,
Flake8Commas,
Flake8NoPep420,
Ruff,
}
@ -525,6 +528,7 @@ impl RuleOrigin {
RuleOrigin::Pyupgrade => Prefixes::Single(RuleCodePrefix::UP),
RuleOrigin::Flake8Pie => Prefixes::Single(RuleCodePrefix::PIE),
RuleOrigin::Flake8Commas => Prefixes::Single(RuleCodePrefix::COM),
RuleOrigin::Flake8NoPep420 => Prefixes::Single(RuleCodePrefix::INP),
RuleOrigin::Ruff => Prefixes::Single(RuleCodePrefix::RUF),
}
}
@ -537,6 +541,7 @@ pub enum LintSource {
Tokens,
Imports,
NoQa,
Filesystem,
}
impl RuleCode {
@ -567,6 +572,7 @@ impl RuleCode {
| RuleCode::RUF003 => &LintSource::Tokens,
RuleCode::E902 => &LintSource::Io,
RuleCode::I001 | RuleCode::I002 => &LintSource::Imports,
RuleCode::INP001 => &LintSource::Filesystem,
_ => &LintSource::Ast,
}
}

View File

@ -0,0 +1,32 @@
//! Rules from [flake8-no-pep420](https://pypi.org/project/flake8-boolean-trap/2.3.0/).
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::linter::test_path;
use crate::registry::RuleCode;
use crate::settings::Settings;
#[test_case(Path::new("test_pass"); "INP001_0")]
#[test_case(Path::new("test_fail_empty"); "INP001_1")]
#[test_case(Path::new("test_fail_nonempty"); "INP001_2")]
#[test_case(Path::new("test_fail_shebang"); "INP001_3")]
#[test_case(Path::new("test_ignored"); "INP001_4")]
fn test_flake8_no_pep420(path: &Path) -> Result<()> {
let snapshot = format!("{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("./resources/test/fixtures/flake8_no_pep420")
.join(path)
.join("example.py")
.as_path(),
&Settings::for_rule(RuleCode::INP001),
)?;
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}
}

View File

@ -0,0 +1,18 @@
use std::path::Path;
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violations;
/// INP001
pub fn implicit_namespace_package(path: &Path) -> Option<Diagnostic> {
if let Some(parent) = path.parent() {
if !parent.join("__init__.py").as_path().exists() {
return Some(Diagnostic::new(
violations::ImplicitNamespacePackage(path.to_string_lossy().to_string()),
Range::default(),
));
}
}
None
}

View File

@ -0,0 +1,15 @@
---
source: src/rules/flake8_no_pep420/mod.rs
expression: diagnostics
---
- kind:
ImplicitNamespacePackage: "./resources/test/fixtures/flake8_no_pep420/test_fail_empty/example.py"
location:
row: 1
column: 0
end_location:
row: 1
column: 0
fix: ~
parent: ~

View File

@ -0,0 +1,15 @@
---
source: src/rules/flake8_no_pep420/mod.rs
expression: diagnostics
---
- kind:
ImplicitNamespacePackage: "./resources/test/fixtures/flake8_no_pep420/test_fail_nonempty/example.py"
location:
row: 1
column: 0
end_location:
row: 1
column: 0
fix: ~
parent: ~

View File

@ -0,0 +1,15 @@
---
source: src/rules/flake8_no_pep420/mod.rs
expression: diagnostics
---
- kind:
ImplicitNamespacePackage: "./resources/test/fixtures/flake8_no_pep420/test_fail_shebang/example.py"
location:
row: 1
column: 0
end_location:
row: 1
column: 0
fix: ~
parent: ~

View File

@ -0,0 +1,6 @@
---
source: src/rules/flake8_no_pep420/mod.rs
expression: diagnostics
---
[]

View File

@ -0,0 +1,6 @@
---
source: src/rules/flake8_no_pep420/mod.rs
expression: diagnostics
---
[]

View File

@ -13,6 +13,7 @@ pub mod flake8_debugger;
pub mod flake8_errmsg;
pub mod flake8_implicit_str_concat;
pub mod flake8_import_conventions;
pub mod flake8_no_pep420;
pub mod flake8_pie;
pub mod flake8_print;
pub mod flake8_pytest_style;

View File

@ -6088,6 +6088,22 @@ impl AlwaysAutofixableViolation for TrailingCommaProhibited {
}
}
// flake8-no-pep420
define_violation!(
pub struct ImplicitNamespacePackage(pub String);
);
impl Violation for ImplicitNamespacePackage {
fn message(&self) -> String {
let ImplicitNamespacePackage(filename) = self;
format!("File `{filename}` is part of an implicit namespace package. Add an `__init__.py`.")
}
fn placeholder() -> Self {
ImplicitNamespacePackage("...".to_string())
}
}
// Ruff
define_violation!(