mirror of https://github.com/astral-sh/ruff
213 lines
7.3 KiB
Rust
213 lines
7.3 KiB
Rust
use crate::line_width::IndentWidth;
|
|
use ruff_diagnostics::Diagnostic;
|
|
use ruff_python_codegen::Stylist;
|
|
use ruff_python_index::Indexer;
|
|
use ruff_python_parser::{TokenKind, Tokens};
|
|
use ruff_source_file::Locator;
|
|
use ruff_text_size::{Ranged, TextRange};
|
|
|
|
use crate::registry::{AsRule, Rule};
|
|
use crate::rules::pycodestyle::rules::logical_lines::{
|
|
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
|
|
missing_whitespace_around_operator, redundant_backslash, space_after_comma,
|
|
space_around_operator, whitespace_around_keywords, whitespace_around_named_parameter_equals,
|
|
whitespace_before_comment, whitespace_before_parameters, LogicalLines, TokenFlags,
|
|
};
|
|
use crate::settings::LinterSettings;
|
|
|
|
/// Return the amount of indentation, expanding tabs to the next multiple of the settings' tab size.
|
|
pub(crate) fn expand_indent(line: &str, indent_width: IndentWidth) -> usize {
|
|
let line = line.trim_end_matches(['\n', '\r']);
|
|
|
|
let mut indent = 0;
|
|
let tab_size = indent_width.as_usize();
|
|
for c in line.bytes() {
|
|
match c {
|
|
b'\t' => indent = (indent / tab_size) * tab_size + tab_size,
|
|
b' ' => indent += 1,
|
|
_ => break,
|
|
}
|
|
}
|
|
|
|
indent
|
|
}
|
|
|
|
pub(crate) fn check_logical_lines(
|
|
tokens: &Tokens,
|
|
locator: &Locator,
|
|
indexer: &Indexer,
|
|
stylist: &Stylist,
|
|
settings: &LinterSettings,
|
|
) -> Vec<Diagnostic> {
|
|
let mut context = LogicalLinesContext::new(settings);
|
|
|
|
let mut prev_line = None;
|
|
let mut prev_indent_level = None;
|
|
let indent_char = stylist.indentation().as_char();
|
|
|
|
let enforce_space_around_operator = settings.rules.any_enabled(&[
|
|
Rule::MultipleSpacesBeforeOperator,
|
|
Rule::MultipleSpacesAfterOperator,
|
|
Rule::TabBeforeOperator,
|
|
Rule::TabAfterOperator,
|
|
]);
|
|
let enforce_whitespace_around_named_parameter_equals = settings.rules.any_enabled(&[
|
|
Rule::UnexpectedSpacesAroundKeywordParameterEquals,
|
|
Rule::MissingWhitespaceAroundParameterEquals,
|
|
]);
|
|
let enforce_missing_whitespace_around_operator = settings.rules.any_enabled(&[
|
|
Rule::MissingWhitespaceAroundOperator,
|
|
Rule::MissingWhitespaceAroundArithmeticOperator,
|
|
Rule::MissingWhitespaceAroundBitwiseOrShiftOperator,
|
|
Rule::MissingWhitespaceAroundModuloOperator,
|
|
]);
|
|
let enforce_missing_whitespace = settings.rules.enabled(Rule::MissingWhitespace);
|
|
let enforce_space_after_comma = settings
|
|
.rules
|
|
.any_enabled(&[Rule::MultipleSpacesAfterComma, Rule::TabAfterComma]);
|
|
let enforce_extraneous_whitespace = settings.rules.any_enabled(&[
|
|
Rule::WhitespaceAfterOpenBracket,
|
|
Rule::WhitespaceBeforeCloseBracket,
|
|
Rule::WhitespaceBeforePunctuation,
|
|
]);
|
|
let enforce_whitespace_around_keywords = settings.rules.any_enabled(&[
|
|
Rule::MultipleSpacesAfterKeyword,
|
|
Rule::MultipleSpacesBeforeKeyword,
|
|
Rule::TabAfterKeyword,
|
|
Rule::TabBeforeKeyword,
|
|
]);
|
|
let enforce_missing_whitespace_after_keyword =
|
|
settings.rules.enabled(Rule::MissingWhitespaceAfterKeyword);
|
|
let enforce_whitespace_before_comment = settings.rules.any_enabled(&[
|
|
Rule::TooFewSpacesBeforeInlineComment,
|
|
Rule::NoSpaceAfterInlineComment,
|
|
Rule::NoSpaceAfterBlockComment,
|
|
Rule::MultipleLeadingHashesForBlockComment,
|
|
]);
|
|
let enforce_whitespace_before_parameters =
|
|
settings.rules.enabled(Rule::WhitespaceBeforeParameters);
|
|
let enforce_redundant_backslash = settings.rules.enabled(Rule::RedundantBackslash);
|
|
let enforce_indentation = settings.rules.any_enabled(&[
|
|
Rule::IndentationWithInvalidMultiple,
|
|
Rule::NoIndentedBlock,
|
|
Rule::UnexpectedIndentation,
|
|
Rule::IndentationWithInvalidMultipleComment,
|
|
Rule::NoIndentedBlockComment,
|
|
Rule::UnexpectedIndentationComment,
|
|
Rule::OverIndented,
|
|
]);
|
|
|
|
for line in &LogicalLines::from_tokens(tokens, locator) {
|
|
if line.flags().contains(TokenFlags::OPERATOR) {
|
|
if enforce_space_around_operator {
|
|
space_around_operator(&line, &mut context);
|
|
}
|
|
|
|
if enforce_whitespace_around_named_parameter_equals {
|
|
whitespace_around_named_parameter_equals(&line, &mut context);
|
|
}
|
|
|
|
if enforce_missing_whitespace_around_operator {
|
|
missing_whitespace_around_operator(&line, &mut context);
|
|
}
|
|
|
|
if enforce_missing_whitespace {
|
|
missing_whitespace(&line, &mut context);
|
|
}
|
|
}
|
|
|
|
if line.flags().contains(TokenFlags::PUNCTUATION) && enforce_space_after_comma {
|
|
space_after_comma(&line, &mut context);
|
|
}
|
|
|
|
if line
|
|
.flags()
|
|
.intersects(TokenFlags::OPERATOR | TokenFlags::BRACKET | TokenFlags::PUNCTUATION)
|
|
&& enforce_extraneous_whitespace
|
|
{
|
|
extraneous_whitespace(&line, &mut context);
|
|
}
|
|
|
|
if line.flags().contains(TokenFlags::KEYWORD) {
|
|
if enforce_whitespace_around_keywords {
|
|
whitespace_around_keywords(&line, &mut context);
|
|
}
|
|
|
|
if enforce_missing_whitespace_after_keyword {
|
|
missing_whitespace_after_keyword(&line, &mut context);
|
|
}
|
|
}
|
|
|
|
if line.flags().contains(TokenFlags::COMMENT) && enforce_whitespace_before_comment {
|
|
whitespace_before_comment(&line, locator, &mut context);
|
|
}
|
|
|
|
if line.flags().contains(TokenFlags::BRACKET) {
|
|
if enforce_whitespace_before_parameters {
|
|
whitespace_before_parameters(&line, &mut context);
|
|
}
|
|
|
|
if enforce_redundant_backslash {
|
|
redundant_backslash(&line, locator, indexer, &mut context);
|
|
}
|
|
}
|
|
|
|
// Extract the indentation level.
|
|
let Some(first_token) = line.first_token() else {
|
|
continue;
|
|
};
|
|
|
|
let range = if first_token.kind() == TokenKind::Indent {
|
|
first_token.range()
|
|
} else {
|
|
TextRange::new(locator.line_start(first_token.start()), first_token.start())
|
|
};
|
|
|
|
let indent_level = expand_indent(locator.slice(range), settings.tab_size);
|
|
|
|
let indent_size = 4;
|
|
|
|
if enforce_indentation {
|
|
for kind in indentation(
|
|
&line,
|
|
prev_line.as_ref(),
|
|
indent_char,
|
|
indent_level,
|
|
prev_indent_level,
|
|
indent_size,
|
|
) {
|
|
if settings.rules.enabled(kind.rule()) {
|
|
context.push_diagnostic(Diagnostic::new(kind, range));
|
|
}
|
|
}
|
|
}
|
|
|
|
if !line.is_comment_only() {
|
|
prev_line = Some(line);
|
|
prev_indent_level = Some(indent_level);
|
|
}
|
|
}
|
|
context.diagnostics
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct LogicalLinesContext<'a> {
|
|
settings: &'a LinterSettings,
|
|
diagnostics: Vec<Diagnostic>,
|
|
}
|
|
|
|
impl<'a> LogicalLinesContext<'a> {
|
|
fn new(settings: &'a LinterSettings) -> Self {
|
|
Self {
|
|
settings,
|
|
diagnostics: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
|
|
if self.settings.rules.enabled(diagnostic.kind.rule()) {
|
|
self.diagnostics.push(diagnostic);
|
|
}
|
|
}
|
|
}
|