## Summary This crate now contains utilities for dealing with trivia more broadly: whitespace, newlines, "simple" trivia lexing, etc. So renaming it to reflect its increased responsibilities. To avoid conflicts, I've also renamed `Token` and `TokenKind` to `SimpleToken` and `SimpleTokenKind`.
162 lines
4.7 KiB
Rust
162 lines
4.7 KiB
Rust
use ruff_text_size::TextRange;
|
|
use rustpython_parser::ast::{Ranged, StmtAsyncWith, StmtWith, Suite, WithItem};
|
|
|
|
use ruff_formatter::{format_args, write, FormatError};
|
|
use ruff_python_ast::node::AnyNodeRef;
|
|
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
|
|
|
use crate::comments::trailing_comments;
|
|
use crate::expression::parentheses::{
|
|
in_parentheses_only_soft_line_break_or_space, optional_parentheses,
|
|
};
|
|
use crate::prelude::*;
|
|
use crate::FormatNodeRule;
|
|
|
|
pub(super) enum AnyStatementWith<'a> {
|
|
With(&'a StmtWith),
|
|
AsyncWith(&'a StmtAsyncWith),
|
|
}
|
|
|
|
impl<'a> AnyStatementWith<'a> {
|
|
const fn is_async(&self) -> bool {
|
|
matches!(self, AnyStatementWith::AsyncWith(_))
|
|
}
|
|
|
|
fn items(&self) -> &[WithItem] {
|
|
match self {
|
|
AnyStatementWith::With(with) => with.items.as_slice(),
|
|
AnyStatementWith::AsyncWith(with) => with.items.as_slice(),
|
|
}
|
|
}
|
|
|
|
fn body(&self) -> &Suite {
|
|
match self {
|
|
AnyStatementWith::With(with) => &with.body,
|
|
AnyStatementWith::AsyncWith(with) => &with.body,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Ranged for AnyStatementWith<'_> {
|
|
fn range(&self) -> TextRange {
|
|
match self {
|
|
AnyStatementWith::With(with) => with.range(),
|
|
AnyStatementWith::AsyncWith(with) => with.range(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a StmtWith> for AnyStatementWith<'a> {
|
|
fn from(value: &'a StmtWith) -> Self {
|
|
AnyStatementWith::With(value)
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a StmtAsyncWith> for AnyStatementWith<'a> {
|
|
fn from(value: &'a StmtAsyncWith) -> Self {
|
|
AnyStatementWith::AsyncWith(value)
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&AnyStatementWith<'a>> for AnyNodeRef<'a> {
|
|
fn from(value: &AnyStatementWith<'a>) -> Self {
|
|
match value {
|
|
AnyStatementWith::With(with) => AnyNodeRef::StmtWith(with),
|
|
AnyStatementWith::AsyncWith(with) => AnyNodeRef::StmtAsyncWith(with),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Format<PyFormatContext<'_>> for AnyStatementWith<'_> {
|
|
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
|
let comments = f.context().comments().clone();
|
|
let dangling_comments = comments.dangling_comments(self);
|
|
|
|
write!(
|
|
f,
|
|
[
|
|
self.is_async()
|
|
.then_some(format_args![text("async"), space()]),
|
|
text("with"),
|
|
space()
|
|
]
|
|
)?;
|
|
|
|
if are_with_items_parenthesized(self, f.context())? {
|
|
optional_parentheses(&format_with(|f| {
|
|
let mut joiner = f.join_comma_separated(self.body().first().unwrap().start());
|
|
|
|
for item in self.items() {
|
|
joiner.entry_with_line_separator(
|
|
item,
|
|
&item.format(),
|
|
in_parentheses_only_soft_line_break_or_space(),
|
|
);
|
|
}
|
|
joiner.finish()
|
|
}))
|
|
.fmt(f)?;
|
|
} else {
|
|
f.join_with(format_args![text(","), space()])
|
|
.entries(self.items().iter().formatted())
|
|
.finish()?;
|
|
}
|
|
|
|
write!(
|
|
f,
|
|
[
|
|
text(":"),
|
|
trailing_comments(dangling_comments),
|
|
block_indent(&self.body().format())
|
|
]
|
|
)
|
|
}
|
|
}
|
|
|
|
fn are_with_items_parenthesized(
|
|
with: &AnyStatementWith,
|
|
context: &PyFormatContext,
|
|
) -> FormatResult<bool> {
|
|
let first_with_item = with
|
|
.items()
|
|
.first()
|
|
.ok_or(FormatError::syntax_error("Expected at least one with item"))?;
|
|
let before_first_with_item = TextRange::new(with.start(), first_with_item.start());
|
|
|
|
let mut tokenizer = SimpleTokenizer::new(context.source(), before_first_with_item)
|
|
.skip_trivia()
|
|
.skip_while(|t| t.kind() == SimpleTokenKind::Async);
|
|
|
|
let with_keyword = tokenizer.next().ok_or(FormatError::syntax_error(
|
|
"Expected a with keyword, didn't find any token",
|
|
))?;
|
|
|
|
debug_assert_eq!(
|
|
with_keyword.kind(),
|
|
SimpleTokenKind::With,
|
|
"Expected with keyword but at {with_keyword:?}"
|
|
);
|
|
|
|
match tokenizer.next() {
|
|
Some(left_paren) => {
|
|
debug_assert_eq!(left_paren.kind(), SimpleTokenKind::LParen);
|
|
Ok(true)
|
|
}
|
|
None => Ok(false),
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct FormatStmtWith;
|
|
|
|
impl FormatNodeRule<StmtWith> for FormatStmtWith {
|
|
fn fmt_fields(&self, item: &StmtWith, f: &mut PyFormatter) -> FormatResult<()> {
|
|
AnyStatementWith::from(item).fmt(f)
|
|
}
|
|
|
|
fn fmt_dangling_comments(&self, _node: &StmtWith, _f: &mut PyFormatter) -> FormatResult<()> {
|
|
// Handled in `fmt_fields`
|
|
Ok(())
|
|
}
|
|
}
|