Avoid missing namespace violations in scripts with shebangs (#8710)

## Summary

I think it's reasonable to avoid raising `INP001` for scripts, and
shebangs are one sufficient way to detect scripts.

Closes https://github.com/astral-sh/ruff/issues/8690.
This commit is contained in:
Charlie Marsh 2023-11-16 14:21:33 -08:00 committed by GitHub
parent d1e88dc984
commit 6d5d079a18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 48 additions and 40 deletions

View file

@ -1,6 +1,8 @@
use std::path::Path;
use ruff_diagnostics::Diagnostic;
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use crate::registry::Rule;
use crate::rules::flake8_no_pep420::rules::implicit_namespace_package;
@ -10,15 +12,22 @@ use crate::settings::LinterSettings;
pub(crate) fn check_file_path(
path: &Path,
package: Option<&Path>,
locator: &Locator,
indexer: &Indexer,
settings: &LinterSettings,
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
// flake8-no-pep420
if settings.rules.enabled(Rule::ImplicitNamespacePackage) {
if let Some(diagnostic) =
implicit_namespace_package(path, package, &settings.project_root, &settings.src)
{
if let Some(diagnostic) = implicit_namespace_package(
path,
package,
locator,
indexer,
&settings.project_root,
&settings.src,
) {
diagnostics.push(diagnostic);
}
}

View file

@ -167,7 +167,7 @@ pub(crate) fn check_tokens(
Rule::ShebangNotFirstLine,
Rule::ShebangMissingPython,
]) {
flake8_executable::rules::from_tokens(tokens, path, locator, &mut diagnostics);
flake8_executable::rules::from_tokens(&mut diagnostics, path, locator, indexer);
}
if settings.rules.any_enabled(&[

View file

@ -117,7 +117,7 @@ pub fn check_path(
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_filesystem())
{
diagnostics.extend(check_file_path(path, package, settings));
diagnostics.extend(check_file_path(path, package, locator, indexer, settings));
}
// Run the logical line-based rules.

View file

@ -1,9 +1,7 @@
use std::path::Path;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
use ruff_diagnostics::Diagnostic;
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
pub(crate) use shebang_leading_whitespace::*;
pub(crate) use shebang_missing_executable_file::*;
@ -20,14 +18,14 @@ mod shebang_not_executable;
mod shebang_not_first_line;
pub(crate) fn from_tokens(
tokens: &[LexResult],
diagnostics: &mut Vec<Diagnostic>,
path: &Path,
locator: &Locator,
diagnostics: &mut Vec<Diagnostic>,
indexer: &Indexer,
) {
let mut has_any_shebang = false;
for (tok, range) in tokens.iter().flatten() {
if let Tok::Comment(comment) = tok {
for range in indexer.comment_ranges() {
let comment = locator.slice(*range);
if let Some(shebang) = ShebangDirective::try_extract(comment) {
has_any_shebang = true;
@ -48,7 +46,6 @@ pub(crate) fn from_tokens(
}
}
}
}
if !has_any_shebang {
if let Some(diagnostic) = shebang_missing_executable_file(path) {

View file

@ -16,7 +16,7 @@ mod tests {
#[test_case(Path::new("test_pass_init"), Path::new("example.py"))]
#[test_case(Path::new("test_fail_empty"), Path::new("example.py"))]
#[test_case(Path::new("test_fail_nonempty"), Path::new("example.py"))]
#[test_case(Path::new("test_fail_shebang"), Path::new("example.py"))]
#[test_case(Path::new("test_pass_shebang"), Path::new("example.py"))]
#[test_case(Path::new("test_ignored"), Path::new("example.py"))]
#[test_case(Path::new("test_pass_namespace_package"), Path::new("example.py"))]
#[test_case(Path::new("test_pass_pyi"), Path::new("example.pyi"))]

View file

@ -1,10 +1,12 @@
use std::path::{Path, PathBuf};
use ruff_text_size::TextRange;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use ruff_text_size::{TextRange, TextSize};
use crate::comments::shebang::ShebangDirective;
use crate::fs;
/// ## What it does
@ -42,6 +44,8 @@ impl Violation for ImplicitNamespacePackage {
pub(crate) fn implicit_namespace_package(
path: &Path,
package: Option<&Path>,
locator: &Locator,
indexer: &Indexer,
project_root: &Path,
src: &[PathBuf],
) -> Option<Diagnostic> {
@ -56,6 +60,11 @@ pub(crate) fn implicit_namespace_package(
&& !path
.parent()
.is_some_and( |parent| src.iter().any(|src| src == parent))
// Ignore files that contain a shebang.
&& !indexer
.comment_ranges()
.first().filter(|range| range.start() == TextSize::from(0))
.is_some_and(|range| ShebangDirective::try_extract(locator.slice(*range)).is_some())
{
#[cfg(all(test, windows))]
let path = path

View file

@ -1,11 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_no_pep420/mod.rs
---
example.py:1:1: INP001 File `./resources/test/fixtures/flake8_no_pep420/test_fail_shebang/example.py` is part of an implicit namespace package. Add an `__init__.py`.
|
1 | #!/bin/env/python
| INP001
2 | print('hi')
|

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/flake8_no_pep420/mod.rs
---