mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:53 +00:00
Move flake8-executable
rules out of physical lines checker (#6039)
## Summary These only need the token stream, and we always prefer token-based to physical line-based rules. There are a few other changes snuck in here: - Renaming the rule files to match the diagnostic names (likely an error). - The "leading whitespace before shebang" rule now works regardless of where the comment occurs (i.e., if the shebang is on the second line, and the first line is blank, we flag and remove that leading whitespace).
This commit is contained in:
parent
7f3797185c
commit
776d598738
22 changed files with 235 additions and 224 deletions
2
crates/ruff/resources/test/fixtures/flake8_executable/EXE004_4.py
vendored
Executable file
2
crates/ruff/resources/test/fixtures/flake8_executable/EXE004_4.py
vendored
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
#!/usr/bin/env python
|
|
@ -1,18 +1,12 @@
|
||||||
//! Lint rules based on checking physical lines.
|
//! Lint rules based on checking physical lines.
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use ruff_text_size::TextSize;
|
use ruff_text_size::TextSize;
|
||||||
|
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
|
||||||
use ruff_python_trivia::UniversalNewlines;
|
use ruff_python_trivia::UniversalNewlines;
|
||||||
|
|
||||||
use crate::comments::shebang::ShebangDirective;
|
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rules::flake8_copyright::rules::missing_copyright_notice;
|
use crate::rules::flake8_copyright::rules::missing_copyright_notice;
|
||||||
use crate::rules::flake8_executable::rules::{
|
|
||||||
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
|
|
||||||
};
|
|
||||||
use crate::rules::pycodestyle::rules::{
|
use crate::rules::pycodestyle::rules::{
|
||||||
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
|
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
|
||||||
tab_indentation, trailing_whitespace,
|
tab_indentation, trailing_whitespace,
|
||||||
|
@ -22,7 +16,6 @@ use crate::rules::pyupgrade::rules::unnecessary_coding_comment;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
|
|
||||||
pub(crate) fn check_physical_lines(
|
pub(crate) fn check_physical_lines(
|
||||||
path: &Path,
|
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
|
@ -30,13 +23,7 @@ pub(crate) fn check_physical_lines(
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||||
let mut has_any_shebang = false;
|
|
||||||
|
|
||||||
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::ShebangLeadingWhitespace);
|
|
||||||
let enforce_shebang_newline = settings.rules.enabled(Rule::ShebangNotFirstLine);
|
|
||||||
let enforce_shebang_python = settings.rules.enabled(Rule::ShebangMissingPython);
|
|
||||||
let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong);
|
let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong);
|
||||||
let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong);
|
let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong);
|
||||||
let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::MissingNewlineAtEndOfFile);
|
let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::MissingNewlineAtEndOfFile);
|
||||||
|
@ -50,7 +37,6 @@ pub(crate) fn check_physical_lines(
|
||||||
let enforce_copyright_notice = settings.rules.enabled(Rule::MissingCopyrightNotice);
|
let enforce_copyright_notice = settings.rules.enabled(Rule::MissingCopyrightNotice);
|
||||||
|
|
||||||
let fix_unnecessary_coding_comment = settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
|
let fix_unnecessary_coding_comment = settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
|
||||||
let fix_shebang_whitespace = settings.rules.should_fix(Rule::ShebangLeadingWhitespace);
|
|
||||||
|
|
||||||
let mut commented_lines_iter = indexer.comment_ranges().iter().peekable();
|
let mut commented_lines_iter = indexer.comment_ranges().iter().peekable();
|
||||||
let mut doc_lines_iter = doc_lines.iter().peekable();
|
let mut doc_lines_iter = doc_lines.iter().peekable();
|
||||||
|
@ -69,43 +55,6 @@ pub(crate) fn check_physical_lines(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if enforce_shebang_missing
|
|
||||||
|| enforce_shebang_not_executable
|
|
||||||
|| enforce_shebang_whitespace
|
|
||||||
|| enforce_shebang_newline
|
|
||||||
|| enforce_shebang_python
|
|
||||||
{
|
|
||||||
if let Some(shebang) = ShebangDirective::try_extract(&line) {
|
|
||||||
has_any_shebang = true;
|
|
||||||
if enforce_shebang_not_executable {
|
|
||||||
if let Some(diagnostic) =
|
|
||||||
shebang_not_executable(path, line.range(), &shebang)
|
|
||||||
{
|
|
||||||
diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if enforce_shebang_whitespace {
|
|
||||||
if let Some(diagnostic) =
|
|
||||||
shebang_whitespace(line.range(), &shebang, fix_shebang_whitespace)
|
|
||||||
{
|
|
||||||
diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if enforce_shebang_newline {
|
|
||||||
if let Some(diagnostic) =
|
|
||||||
shebang_newline(line.range(), &shebang, index == 0)
|
|
||||||
{
|
|
||||||
diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if enforce_shebang_python {
|
|
||||||
if let Some(diagnostic) = shebang_python(line.range(), &shebang) {
|
|
||||||
diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while doc_lines_iter
|
while doc_lines_iter
|
||||||
|
@ -158,12 +107,6 @@ pub(crate) fn check_physical_lines(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if enforce_shebang_missing && !has_any_shebang {
|
|
||||||
if let Some(diagnostic) = shebang_missing(path) {
|
|
||||||
diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if enforce_copyright_notice {
|
if enforce_copyright_notice {
|
||||||
if let Some(diagnostic) = missing_copyright_notice(locator, settings) {
|
if let Some(diagnostic) = missing_copyright_notice(locator, settings) {
|
||||||
diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
|
@ -175,8 +118,6 @@ pub(crate) fn check_physical_lines(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use rustpython_parser::lexer::lex;
|
use rustpython_parser::lexer::lex;
|
||||||
use rustpython_parser::Mode;
|
use rustpython_parser::Mode;
|
||||||
|
|
||||||
|
@ -198,7 +139,6 @@ mod tests {
|
||||||
|
|
||||||
let check_with_max_line_length = |line_length: LineLength| {
|
let check_with_max_line_length = |line_length: LineLength| {
|
||||||
check_physical_lines(
|
check_physical_lines(
|
||||||
Path::new("foo.py"),
|
|
||||||
&locator,
|
&locator,
|
||||||
&stylist,
|
&stylist,
|
||||||
&indexer,
|
&indexer,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! Lint rules based on token traversal.
|
//! Lint rules based on token traversal.
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use rustpython_parser::lexer::LexResult;
|
use rustpython_parser::lexer::LexResult;
|
||||||
use rustpython_parser::Tok;
|
use rustpython_parser::Tok;
|
||||||
|
|
||||||
|
@ -11,15 +13,16 @@ use crate::lex::docstring_detection::StateMachine;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
use crate::rules::ruff::rules::Context;
|
use crate::rules::ruff::rules::Context;
|
||||||
use crate::rules::{
|
use crate::rules::{
|
||||||
eradicate, flake8_commas, flake8_fixme, flake8_implicit_str_concat, flake8_pyi, flake8_quotes,
|
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
|
||||||
flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
|
flake8_pyi, flake8_quotes, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||||
};
|
};
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
|
|
||||||
pub(crate) fn check_tokens(
|
pub(crate) fn check_tokens(
|
||||||
|
tokens: &[LexResult],
|
||||||
|
path: &Path,
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
tokens: &[LexResult],
|
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
is_stub: bool,
|
is_stub: bool,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
|
@ -143,6 +146,16 @@ pub(crate) fn check_tokens(
|
||||||
flake8_pyi::rules::type_comment_in_stub(&mut diagnostics, locator, indexer);
|
flake8_pyi::rules::type_comment_in_stub(&mut diagnostics, locator, indexer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.rules.any_enabled(&[
|
||||||
|
Rule::ShebangNotExecutable,
|
||||||
|
Rule::ShebangMissingExecutableFile,
|
||||||
|
Rule::ShebangLeadingWhitespace,
|
||||||
|
Rule::ShebangNotFirstLine,
|
||||||
|
Rule::ShebangMissingPython,
|
||||||
|
]) {
|
||||||
|
flake8_executable::rules::from_tokens(tokens, path, locator, settings, &mut diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
if settings.rules.any_enabled(&[
|
if settings.rules.any_enabled(&[
|
||||||
Rule::InvalidTodoTag,
|
Rule::InvalidTodoTag,
|
||||||
Rule::MissingTodoAuthor,
|
Rule::MissingTodoAuthor,
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
use ruff_python_trivia::{is_python_whitespace, Cursor};
|
use std::ops::Deref;
|
||||||
use ruff_text_size::{TextLen, TextSize};
|
|
||||||
|
use ruff_python_trivia::Cursor;
|
||||||
|
|
||||||
/// A shebang directive (e.g., `#!/usr/bin/env python3`).
|
/// A shebang directive (e.g., `#!/usr/bin/env python3`).
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub(crate) struct ShebangDirective<'a> {
|
pub(crate) struct ShebangDirective<'a>(&'a str);
|
||||||
/// The offset of the directive contents (e.g., `/usr/bin/env python3`) from the start of the
|
|
||||||
/// line.
|
|
||||||
pub(crate) offset: TextSize,
|
|
||||||
/// The contents of the directive (e.g., `"/usr/bin/env python3"`).
|
|
||||||
pub(crate) contents: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ShebangDirective<'a> {
|
impl<'a> ShebangDirective<'a> {
|
||||||
/// Parse a shebang directive from a line, or return `None` if the line does not contain a
|
/// Parse a shebang directive from a line, or return `None` if the line does not contain a
|
||||||
|
@ -17,9 +12,6 @@ impl<'a> ShebangDirective<'a> {
|
||||||
pub(crate) fn try_extract(line: &'a str) -> Option<Self> {
|
pub(crate) fn try_extract(line: &'a str) -> Option<Self> {
|
||||||
let mut cursor = Cursor::new(line);
|
let mut cursor = Cursor::new(line);
|
||||||
|
|
||||||
// Trim whitespace.
|
|
||||||
cursor.eat_while(is_python_whitespace);
|
|
||||||
|
|
||||||
// Trim the `#!` prefix.
|
// Trim the `#!` prefix.
|
||||||
if !cursor.eat_char('#') {
|
if !cursor.eat_char('#') {
|
||||||
return None;
|
return None;
|
||||||
|
@ -28,10 +20,15 @@ impl<'a> ShebangDirective<'a> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Self {
|
Some(Self(cursor.chars().as_str()))
|
||||||
offset: line.text_len() - cursor.text_len(),
|
}
|
||||||
contents: cursor.chars().as_str(),
|
}
|
||||||
})
|
|
||||||
|
impl Deref for ShebangDirective<'_> {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +56,12 @@ mod tests {
|
||||||
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shebang_match_trailing_comment() {
|
||||||
|
let source = "#!/usr/bin/env python # trailing comment";
|
||||||
|
assert_debug_snapshot!(ShebangDirective::try_extract(source));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shebang_leading_space() {
|
fn shebang_leading_space() {
|
||||||
let source = " #!/usr/bin/env python";
|
let source = " #!/usr/bin/env python";
|
||||||
|
|
|
@ -2,9 +2,4 @@
|
||||||
source: crates/ruff/src/comments/shebang.rs
|
source: crates/ruff/src/comments/shebang.rs
|
||||||
expression: "ShebangDirective::try_extract(source)"
|
expression: "ShebangDirective::try_extract(source)"
|
||||||
---
|
---
|
||||||
Some(
|
None
|
||||||
ShebangDirective {
|
|
||||||
offset: 4,
|
|
||||||
contents: "/usr/bin/env python",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
|
@ -3,8 +3,7 @@ source: crates/ruff/src/comments/shebang.rs
|
||||||
expression: "ShebangDirective::try_extract(source)"
|
expression: "ShebangDirective::try_extract(source)"
|
||||||
---
|
---
|
||||||
Some(
|
Some(
|
||||||
ShebangDirective {
|
ShebangDirective(
|
||||||
offset: 2,
|
"/usr/bin/env python",
|
||||||
contents: "/usr/bin/env python",
|
),
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/comments/shebang.rs
|
||||||
|
expression: "ShebangDirective::try_extract(source)"
|
||||||
|
---
|
||||||
|
Some(
|
||||||
|
ShebangDirective(
|
||||||
|
"/usr/bin/env python # trailing comment",
|
||||||
|
),
|
||||||
|
)
|
|
@ -100,7 +100,9 @@ pub fn check_path(
|
||||||
.any(|rule_code| rule_code.lint_source().is_tokens())
|
.any(|rule_code| rule_code.lint_source().is_tokens())
|
||||||
{
|
{
|
||||||
let is_stub = is_python_stub_file(path);
|
let is_stub = is_python_stub_file(path);
|
||||||
diagnostics.extend(check_tokens(locator, indexer, &tokens, settings, is_stub));
|
diagnostics.extend(check_tokens(
|
||||||
|
&tokens, path, locator, indexer, settings, is_stub,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the filesystem-based rules.
|
// Run the filesystem-based rules.
|
||||||
|
@ -193,7 +195,7 @@ pub fn check_path(
|
||||||
.any(|rule_code| rule_code.lint_source().is_physical_lines())
|
.any(|rule_code| rule_code.lint_source().is_physical_lines())
|
||||||
{
|
{
|
||||||
diagnostics.extend(check_physical_lines(
|
diagnostics.extend(check_physical_lines(
|
||||||
path, locator, stylist, indexer, &doc_lines, settings,
|
locator, stylist, indexer, &doc_lines, settings,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -238,22 +238,16 @@ impl Rule {
|
||||||
match self {
|
match self {
|
||||||
Rule::InvalidPyprojectToml => LintSource::PyprojectToml,
|
Rule::InvalidPyprojectToml => LintSource::PyprojectToml,
|
||||||
Rule::UnusedNOQA => LintSource::Noqa,
|
Rule::UnusedNOQA => LintSource::Noqa,
|
||||||
|
Rule::BidirectionalUnicode
|
||||||
Rule::DocLineTooLong
|
| Rule::BlankLineWithWhitespace
|
||||||
|
| Rule::DocLineTooLong
|
||||||
| Rule::LineTooLong
|
| Rule::LineTooLong
|
||||||
| Rule::MixedSpacesAndTabs
|
|
||||||
| Rule::MissingNewlineAtEndOfFile
|
|
||||||
| Rule::UTF8EncodingDeclaration
|
|
||||||
| Rule::ShebangMissingExecutableFile
|
|
||||||
| Rule::ShebangNotExecutable
|
|
||||||
| Rule::ShebangNotFirstLine
|
|
||||||
| Rule::BidirectionalUnicode
|
|
||||||
| Rule::ShebangMissingPython
|
|
||||||
| Rule::ShebangLeadingWhitespace
|
|
||||||
| Rule::TrailingWhitespace
|
|
||||||
| Rule::TabIndentation
|
|
||||||
| Rule::MissingCopyrightNotice
|
| Rule::MissingCopyrightNotice
|
||||||
| Rule::BlankLineWithWhitespace => LintSource::PhysicalLines,
|
| Rule::MissingNewlineAtEndOfFile
|
||||||
|
| Rule::MixedSpacesAndTabs
|
||||||
|
| Rule::TabIndentation
|
||||||
|
| Rule::TrailingWhitespace
|
||||||
|
| Rule::UTF8EncodingDeclaration => LintSource::PhysicalLines,
|
||||||
Rule::AmbiguousUnicodeCharacterComment
|
Rule::AmbiguousUnicodeCharacterComment
|
||||||
| Rule::AmbiguousUnicodeCharacterDocstring
|
| Rule::AmbiguousUnicodeCharacterDocstring
|
||||||
| Rule::AmbiguousUnicodeCharacterString
|
| Rule::AmbiguousUnicodeCharacterString
|
||||||
|
@ -264,33 +258,38 @@ impl Rule {
|
||||||
| Rule::BlanketNOQA
|
| Rule::BlanketNOQA
|
||||||
| Rule::BlanketTypeIgnore
|
| Rule::BlanketTypeIgnore
|
||||||
| Rule::CommentedOutCode
|
| Rule::CommentedOutCode
|
||||||
| Rule::MultiLineImplicitStringConcatenation
|
| Rule::ExtraneousParentheses
|
||||||
| Rule::InvalidCharacterBackspace
|
| Rule::InvalidCharacterBackspace
|
||||||
| Rule::InvalidCharacterSub
|
|
||||||
| Rule::InvalidCharacterEsc
|
| Rule::InvalidCharacterEsc
|
||||||
| Rule::InvalidCharacterNul
|
| Rule::InvalidCharacterNul
|
||||||
|
| Rule::InvalidCharacterSub
|
||||||
| Rule::InvalidCharacterZeroWidthSpace
|
| Rule::InvalidCharacterZeroWidthSpace
|
||||||
| Rule::ExtraneousParentheses
|
|
||||||
| Rule::InvalidEscapeSequence
|
| Rule::InvalidEscapeSequence
|
||||||
| Rule::SingleLineImplicitStringConcatenation
|
|
||||||
| Rule::MissingTrailingComma
|
|
||||||
| Rule::TrailingCommaOnBareTuple
|
|
||||||
| Rule::MultipleStatementsOnOneLineColon
|
|
||||||
| Rule::UselessSemicolon
|
|
||||||
| Rule::MultipleStatementsOnOneLineSemicolon
|
|
||||||
| Rule::ProhibitedTrailingComma
|
|
||||||
| Rule::TypeCommentInStub
|
|
||||||
| Rule::InvalidTodoTag
|
|
||||||
| Rule::MissingTodoAuthor
|
|
||||||
| Rule::MissingTodoLink
|
|
||||||
| Rule::MissingTodoColon
|
|
||||||
| Rule::MissingTodoDescription
|
|
||||||
| Rule::InvalidTodoCapitalization
|
| Rule::InvalidTodoCapitalization
|
||||||
| Rule::MissingSpaceAfterTodoColon
|
| Rule::InvalidTodoTag
|
||||||
| Rule::LineContainsFixme
|
| Rule::LineContainsFixme
|
||||||
| Rule::LineContainsHack
|
| Rule::LineContainsHack
|
||||||
| Rule::LineContainsTodo
|
| Rule::LineContainsTodo
|
||||||
| Rule::LineContainsXxx => LintSource::Tokens,
|
| Rule::LineContainsXxx
|
||||||
|
| Rule::MissingSpaceAfterTodoColon
|
||||||
|
| Rule::MissingTodoAuthor
|
||||||
|
| Rule::MissingTodoColon
|
||||||
|
| Rule::MissingTodoDescription
|
||||||
|
| Rule::MissingTodoLink
|
||||||
|
| Rule::MissingTrailingComma
|
||||||
|
| Rule::MultiLineImplicitStringConcatenation
|
||||||
|
| Rule::MultipleStatementsOnOneLineColon
|
||||||
|
| Rule::MultipleStatementsOnOneLineSemicolon
|
||||||
|
| Rule::ProhibitedTrailingComma
|
||||||
|
| Rule::ShebangLeadingWhitespace
|
||||||
|
| Rule::ShebangMissingExecutableFile
|
||||||
|
| Rule::ShebangMissingPython
|
||||||
|
| Rule::ShebangNotExecutable
|
||||||
|
| Rule::ShebangNotFirstLine
|
||||||
|
| Rule::SingleLineImplicitStringConcatenation
|
||||||
|
| Rule::TrailingCommaOnBareTuple
|
||||||
|
| Rule::TypeCommentInStub
|
||||||
|
| Rule::UselessSemicolon => LintSource::Tokens,
|
||||||
Rule::IOError => LintSource::Io,
|
Rule::IOError => LintSource::Io,
|
||||||
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
|
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
|
||||||
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
|
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
|
||||||
|
|
|
@ -24,6 +24,7 @@ mod tests {
|
||||||
#[test_case(Path::new("EXE004_1.py"))]
|
#[test_case(Path::new("EXE004_1.py"))]
|
||||||
#[test_case(Path::new("EXE004_2.py"))]
|
#[test_case(Path::new("EXE004_2.py"))]
|
||||||
#[test_case(Path::new("EXE004_3.py"))]
|
#[test_case(Path::new("EXE004_3.py"))]
|
||||||
|
#[test_case(Path::new("EXE004_4.py"))]
|
||||||
#[test_case(Path::new("EXE005_1.py"))]
|
#[test_case(Path::new("EXE005_1.py"))]
|
||||||
#[test_case(Path::new("EXE005_2.py"))]
|
#[test_case(Path::new("EXE005_2.py"))]
|
||||||
#[test_case(Path::new("EXE005_3.py"))]
|
#[test_case(Path::new("EXE005_3.py"))]
|
||||||
|
|
|
@ -1,11 +1,60 @@
|
||||||
pub(crate) use shebang_missing::*;
|
use std::path::Path;
|
||||||
pub(crate) use shebang_newline::*;
|
|
||||||
pub(crate) use shebang_not_executable::*;
|
|
||||||
pub(crate) use shebang_python::*;
|
|
||||||
pub(crate) use shebang_whitespace::*;
|
|
||||||
|
|
||||||
mod shebang_missing;
|
use rustpython_parser::lexer::LexResult;
|
||||||
mod shebang_newline;
|
use rustpython_parser::Tok;
|
||||||
|
|
||||||
|
use ruff_diagnostics::Diagnostic;
|
||||||
|
use ruff_python_ast::source_code::Locator;
|
||||||
|
pub(crate) use shebang_leading_whitespace::*;
|
||||||
|
pub(crate) use shebang_missing_executable_file::*;
|
||||||
|
pub(crate) use shebang_missing_python::*;
|
||||||
|
pub(crate) use shebang_not_executable::*;
|
||||||
|
pub(crate) use shebang_not_first_line::*;
|
||||||
|
|
||||||
|
use crate::comments::shebang::ShebangDirective;
|
||||||
|
use crate::settings::Settings;
|
||||||
|
|
||||||
|
mod shebang_leading_whitespace;
|
||||||
|
mod shebang_missing_executable_file;
|
||||||
|
mod shebang_missing_python;
|
||||||
mod shebang_not_executable;
|
mod shebang_not_executable;
|
||||||
mod shebang_python;
|
mod shebang_not_first_line;
|
||||||
mod shebang_whitespace;
|
|
||||||
|
pub(crate) fn from_tokens(
|
||||||
|
tokens: &[LexResult],
|
||||||
|
path: &Path,
|
||||||
|
locator: &Locator,
|
||||||
|
settings: &Settings,
|
||||||
|
diagnostics: &mut Vec<Diagnostic>,
|
||||||
|
) {
|
||||||
|
let mut has_any_shebang = false;
|
||||||
|
for (tok, range) in tokens.iter().flatten() {
|
||||||
|
if let Tok::Comment(comment) = tok {
|
||||||
|
if let Some(shebang) = ShebangDirective::try_extract(comment) {
|
||||||
|
has_any_shebang = true;
|
||||||
|
|
||||||
|
if let Some(diagnostic) = shebang_missing_python(*range, &shebang) {
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(diagnostic) = shebang_not_executable(path, *range) {
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(diagnostic) = shebang_leading_whitespace(*range, locator, settings) {
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(diagnostic) = shebang_not_first_line(*range, locator) {
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_any_shebang {
|
||||||
|
if let Some(diagnostic) = shebang_missing_executable_file(path) {
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use std::ops::Sub;
|
|
||||||
|
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::source_code::Locator;
|
||||||
|
use ruff_python_trivia::is_python_whitespace;
|
||||||
|
|
||||||
use crate::comments::shebang::ShebangDirective;
|
use crate::registry::AsRule;
|
||||||
|
use crate::settings::Settings;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for whitespace before a shebang directive.
|
/// Checks for whitespace before a shebang directive.
|
||||||
|
@ -46,31 +47,29 @@ impl AlwaysAutofixableViolation for ShebangLeadingWhitespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// EXE004
|
/// EXE004
|
||||||
pub(crate) fn shebang_whitespace(
|
pub(crate) fn shebang_leading_whitespace(
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
shebang: &ShebangDirective,
|
locator: &Locator,
|
||||||
autofix: bool,
|
settings: &Settings,
|
||||||
) -> Option<Diagnostic> {
|
) -> Option<Diagnostic> {
|
||||||
let ShebangDirective {
|
// If the shebang is at the beginning of the file, abort.
|
||||||
offset,
|
if range.start() == TextSize::from(0) {
|
||||||
contents: _,
|
return None;
|
||||||
} = shebang;
|
}
|
||||||
|
|
||||||
if *offset > TextSize::from(2) {
|
// If the entire prefix _isn't_ whitespace, abort (this is handled by EXE005).
|
||||||
let leading_space_start = range.start();
|
if !locator
|
||||||
let leading_space_len = offset.sub(TextSize::new(2));
|
.up_to(range.start())
|
||||||
let mut diagnostic = Diagnostic::new(
|
.chars()
|
||||||
ShebangLeadingWhitespace,
|
.all(|c| is_python_whitespace(c) || matches!(c, '\r' | '\n'))
|
||||||
TextRange::at(leading_space_start, leading_space_len),
|
{
|
||||||
);
|
return None;
|
||||||
if autofix {
|
}
|
||||||
diagnostic.set_fix(Fix::automatic(Edit::range_deletion(TextRange::at(
|
|
||||||
leading_space_start,
|
let prefix = TextRange::up_to(range.start());
|
||||||
leading_space_len,
|
let mut diagnostic = Diagnostic::new(ShebangLeadingWhitespace, prefix);
|
||||||
))));
|
if settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||||
|
diagnostic.set_fix(Fix::automatic(Edit::range_deletion(prefix)));
|
||||||
}
|
}
|
||||||
Some(diagnostic)
|
Some(diagnostic)
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use wsl;
|
|
||||||
|
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
|
use wsl;
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
@ -43,20 +42,22 @@ impl Violation for ShebangMissingExecutableFile {
|
||||||
|
|
||||||
/// EXE002
|
/// EXE002
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub(crate) fn shebang_missing(filepath: &Path) -> Option<Diagnostic> {
|
pub(crate) fn shebang_missing_executable_file(filepath: &Path) -> Option<Diagnostic> {
|
||||||
// WSL supports Windows file systems, which do not have executable bits.
|
// WSL supports Windows file systems, which do not have executable bits.
|
||||||
// Instead, everything is executable. Therefore, we skip this rule on WSL.
|
// Instead, everything is executable. Therefore, we skip this rule on WSL.
|
||||||
if wsl::is_wsl() {
|
if wsl::is_wsl() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if let Ok(true) = is_executable(filepath) {
|
if let Ok(true) = is_executable(filepath) {
|
||||||
let diagnostic = Diagnostic::new(ShebangMissingExecutableFile, TextRange::default());
|
return Some(Diagnostic::new(
|
||||||
return Some(diagnostic);
|
ShebangMissingExecutableFile,
|
||||||
|
TextRange::default(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
pub(crate) fn shebang_missing(_filepath: &Path) -> Option<Diagnostic> {
|
pub(crate) fn shebang_missing_executable_file(_filepath: &Path) -> Option<Diagnostic> {
|
||||||
None
|
None
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
@ -41,15 +41,13 @@ impl Violation for ShebangMissingPython {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// EXE003
|
/// EXE003
|
||||||
pub(crate) fn shebang_python(range: TextRange, shebang: &ShebangDirective) -> Option<Diagnostic> {
|
pub(crate) fn shebang_missing_python(
|
||||||
let ShebangDirective { offset, contents } = shebang;
|
range: TextRange,
|
||||||
|
shebang: &ShebangDirective,
|
||||||
|
) -> Option<Diagnostic> {
|
||||||
|
if shebang.contains("python") || shebang.contains("pytest") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if contents.contains("python") || contents.contains("pytest") {
|
Some(Diagnostic::new(ShebangMissingPython, range))
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Diagnostic::new(
|
|
||||||
ShebangMissingPython,
|
|
||||||
TextRange::at(range.start() + offset, contents.text_len()).sub_start(TextSize::from(2)),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,15 +2,12 @@
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
use wsl;
|
use wsl;
|
||||||
|
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::comments::shebang::ShebangDirective;
|
|
||||||
use crate::registry::AsRule;
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
use crate::rules::flake8_executable::helpers::is_executable;
|
use crate::rules::flake8_executable::helpers::is_executable;
|
||||||
|
|
||||||
|
@ -45,34 +42,21 @@ impl Violation for ShebangNotExecutable {
|
||||||
|
|
||||||
/// EXE001
|
/// EXE001
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub(crate) fn shebang_not_executable(
|
pub(crate) fn shebang_not_executable(filepath: &Path, range: TextRange) -> Option<Diagnostic> {
|
||||||
filepath: &Path,
|
|
||||||
range: TextRange,
|
|
||||||
shebang: &ShebangDirective,
|
|
||||||
) -> Option<Diagnostic> {
|
|
||||||
// WSL supports Windows file systems, which do not have executable bits.
|
// WSL supports Windows file systems, which do not have executable bits.
|
||||||
// Instead, everything is executable. Therefore, we skip this rule on WSL.
|
// Instead, everything is executable. Therefore, we skip this rule on WSL.
|
||||||
if wsl::is_wsl() {
|
if wsl::is_wsl() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let ShebangDirective { offset, contents } = shebang;
|
|
||||||
|
|
||||||
if let Ok(false) = is_executable(filepath) {
|
if let Ok(false) = is_executable(filepath) {
|
||||||
let diagnostic = Diagnostic::new(
|
return Some(Diagnostic::new(ShebangNotExecutable, range));
|
||||||
ShebangNotExecutable,
|
|
||||||
TextRange::at(range.start() + offset, contents.text_len()),
|
|
||||||
);
|
|
||||||
return Some(diagnostic);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
pub(crate) fn shebang_not_executable(
|
pub(crate) fn shebang_not_executable(_filepath: &Path, _range: TextRange) -> Option<Diagnostic> {
|
||||||
_filepath: &Path,
|
|
||||||
_range: TextRange,
|
|
||||||
_shebang: &ShebangDirective,
|
|
||||||
) -> Option<Diagnostic> {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::source_code::Locator;
|
||||||
use crate::comments::shebang::ShebangDirective;
|
use ruff_python_trivia::is_python_whitespace;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for a shebang directive that is not at the beginning of the file.
|
/// Checks for a shebang directive that is not at the beginning of the file.
|
||||||
|
@ -42,19 +42,20 @@ impl Violation for ShebangNotFirstLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// EXE005
|
/// EXE005
|
||||||
pub(crate) fn shebang_newline(
|
pub(crate) fn shebang_not_first_line(range: TextRange, locator: &Locator) -> Option<Diagnostic> {
|
||||||
range: TextRange,
|
// If the shebang is at the beginning of the file, abort.
|
||||||
shebang: &ShebangDirective,
|
if range.start() == TextSize::from(0) {
|
||||||
first_line: bool,
|
return None;
|
||||||
) -> Option<Diagnostic> {
|
}
|
||||||
let ShebangDirective { offset, contents } = shebang;
|
|
||||||
|
|
||||||
if first_line {
|
// If the entire prefix is whitespace, abort (this is handled by EXE004).
|
||||||
None
|
if locator
|
||||||
} else {
|
.up_to(range.start())
|
||||||
Some(Diagnostic::new(
|
.chars()
|
||||||
ShebangNotFirstLine,
|
.all(|c| is_python_whitespace(c) || matches!(c, '\r' | '\n'))
|
||||||
TextRange::at(range.start() + offset, contents.text_len()),
|
{
|
||||||
))
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(Diagnostic::new(ShebangNotFirstLine, range))
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
||||||
---
|
---
|
||||||
EXE001_1.py:1:3: EXE001 Shebang is present but file is not executable
|
EXE001_1.py:1:1: EXE001 Shebang is present but file is not executable
|
||||||
|
|
|
|
||||||
1 | #!/usr/bin/python
|
1 | #!/usr/bin/python
|
||||||
| ^^^^^^^^^^^^^^^ EXE001
|
| ^^^^^^^^^^^^^^^^^ EXE001
|
||||||
2 |
|
2 |
|
||||||
3 | if __name__ == '__main__':
|
3 | if __name__ == '__main__':
|
||||||
|
|
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
||||||
---
|
---
|
||||||
EXE004_3.py:1:1: EXE002 The file is executable but no shebang is present
|
EXE004_3.py:2:7: EXE005 Shebang should be at the beginning of the file
|
||||||
|
|
|
|
||||||
1 |
|
|
||||||
| EXE002
|
|
||||||
2 | pass #!/usr/bin/env python
|
2 | pass #!/usr/bin/env python
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ EXE005
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
||||||
|
---
|
||||||
|
EXE004_4.py:1:1: EXE004 [*] Avoid whitespace before shebang
|
||||||
|
|
|
||||||
|
1 | /
|
||||||
|
2 | | #!/usr/bin/env python
|
||||||
|
| |____^ EXE004
|
||||||
|
|
|
||||||
|
= help: Remove whitespace before shebang
|
||||||
|
|
||||||
|
ℹ Fix
|
||||||
|
1 |-
|
||||||
|
2 |- #!/usr/bin/env python
|
||||||
|
1 |+#!/usr/bin/env python
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
||||||
---
|
---
|
||||||
EXE005_1.py:3:3: EXE005 Shebang should be at the beginning of the file
|
EXE005_1.py:3:1: EXE005 Shebang should be at the beginning of the file
|
||||||
|
|
|
|
||||||
2 | # A python comment
|
2 | # A python comment
|
||||||
3 | #!/usr/bin/python
|
3 | #!/usr/bin/python
|
||||||
| ^^^^^^^^^^^^^^^ EXE005
|
| ^^^^^^^^^^^^^^^^^ EXE005
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
||||||
---
|
---
|
||||||
EXE005_2.py:4:3: EXE005 Shebang should be at the beginning of the file
|
EXE005_2.py:4:1: EXE005 Shebang should be at the beginning of the file
|
||||||
|
|
|
|
||||||
3 | # A python comment
|
3 | # A python comment
|
||||||
4 | #!/usr/bin/python
|
4 | #!/usr/bin/python
|
||||||
| ^^^^^^^^^^^^^^^ EXE005
|
| ^^^^^^^^^^^^^^^^^ EXE005
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
source: crates/ruff/src/rules/flake8_executable/mod.rs
|
||||||
---
|
---
|
||||||
EXE005_3.py:6:3: EXE005 Shebang should be at the beginning of the file
|
EXE005_3.py:6:1: EXE005 Shebang should be at the beginning of the file
|
||||||
|
|
|
|
||||||
4 | """
|
4 | """
|
||||||
5 | # A python comment
|
5 | # A python comment
|
||||||
6 | #!/usr/bin/python
|
6 | #!/usr/bin/python
|
||||||
| ^^^^^^^^^^^^^^^ EXE005
|
| ^^^^^^^^^^^^^^^^^ EXE005
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue