diff --git a/crates/ruff/src/ast/mod.rs b/crates/ruff/src/ast/mod.rs index 8d4df7974b..c8a44a8b5a 100644 --- a/crates/ruff/src/ast/mod.rs +++ b/crates/ruff/src/ast/mod.rs @@ -8,6 +8,7 @@ pub mod helpers; pub mod logging; pub mod operations; pub mod relocate; +pub mod strings; pub mod types; pub mod typing; pub mod visitor; diff --git a/crates/ruff/src/ast/strings.rs b/crates/ruff/src/ast/strings.rs new file mode 100644 index 0000000000..c20de07deb --- /dev/null +++ b/crates/ruff/src/ast/strings.rs @@ -0,0 +1,38 @@ +use ruff_python_stdlib::str::{ + SINGLE_QUOTE_PREFIXES, SINGLE_QUOTE_SUFFIXES, TRIPLE_QUOTE_PREFIXES, TRIPLE_QUOTE_SUFFIXES, +}; + +/// Strip the leading and trailing quotes from a docstring. +pub fn raw_contents(contents: &str) -> &str { + for pattern in TRIPLE_QUOTE_PREFIXES { + if contents.starts_with(pattern) { + return &contents[pattern.len()..contents.len() - 3]; + } + } + for pattern in SINGLE_QUOTE_PREFIXES { + if contents.starts_with(pattern) { + return &contents[pattern.len()..contents.len() - 1]; + } + } + unreachable!("Expected docstring to start with a valid triple- or single-quote prefix") +} + +/// Return the leading quote string for a docstring (e.g., `"""`). +pub fn leading_quote(content: &str) -> Option<&str> { + if let Some(first_line) = content.lines().next() { + for pattern in TRIPLE_QUOTE_PREFIXES.iter().chain(SINGLE_QUOTE_PREFIXES) { + if first_line.starts_with(pattern) { + return Some(pattern); + } + } + } + None +} + +/// Return the trailing quote string for a docstring (e.g., `"""`). +pub fn trailing_quote(content: &str) -> Option<&&str> { + TRIPLE_QUOTE_SUFFIXES + .iter() + .chain(SINGLE_QUOTE_SUFFIXES) + .find(|&pattern| content.ends_with(pattern)) +} diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index f6270dcad3..f9a939afd5 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -43,7 +43,7 @@ use crate::settings::types::PythonVersion; use crate::settings::{flags, Settings}; use crate::source_code::{Indexer, Locator, Stylist}; use crate::visibility::transition_scope; -use crate::{autofix, docstrings, noqa, visibility}; +use crate::{ast, autofix, docstrings, noqa, visibility}; mod deferred; @@ -5298,7 +5298,7 @@ impl<'a> Checker<'a> { Location::new(expr.location.row(), expr.location.column()), )); - let body = pydocstyle::helpers::raw_contents(contents); + let body = ast::strings::raw_contents(contents); let docstring = Docstring { kind: definition.kind, expr, diff --git a/crates/ruff/src/rules/pydocstyle/helpers.rs b/crates/ruff/src/rules/pydocstyle/helpers.rs index a95dbc0dfa..cb92a29db6 100644 --- a/crates/ruff/src/rules/pydocstyle/helpers.rs +++ b/crates/ruff/src/rules/pydocstyle/helpers.rs @@ -1,49 +1,10 @@ use std::collections::BTreeSet; -use ruff_python_stdlib::str::{ - SINGLE_QUOTE_PREFIXES, SINGLE_QUOTE_SUFFIXES, TRIPLE_QUOTE_PREFIXES, TRIPLE_QUOTE_SUFFIXES, -}; - use crate::ast::cast; use crate::ast::helpers::{map_callable, to_call_path}; use crate::checkers::ast::Checker; use crate::docstrings::definition::{Definition, DefinitionKind}; -/// Strip the leading and trailing quotes from a docstring. -pub fn raw_contents(contents: &str) -> &str { - for pattern in TRIPLE_QUOTE_PREFIXES { - if contents.starts_with(pattern) { - return &contents[pattern.len()..contents.len() - 3]; - } - } - for pattern in SINGLE_QUOTE_PREFIXES { - if contents.starts_with(pattern) { - return &contents[pattern.len()..contents.len() - 1]; - } - } - unreachable!("Expected docstring to start with a valid triple- or single-quote prefix") -} - -/// Return the leading quote string for a docstring (e.g., `"""`). -pub fn leading_quote(content: &str) -> Option<&str> { - if let Some(first_line) = content.lines().next() { - for pattern in TRIPLE_QUOTE_PREFIXES.iter().chain(SINGLE_QUOTE_PREFIXES) { - if first_line.starts_with(pattern) { - return Some(pattern); - } - } - } - None -} - -/// Return the trailing quote string for a docstring (e.g., `"""`). -pub fn trailing_quote(content: &str) -> Option<&&str> { - TRIPLE_QUOTE_SUFFIXES - .iter() - .chain(SINGLE_QUOTE_SUFFIXES) - .find(|&pattern| content.ends_with(pattern)) -} - /// Return the index of the first logical line in a string. pub fn logical_line(content: &str) -> Option { // Find the first logical line. diff --git a/crates/ruff/src/rules/pydocstyle/rules/ends_with_period.rs b/crates/ruff/src/rules/pydocstyle/rules/ends_with_period.rs index 4b89bc8d0b..82397122fd 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/ends_with_period.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/ends_with_period.rs @@ -2,6 +2,7 @@ use strum::IntoEnumIterator; use ruff_macros::{derive_message_formats, violation}; +use crate::ast::strings::leading_quote; use crate::ast::types::Range; use crate::checkers::ast::Checker; use crate::docstrings::definition::Docstring; @@ -9,7 +10,7 @@ use crate::docstrings::sections::SectionKind; use crate::fix::Fix; use crate::message::Location; use crate::registry::Diagnostic; -use crate::rules::pydocstyle::helpers::{leading_quote, logical_line}; +use crate::rules::pydocstyle::helpers::logical_line; use crate::violation::AlwaysAutofixableViolation; #[violation] diff --git a/crates/ruff/src/rules/pydocstyle/rules/ends_with_punctuation.rs b/crates/ruff/src/rules/pydocstyle/rules/ends_with_punctuation.rs index da3e47765a..0ba1f2a42a 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/ends_with_punctuation.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/ends_with_punctuation.rs @@ -2,6 +2,7 @@ use strum::IntoEnumIterator; use ruff_macros::{derive_message_formats, violation}; +use crate::ast::strings::leading_quote; use crate::ast::types::Range; use crate::checkers::ast::Checker; use crate::docstrings::definition::Docstring; @@ -9,7 +10,7 @@ use crate::docstrings::sections::SectionKind; use crate::fix::Fix; use crate::message::Location; use crate::registry::Diagnostic; -use crate::rules::pydocstyle::helpers::{leading_quote, logical_line}; +use crate::rules::pydocstyle::helpers::logical_line; use crate::violation::AlwaysAutofixableViolation; #[violation] diff --git a/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs b/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs index 6fdd6274a0..0080cedc06 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/multi_line_summary_start.rs @@ -1,6 +1,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_stdlib::str::TRIPLE_QUOTE_PREFIXES; +use crate::ast::strings::leading_quote; use crate::ast::types::Range; use crate::ast::whitespace::LinesWithTrailingNewline; use crate::checkers::ast::Checker; @@ -8,7 +9,6 @@ use crate::docstrings::definition::{DefinitionKind, Docstring}; use crate::fix::Fix; use crate::message::Location; use crate::registry::{Diagnostic, Rule}; -use crate::rules::pydocstyle::helpers::leading_quote; use crate::violation::AlwaysAutofixableViolation; #[violation] diff --git a/crates/ruff/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs b/crates/ruff/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs index a4f86b43b0..ecbde309ec 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/no_surrounding_whitespace.rs @@ -1,5 +1,6 @@ use ruff_macros::{derive_message_formats, violation}; +use crate::ast::strings::leading_quote; use crate::ast::types::Range; use crate::ast::whitespace::LinesWithTrailingNewline; use crate::checkers::ast::Checker; @@ -7,7 +8,6 @@ use crate::docstrings::definition::Docstring; use crate::fix::Fix; use crate::message::Location; use crate::registry::Diagnostic; -use crate::rules::pydocstyle::helpers::leading_quote; use crate::violation::AlwaysAutofixableViolation; #[violation] diff --git a/crates/ruff/src/rules/pydocstyle/rules/one_liner.rs b/crates/ruff/src/rules/pydocstyle/rules/one_liner.rs index 8e0f0dd459..6dd8d881b3 100644 --- a/crates/ruff/src/rules/pydocstyle/rules/one_liner.rs +++ b/crates/ruff/src/rules/pydocstyle/rules/one_liner.rs @@ -1,12 +1,12 @@ use ruff_macros::{derive_message_formats, violation}; +use crate::ast::strings::{leading_quote, trailing_quote}; use crate::ast::types::Range; use crate::ast::whitespace::LinesWithTrailingNewline; use crate::checkers::ast::Checker; use crate::docstrings::definition::Docstring; use crate::fix::Fix; use crate::registry::Diagnostic; -use crate::rules::pydocstyle::helpers; use crate::violation::AlwaysAutofixableViolation; #[violation] @@ -41,8 +41,8 @@ pub fn one_liner(checker: &mut Checker, docstring: &Docstring) { let mut diagnostic = Diagnostic::new(FitsOnOneLine, Range::from_located(docstring.expr)); if checker.patch(diagnostic.kind.rule()) { if let (Some(leading), Some(trailing)) = ( - helpers::leading_quote(docstring.contents), - helpers::trailing_quote(docstring.contents), + leading_quote(docstring.contents), + trailing_quote(docstring.contents), ) { // If removing whitespace would lead to an invalid string of quote // characters, avoid applying the fix. diff --git a/crates/ruff/src/rules/pylint/rules/bad_string_format_type.rs b/crates/ruff/src/rules/pylint/rules/bad_string_format_type.rs index cf8896c3ec..ca60711b67 100644 --- a/crates/ruff/src/rules/pylint/rules/bad_string_format_type.rs +++ b/crates/ruff/src/rules/pylint/rules/bad_string_format_type.rs @@ -6,10 +6,10 @@ use rustpython_common::cformat::{CFormatPart, CFormatSpec, CFormatStrOrBytes, CF use rustpython_parser::ast::{Constant, Expr, ExprKind, Location, Operator}; use rustpython_parser::{lexer, Mode, Tok}; +use crate::ast::strings::{leading_quote, trailing_quote}; use crate::ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::Diagnostic; -use crate::rules::pydocstyle::helpers::{leading_quote, trailing_quote}; use crate::violation::Violation; /// ## What it does diff --git a/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs index 76e1e6a82c..ec317b2e02 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs @@ -6,11 +6,11 @@ use rustpython_common::format::{ use rustpython_parser::ast::{Constant, Expr, ExprKind, KeywordData}; use rustpython_parser::{lexer, Mode, Tok}; +use crate::ast::strings::{leading_quote, trailing_quote}; use crate::ast::types::Range; use crate::checkers::ast::Checker; use crate::fix::Fix; use crate::registry::Diagnostic; -use crate::rules::pydocstyle::helpers::{leading_quote, trailing_quote}; use crate::rules::pyflakes::format::FormatSummary; use crate::rules::pyupgrade::helpers::curly_escape; use crate::violation::AlwaysAutofixableViolation; diff --git a/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs index 33c42e1e18..a81a9b1537 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -10,12 +10,12 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_stdlib::identifiers::is_identifier; use ruff_python_stdlib::keyword::KWLIST; +use crate::ast::strings::{leading_quote, trailing_quote}; use crate::ast::types::Range; use crate::ast::whitespace::indentation; use crate::checkers::ast::Checker; use crate::fix::Fix; use crate::registry::Diagnostic; -use crate::rules::pydocstyle::helpers::{leading_quote, trailing_quote}; use crate::rules::pyupgrade::helpers::curly_escape; use crate::violation::AlwaysAutofixableViolation; diff --git a/crates/ruff/src/source_code/stylist.rs b/crates/ruff/src/source_code/stylist.rs index 8bcf141be4..2918057195 100644 --- a/crates/ruff/src/source_code/stylist.rs +++ b/crates/ruff/src/source_code/stylist.rs @@ -8,8 +8,8 @@ use ruff_rustpython::vendor; use rustpython_parser::ast::Location; use rustpython_parser::{lexer, Mode, Tok}; +use crate::ast::strings::leading_quote; use crate::ast::types::Range; -use crate::rules::pydocstyle::helpers::leading_quote; use crate::source_code::Locator; pub struct Stylist<'a> {