Implement EXE001 and EXE002 from flake8-executable (#2118)
This commit is contained in:
@@ -1188,6 +1188,8 @@ For more, see [flake8-executable](https://pypi.org/project/flake8-executable/) o
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| EXE001 | shebang-not-executable | Shebang is present but file is not executable | |
|
||||
| EXE002 | shebang-missing-executable-file | The file is executable but no shebang is present | |
|
||||
| EXE003 | shebang-python | Shebang should contain "python" | |
|
||||
| EXE004 | shebang-whitespace | Avoid whitespace before shebang | 🛠 |
|
||||
| EXE005 | shebang-newline | Shebang should be at the beginning of the file | |
|
||||
|
||||
4
resources/test/fixtures/flake8_executable/EXE001_1.py
vendored
Normal file
4
resources/test/fixtures/flake8_executable/EXE001_1.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
2
resources/test/fixtures/flake8_executable/EXE001_2.py
vendored
Normal file
2
resources/test/fixtures/flake8_executable/EXE001_2.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
4
resources/test/fixtures/flake8_executable/EXE001_3.py
vendored
Executable file
4
resources/test/fixtures/flake8_executable/EXE001_3.py
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
2
resources/test/fixtures/flake8_executable/EXE002_1.py
vendored
Executable file
2
resources/test/fixtures/flake8_executable/EXE002_1.py
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
2
resources/test/fixtures/flake8_executable/EXE002_2.py
vendored
Normal file
2
resources/test/fixtures/flake8_executable/EXE002_2.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
4
resources/test/fixtures/flake8_executable/EXE002_3.py
vendored
Executable file
4
resources/test/fixtures/flake8_executable/EXE002_3.py
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
0
resources/test/fixtures/flake8_executable/EXE003.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE003.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE004_1.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE004_1.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE004_3.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE004_3.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_1.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_1.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_2.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_2.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_3.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_3.py
vendored
Normal file → Executable file
@@ -1405,6 +1405,8 @@
|
||||
"EXE",
|
||||
"EXE0",
|
||||
"EXE00",
|
||||
"EXE001",
|
||||
"EXE002",
|
||||
"EXE003",
|
||||
"EXE004",
|
||||
"EXE005",
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
//! Lint rules based on checking raw physical lines.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::rules::flake8_executable::helpers::extract_shebang;
|
||||
use crate::rules::flake8_executable::rules::{shebang_newline, shebang_python, shebang_whitespace};
|
||||
use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective};
|
||||
use crate::rules::flake8_executable::rules::{
|
||||
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
|
||||
};
|
||||
use crate::rules::pycodestyle::rules::{
|
||||
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
|
||||
};
|
||||
@@ -11,6 +15,7 @@ use crate::rules::pyupgrade::rules::unnecessary_coding_comment;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
pub fn check_lines(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
commented_lines: &[usize],
|
||||
doc_lines: &[usize],
|
||||
@@ -18,8 +23,11 @@ pub fn check_lines(
|
||||
autofix: flags::Autofix,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
let mut has_any_shebang = false;
|
||||
|
||||
let enforce_blanket_noqa = settings.rules.enabled(&Rule::BlanketNOQA);
|
||||
let enforce_shebang_not_executable = settings.rules.enabled(&Rule::ShebangNotExecutable);
|
||||
let enforce_shebang_missing = settings.rules.enabled(&Rule::ShebangMissingExecutableFile);
|
||||
let enforce_shebang_whitespace = settings.rules.enabled(&Rule::ShebangWhitespace);
|
||||
let enforce_shebang_newline = settings.rules.enabled(&Rule::ShebangNewline);
|
||||
let enforce_shebang_python = settings.rules.enabled(&Rule::ShebangPython);
|
||||
@@ -68,8 +76,23 @@ pub fn check_lines(
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_shebang_whitespace || enforce_shebang_newline || enforce_shebang_python {
|
||||
if enforce_shebang_missing
|
||||
|| enforce_shebang_not_executable
|
||||
|| enforce_shebang_whitespace
|
||||
|| enforce_shebang_newline
|
||||
|| enforce_shebang_python
|
||||
{
|
||||
let shebang = extract_shebang(line);
|
||||
if enforce_shebang_not_executable {
|
||||
if let Some(diagnostic) = shebang_not_executable(path, index, &shebang) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if enforce_shebang_missing {
|
||||
if !has_any_shebang && matches!(shebang, ShebangDirective::Match(_, _, _, _)) {
|
||||
has_any_shebang = true;
|
||||
}
|
||||
}
|
||||
if enforce_shebang_whitespace {
|
||||
if let Some(diagnostic) =
|
||||
shebang_whitespace(index, &shebang, fix_shebang_whitespace)
|
||||
@@ -124,12 +147,20 @@ pub fn check_lines(
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_shebang_missing && !has_any_shebang {
|
||||
if let Some(diagnostic) = shebang_missing(path) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
diagnostics
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use super::check_lines;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::{flags, Settings};
|
||||
@@ -139,6 +170,7 @@ mod tests {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
check_lines(
|
||||
Path::new("foo.py"),
|
||||
line,
|
||||
&[],
|
||||
&[],
|
||||
|
||||
@@ -141,6 +141,7 @@ pub fn check_path(
|
||||
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Lines))
|
||||
{
|
||||
diagnostics.extend(check_lines(
|
||||
path,
|
||||
contents,
|
||||
indexer.commented_lines(),
|
||||
&doc_lines,
|
||||
|
||||
@@ -424,6 +424,8 @@ ruff_macros::define_rule_mapping!(
|
||||
// flake8-no-pep420
|
||||
INP001 => violations::ImplicitNamespacePackage,
|
||||
// flake8-executable
|
||||
EXE001 => rules::flake8_executable::rules::ShebangNotExecutable,
|
||||
EXE002 => rules::flake8_executable::rules::ShebangMissingExecutableFile,
|
||||
EXE003 => rules::flake8_executable::rules::ShebangPython,
|
||||
EXE004 => rules::flake8_executable::rules::ShebangWhitespace,
|
||||
EXE005 => rules::flake8_executable::rules::ShebangNewline,
|
||||
@@ -646,6 +648,8 @@ impl Rule {
|
||||
| Rule::MixedSpacesAndTabs
|
||||
| Rule::NoNewLineAtEndOfFile
|
||||
| Rule::PEP3120UnnecessaryCodingComment
|
||||
| Rule::ShebangMissingExecutableFile
|
||||
| Rule::ShebangNotExecutable
|
||||
| Rule::ShebangNewline
|
||||
| Rule::ShebangPython
|
||||
| Rule::ShebangWhitespace => &LintSource::Lines,
|
||||
|
||||
@@ -13,6 +13,12 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(Path::new("EXE001_1.py"); "EXE001_1")]
|
||||
#[test_case(Path::new("EXE001_2.py"); "EXE001_2")]
|
||||
#[test_case(Path::new("EXE001_3.py"); "EXE001_3")]
|
||||
#[test_case(Path::new("EXE002_1.py"); "EXE002_1")]
|
||||
#[test_case(Path::new("EXE002_2.py"); "EXE002_2")]
|
||||
#[test_case(Path::new("EXE002_3.py"); "EXE002_3")]
|
||||
#[test_case(Path::new("EXE003.py"); "EXE003")]
|
||||
#[test_case(Path::new("EXE004_1.py"); "EXE004_1")]
|
||||
#[test_case(Path::new("EXE004_2.py"); "EXE004_2")]
|
||||
@@ -27,6 +33,8 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rules(vec![
|
||||
Rule::ShebangNotExecutable,
|
||||
Rule::ShebangMissingExecutableFile,
|
||||
Rule::ShebangWhitespace,
|
||||
Rule::ShebangNewline,
|
||||
Rule::ShebangPython,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
pub use shebang_missing::{shebang_missing, ShebangMissingExecutableFile};
|
||||
pub use shebang_newline::{shebang_newline, ShebangNewline};
|
||||
pub use shebang_not_executable::{shebang_not_executable, ShebangNotExecutable};
|
||||
pub use shebang_python::{shebang_python, ShebangPython};
|
||||
pub use shebang_whitespace::{shebang_whitespace, ShebangWhitespace};
|
||||
|
||||
mod shebang_missing;
|
||||
mod shebang_newline;
|
||||
mod shebang_not_executable;
|
||||
mod shebang_python;
|
||||
mod shebang_whitespace;
|
||||
|
||||
42
src/rules/flake8_executable/rules/shebang_missing.rs
Normal file
42
src/rules/flake8_executable/rules/shebang_missing.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use crate::ast::types::Range;
|
||||
use crate::define_violation;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct ShebangMissingExecutableFile;
|
||||
);
|
||||
impl Violation for ShebangMissingExecutableFile {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("The file is executable but no shebang is present")
|
||||
}
|
||||
}
|
||||
|
||||
/// EXE002
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn shebang_missing(filepath: &Path) -> Option<Diagnostic> {
|
||||
if let Ok(metadata) = filepath.metadata() {
|
||||
// Check if file is executable by anyone
|
||||
if metadata.mode() & 0o111 == 0 {
|
||||
None
|
||||
} else {
|
||||
let diagnostic = Diagnostic::new(ShebangMissingExecutableFile, Range::default());
|
||||
Some(diagnostic)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub fn shebang_missing(_filepath: &Path) -> Option<Diagnostic> {
|
||||
None
|
||||
}
|
||||
63
src/rules/flake8_executable/rules/shebang_not_executable.rs
Normal file
63
src/rules/flake8_executable/rules/shebang_not_executable.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_macros::derive_message_formats;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rustpython_ast::Location;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use crate::ast::types::Range;
|
||||
use crate::define_violation;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::flake8_executable::helpers::ShebangDirective;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct ShebangNotExecutable;
|
||||
);
|
||||
impl Violation for ShebangNotExecutable {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Shebang is present but file is not executable")
|
||||
}
|
||||
}
|
||||
|
||||
/// EXE001
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn shebang_not_executable(
|
||||
filepath: &Path,
|
||||
lineno: usize,
|
||||
shebang: &ShebangDirective,
|
||||
) -> Option<Diagnostic> {
|
||||
if let ShebangDirective::Match(_, start, end, _) = shebang {
|
||||
if let Ok(metadata) = filepath.metadata() {
|
||||
// Check if file is executable by anyone
|
||||
if metadata.mode() & 0o111 == 0 {
|
||||
let diagnostic = Diagnostic::new(
|
||||
ShebangNotExecutable,
|
||||
Range::new(
|
||||
Location::new(lineno + 1, *start),
|
||||
Location::new(lineno + 1, *end),
|
||||
),
|
||||
);
|
||||
Some(diagnostic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub fn shebang_not_executable(
|
||||
_filepath: &Path,
|
||||
_lineno: usize,
|
||||
_shebang: &ShebangDirective,
|
||||
) -> Option<Diagnostic> {
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ShebangNotExecutable: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 2
|
||||
end_location:
|
||||
row: 1
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ShebangMissingExecutableFile: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
Reference in New Issue
Block a user