Implement EXE001 and EXE002 from flake8-executable (#2118)

This commit is contained in:
Edgar R. M
2023-01-24 07:02:47 -06:00
committed by GitHub
parent ca58c72fc9
commit f5f0ed280a
27 changed files with 233 additions and 3 deletions

View File

@@ -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 | |

View File

@@ -0,0 +1,4 @@
#!/usr/bin/python
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -0,0 +1,2 @@
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -0,0 +1,4 @@
#!/usr/bin/python
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -0,0 +1,2 @@
if __name__ == '__main__':
print('I should be executable.')

View File

@@ -0,0 +1,2 @@
if __name__ == '__main__':
print('I should be executable.')

View 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
View File

0
resources/test/fixtures/flake8_executable/EXE004_1.py vendored Normal file → Executable file
View File

0
resources/test/fixtures/flake8_executable/EXE004_3.py vendored Normal file → Executable file
View File

0
resources/test/fixtures/flake8_executable/EXE005_1.py vendored Normal file → Executable file
View File

0
resources/test/fixtures/flake8_executable/EXE005_2.py vendored Normal file → Executable file
View File

0
resources/test/fixtures/flake8_executable/EXE005_3.py vendored Normal file → Executable file
View File

View File

@@ -1405,6 +1405,8 @@
"EXE",
"EXE0",
"EXE00",
"EXE001",
"EXE002",
"EXE003",
"EXE004",
"EXE005",

View File

@@ -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,
&[],
&[],

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View 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
}

View 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
}

View File

@@ -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: ~

View File

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

View File

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

View File

@@ -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: ~

View File

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

View File

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