Skip to content

Commit 6d5d079

Browse files
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 #8690.
1 parent d1e88dc commit 6d5d079

File tree

9 files changed

+52
-44
lines changed

9 files changed

+52
-44
lines changed

crates/ruff_linter/src/checkers/filesystem.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::path::Path;
22

33
use ruff_diagnostics::Diagnostic;
4+
use ruff_python_index::Indexer;
5+
use ruff_source_file::Locator;
46

57
use crate::registry::Rule;
68
use crate::rules::flake8_no_pep420::rules::implicit_namespace_package;
@@ -10,15 +12,22 @@ use crate::settings::LinterSettings;
1012
pub(crate) fn check_file_path(
1113
path: &Path,
1214
package: Option<&Path>,
15+
locator: &Locator,
16+
indexer: &Indexer,
1317
settings: &LinterSettings,
1418
) -> Vec<Diagnostic> {
1519
let mut diagnostics: Vec<Diagnostic> = vec![];
1620

1721
// flake8-no-pep420
1822
if settings.rules.enabled(Rule::ImplicitNamespacePackage) {
19-
if let Some(diagnostic) =
20-
implicit_namespace_package(path, package, &settings.project_root, &settings.src)
21-
{
23+
if let Some(diagnostic) = implicit_namespace_package(
24+
path,
25+
package,
26+
locator,
27+
indexer,
28+
&settings.project_root,
29+
&settings.src,
30+
) {
2231
diagnostics.push(diagnostic);
2332
}
2433
}

crates/ruff_linter/src/checkers/tokens.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ pub(crate) fn check_tokens(
167167
Rule::ShebangNotFirstLine,
168168
Rule::ShebangMissingPython,
169169
]) {
170-
flake8_executable::rules::from_tokens(tokens, path, locator, &mut diagnostics);
170+
flake8_executable::rules::from_tokens(&mut diagnostics, path, locator, indexer);
171171
}
172172

173173
if settings.rules.any_enabled(&[

crates/ruff_linter/src/linter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub fn check_path(
117117
.iter_enabled()
118118
.any(|rule_code| rule_code.lint_source().is_filesystem())
119119
{
120-
diagnostics.extend(check_file_path(path, package, settings));
120+
diagnostics.extend(check_file_path(path, package, locator, indexer, settings));
121121
}
122122

123123
// Run the logical line-based rules.

crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
use std::path::Path;
22

3-
use ruff_python_parser::lexer::LexResult;
4-
use ruff_python_parser::Tok;
5-
63
use ruff_diagnostics::Diagnostic;
4+
use ruff_python_index::Indexer;
75
use ruff_source_file::Locator;
86
pub(crate) use shebang_leading_whitespace::*;
97
pub(crate) use shebang_missing_executable_file::*;
@@ -20,32 +18,31 @@ mod shebang_not_executable;
2018
mod shebang_not_first_line;
2119

2220
pub(crate) fn from_tokens(
23-
tokens: &[LexResult],
21+
diagnostics: &mut Vec<Diagnostic>,
2422
path: &Path,
2523
locator: &Locator,
26-
diagnostics: &mut Vec<Diagnostic>,
24+
indexer: &Indexer,
2725
) {
2826
let mut has_any_shebang = false;
29-
for (tok, range) in tokens.iter().flatten() {
30-
if let Tok::Comment(comment) = tok {
31-
if let Some(shebang) = ShebangDirective::try_extract(comment) {
32-
has_any_shebang = true;
33-
34-
if let Some(diagnostic) = shebang_missing_python(*range, &shebang) {
35-
diagnostics.push(diagnostic);
36-
}
37-
38-
if let Some(diagnostic) = shebang_not_executable(path, *range) {
39-
diagnostics.push(diagnostic);
40-
}
41-
42-
if let Some(diagnostic) = shebang_leading_whitespace(*range, locator) {
43-
diagnostics.push(diagnostic);
44-
}
45-
46-
if let Some(diagnostic) = shebang_not_first_line(*range, locator) {
47-
diagnostics.push(diagnostic);
48-
}
27+
for range in indexer.comment_ranges() {
28+
let comment = locator.slice(*range);
29+
if let Some(shebang) = ShebangDirective::try_extract(comment) {
30+
has_any_shebang = true;
31+
32+
if let Some(diagnostic) = shebang_missing_python(*range, &shebang) {
33+
diagnostics.push(diagnostic);
34+
}
35+
36+
if let Some(diagnostic) = shebang_not_executable(path, *range) {
37+
diagnostics.push(diagnostic);
38+
}
39+
40+
if let Some(diagnostic) = shebang_leading_whitespace(*range, locator) {
41+
diagnostics.push(diagnostic);
42+
}
43+
44+
if let Some(diagnostic) = shebang_not_first_line(*range, locator) {
45+
diagnostics.push(diagnostic);
4946
}
5047
}
5148
}

crates/ruff_linter/src/rules/flake8_no_pep420/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mod tests {
1616
#[test_case(Path::new("test_pass_init"), Path::new("example.py"))]
1717
#[test_case(Path::new("test_fail_empty"), Path::new("example.py"))]
1818
#[test_case(Path::new("test_fail_nonempty"), Path::new("example.py"))]
19-
#[test_case(Path::new("test_fail_shebang"), Path::new("example.py"))]
19+
#[test_case(Path::new("test_pass_shebang"), Path::new("example.py"))]
2020
#[test_case(Path::new("test_ignored"), Path::new("example.py"))]
2121
#[test_case(Path::new("test_pass_namespace_package"), Path::new("example.py"))]
2222
#[test_case(Path::new("test_pass_pyi"), Path::new("example.pyi"))]

crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use std::path::{Path, PathBuf};
22

3-
use ruff_text_size::TextRange;
4-
53
use ruff_diagnostics::{Diagnostic, Violation};
64
use ruff_macros::{derive_message_formats, violation};
5+
use ruff_python_index::Indexer;
6+
use ruff_source_file::Locator;
7+
use ruff_text_size::{TextRange, TextSize};
78

9+
use crate::comments::shebang::ShebangDirective;
810
use crate::fs;
911

1012
/// ## What it does
@@ -42,6 +44,8 @@ impl Violation for ImplicitNamespacePackage {
4244
pub(crate) fn implicit_namespace_package(
4345
path: &Path,
4446
package: Option<&Path>,
47+
locator: &Locator,
48+
indexer: &Indexer,
4549
project_root: &Path,
4650
src: &[PathBuf],
4751
) -> Option<Diagnostic> {
@@ -56,6 +60,11 @@ pub(crate) fn implicit_namespace_package(
5660
&& !path
5761
.parent()
5862
.is_some_and( |parent| src.iter().any(|src| src == parent))
63+
// Ignore files that contain a shebang.
64+
&& !indexer
65+
.comment_ranges()
66+
.first().filter(|range| range.start() == TextSize::from(0))
67+
.is_some_and(|range| ShebangDirective::try_extract(locator.slice(*range)).is_some())
5968
{
6069
#[cfg(all(test, windows))]
6170
let path = path

crates/ruff_linter/src/rules/flake8_no_pep420/snapshots/ruff_linter__rules__flake8_no_pep420__tests__test_fail_shebang.snap

Lines changed: 0 additions & 11 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
source: crates/ruff_linter/src/rules/flake8_no_pep420/mod.rs
3+
---
4+

0 commit comments

Comments
 (0)