Skip to content

Commit

Permalink
[ruff] Indented form feeds (RUF054) (#16049)
Browse files Browse the repository at this point in the history
## Summary

Resolves #12321.

The physical-line-based `RUF054` checks for form feed characters that
are preceded by only tabs and spaces, but not any other characters,
including form feeds.

## Test Plan

`cargo nextest run` and `cargo insta test`.
  • Loading branch information
InSyncWithFoo authored Feb 10, 2025
1 parent 9ae98d4 commit f367aa8
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 2 deletions.
32 changes: 32 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/ruff/RUF054.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
############# Warning ############
# This file contains form feeds. #
############# Warning ############


# Errors





def _():
pass

if False:
print('F')
print('T')


# No errors







def _():
pass

def f():
pass
7 changes: 7 additions & 0 deletions crates/ruff_linter/src/checkers/physical_lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::rules::pycodestyle::rules::{
trailing_whitespace,
};
use crate::rules::pylint;
use crate::rules::ruff::rules::indented_form_feed;
use crate::settings::LinterSettings;
use crate::Locator;

Expand Down Expand Up @@ -71,6 +72,12 @@ pub(crate) fn check_physical_lines(
diagnostics.push(diagnostic);
}
}

if settings.rules.enabled(Rule::IndentedFormFeed) {
if let Some(diagnostic) = indented_form_feed(&line) {
diagnostics.push(diagnostic);
}
}
}

if enforce_no_newline_at_end_of_file {
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "051") => (RuleGroup::Preview, rules::ruff::rules::IfKeyInDictDel),
(Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable),
(Ruff, "053") => (RuleGroup::Preview, rules::ruff::rules::ClassWithMixedTypeVars),
(Ruff, "054") => (RuleGroup::Preview, rules::ruff::rules::IndentedFormFeed),
(Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression),
(Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback),
(Ruff, "057") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRound),
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ impl Rule {
Rule::BidirectionalUnicode
| Rule::BlankLineWithWhitespace
| Rule::DocLineTooLong
| Rule::IndentedFormFeed
| Rule::LineTooLong
| Rule::MissingCopyrightNotice
| Rule::MissingNewlineAtEndOfFile
Expand Down
4 changes: 2 additions & 2 deletions crates/ruff_linter/src/rules/ruff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ mod tests {

use anyhow::Result;
use regex::Regex;
use ruff_source_file::SourceFileBuilder;
use rustc_hash::FxHashSet;
use test_case::test_case;

use ruff_source_file::SourceFileBuilder;

use crate::pyproject_toml::lint_pyproject_toml;
use crate::registry::Rule;
use crate::settings::types::{
Expand Down Expand Up @@ -436,6 +435,7 @@ mod tests {
#[test_case(Rule::StarmapZip, Path::new("RUF058_0.py"))]
#[test_case(Rule::StarmapZip, Path::new("RUF058_1.py"))]
#[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))]
#[test_case(Rule::IndentedFormFeed, Path::new("RUF054.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
Expand Down
71 changes: 71 additions & 0 deletions crates/ruff_linter/src/rules/ruff/rules/indented_form_feed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use memchr::memchr;

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_source_file::Line;
use ruff_text_size::{TextRange, TextSize};

/// ## What it does
/// Checks for form feed characters preceded by either a space or a tab.
///
/// ## Why is this bad?
/// [The language reference][lexical-analysis-indentation] states:
///
/// > A formfeed character may be present at the start of the line;
/// > it will be ignored for the indentation calculations above.
/// > Formfeed characters occurring elsewhere in the leading whitespace
/// > have an undefined effect (for instance, they may reset the space count to zero).
///
/// ## Example
///
/// ```python
/// if foo():\n \fbar()
/// ```
///
/// Use instead:
///
/// ```python
/// if foo():\n bar()
/// ```
///
/// [lexical-analysis-indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation
#[derive(ViolationMetadata)]
pub(crate) struct IndentedFormFeed;

impl Violation for IndentedFormFeed {
#[derive_message_formats]
fn message(&self) -> String {
"Indented form feed".to_string()
}

fn fix_title(&self) -> Option<String> {
Some("Remove form feed".to_string())
}
}

const FORM_FEED: u8 = b'\x0c';
const SPACE: u8 = b' ';
const TAB: u8 = b'\t';

/// RUF054
pub(crate) fn indented_form_feed(line: &Line) -> Option<Diagnostic> {
let index_relative_to_line = memchr(FORM_FEED, line.as_bytes())?;

if index_relative_to_line == 0 {
return None;
}

if line[..index_relative_to_line]
.as_bytes()
.iter()
.any(|byte| *byte != SPACE && *byte != TAB)
{
return None;
}

let relative_index = u32::try_from(index_relative_to_line).ok()?;
let absolute_index = line.start() + TextSize::new(relative_index);
let range = TextRange::at(absolute_index, 1.into());

Some(Diagnostic::new(IndentedFormFeed, range))
}
2 changes: 2 additions & 0 deletions crates/ruff_linter/src/rules/ruff/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) use function_call_in_dataclass_default::*;
pub(crate) use if_key_in_dict_del::*;
pub(crate) use implicit_optional::*;
pub(crate) use incorrectly_parenthesized_tuple_in_subscript::*;
pub(crate) use indented_form_feed::*;
pub(crate) use invalid_assert_message_literal_argument::*;
pub(crate) use invalid_formatter_suppression_comment::*;
pub(crate) use invalid_index_type::*;
Expand Down Expand Up @@ -69,6 +70,7 @@ mod helpers;
mod if_key_in_dict_del;
mod implicit_optional;
mod incorrectly_parenthesized_tuple_in_subscript;
mod indented_form_feed;
mod invalid_assert_message_literal_argument;
mod invalid_formatter_suppression_comment;
mod invalid_index_type;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF054.py:8:2: RUF054 Indented form feed
|
6 | # Errors
7 |
8 |
| ^ RUF054
|
= help: Remove form feed

RUF054.py:10:3: RUF054 Indented form feed
|
10 |
| ^ RUF054
11 |
12 | def _():
|
= help: Remove form feed

RUF054.py:13:2: RUF054 Indented form feed
|
12 | def _():
13 | pass
| ^ RUF054
14 |
15 | if False:
|
= help: Remove form feed

RUF054.py:17:5: RUF054 Indented form feed
|
15 | if False:
16 | print('F')
17 | print('T')
| ^ RUF054
|
= help: Remove form feed
1 change: 1 addition & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scripts/check_docs_formatted.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
KNOWN_PARSE_ERRORS = [
"blank-line-with-whitespace",
"indentation-with-invalid-multiple-comment",
"indented-form-feed",
"missing-newline-at-end-of-file",
"mixed-spaces-and-tabs",
"no-indented-block",
Expand Down

0 comments on commit f367aa8

Please # to comment.