Updates the relevant formatting logic to utilize the new 'style_edition' option directly instead of the now deprecated 'version' option. 'version' has only been soft deprecated and has auto mapping in place so there should be zero formatting impact to current 'version' users.
724 lines
22 KiB
Rust
724 lines
22 KiB
Rust
use std::borrow::Cow;
|
|
|
|
use rustc_ast::ast::{
|
|
self, Attribute, MetaItem, MetaItemKind, NestedMetaItem, NodeId, Path, Visibility,
|
|
VisibilityKind,
|
|
};
|
|
use rustc_ast::ptr;
|
|
use rustc_ast_pretty::pprust;
|
|
use rustc_span::{sym, symbol, BytePos, LocalExpnId, Span, Symbol, SyntaxContext};
|
|
use unicode_width::UnicodeWidthStr;
|
|
|
|
use crate::comment::{filter_normal_code, CharClasses, FullCodeCharKind, LineClasses};
|
|
use crate::config::{Config, StyleEdition};
|
|
use crate::rewrite::RewriteContext;
|
|
use crate::shape::{Indent, Shape};
|
|
|
|
#[inline]
|
|
pub(crate) fn depr_skip_annotation() -> Symbol {
|
|
Symbol::intern("rustfmt_skip")
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn skip_annotation() -> Symbol {
|
|
Symbol::intern("rustfmt::skip")
|
|
}
|
|
|
|
pub(crate) fn rewrite_ident<'a>(context: &'a RewriteContext<'_>, ident: symbol::Ident) -> &'a str {
|
|
context.snippet(ident.span)
|
|
}
|
|
|
|
// Computes the length of a string's last line, minus offset.
|
|
pub(crate) fn extra_offset(text: &str, shape: Shape) -> usize {
|
|
match text.rfind('\n') {
|
|
// 1 for newline character
|
|
Some(idx) => text.len().saturating_sub(idx + 1 + shape.used_width()),
|
|
None => text.len(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn is_same_visibility(a: &Visibility, b: &Visibility) -> bool {
|
|
match (&a.kind, &b.kind) {
|
|
(
|
|
VisibilityKind::Restricted { path: p, .. },
|
|
VisibilityKind::Restricted { path: q, .. },
|
|
) => pprust::path_to_string(p) == pprust::path_to_string(q),
|
|
(VisibilityKind::Public, VisibilityKind::Public)
|
|
| (VisibilityKind::Inherited, VisibilityKind::Inherited) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
// Uses Cow to avoid allocating in the common cases.
|
|
pub(crate) fn format_visibility(
|
|
context: &RewriteContext<'_>,
|
|
vis: &Visibility,
|
|
) -> Cow<'static, str> {
|
|
match vis.kind {
|
|
VisibilityKind::Public => Cow::from("pub "),
|
|
VisibilityKind::Inherited => Cow::from(""),
|
|
VisibilityKind::Restricted { ref path, .. } => {
|
|
let Path { ref segments, .. } = **path;
|
|
let mut segments_iter = segments.iter().map(|seg| rewrite_ident(context, seg.ident));
|
|
if path.is_global() {
|
|
segments_iter
|
|
.next()
|
|
.expect("Non-global path in pub(restricted)?");
|
|
}
|
|
let is_keyword = |s: &str| s == "crate" || s == "self" || s == "super";
|
|
let path = segments_iter.collect::<Vec<_>>().join("::");
|
|
let in_str = if is_keyword(&path) { "" } else { "in " };
|
|
|
|
Cow::from(format!("pub({in_str}{path}) "))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn format_coro(coroutine_kind: &ast::CoroutineKind) -> &'static str {
|
|
match coroutine_kind {
|
|
ast::CoroutineKind::Async { .. } => "async ",
|
|
ast::CoroutineKind::Gen { .. } => "gen ",
|
|
ast::CoroutineKind::AsyncGen { .. } => "async gen ",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn format_constness(constness: ast::Const) -> &'static str {
|
|
match constness {
|
|
ast::Const::Yes(..) => "const ",
|
|
ast::Const::No => "",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn format_constness_right(constness: ast::Const) -> &'static str {
|
|
match constness {
|
|
ast::Const::Yes(..) => " const",
|
|
ast::Const::No => "",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn format_defaultness(defaultness: ast::Defaultness) -> &'static str {
|
|
match defaultness {
|
|
ast::Defaultness::Default(..) => "default ",
|
|
ast::Defaultness::Final => "",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn format_safety(unsafety: ast::Safety) -> &'static str {
|
|
match unsafety {
|
|
ast::Safety::Unsafe(..) => "unsafe ",
|
|
ast::Safety::Safe(..) => "safe ",
|
|
ast::Safety::Default => "",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn format_auto(is_auto: ast::IsAuto) -> &'static str {
|
|
match is_auto {
|
|
ast::IsAuto::Yes => "auto ",
|
|
ast::IsAuto::No => "",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn format_mutability(mutability: ast::Mutability) -> &'static str {
|
|
match mutability {
|
|
ast::Mutability::Mut => "mut ",
|
|
ast::Mutability::Not => "",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn format_extern(ext: ast::Extern, explicit_abi: bool) -> Cow<'static, str> {
|
|
match ext {
|
|
ast::Extern::None => Cow::from(""),
|
|
ast::Extern::Implicit(_) if explicit_abi => Cow::from("extern \"C\" "),
|
|
ast::Extern::Implicit(_) => Cow::from("extern "),
|
|
// turn `extern "C"` into `extern` when `explicit_abi` is set to false
|
|
ast::Extern::Explicit(abi, _) if abi.symbol_unescaped == sym::C && !explicit_abi => {
|
|
Cow::from("extern ")
|
|
}
|
|
ast::Extern::Explicit(abi, _) => {
|
|
Cow::from(format!(r#"extern "{}" "#, abi.symbol_unescaped))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
// Transform `Vec<rustc_ast::ptr::P<T>>` into `Vec<&T>`
|
|
pub(crate) fn ptr_vec_to_ref_vec<T>(vec: &[ptr::P<T>]) -> Vec<&T> {
|
|
vec.iter().map(|x| &**x).collect::<Vec<_>>()
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn filter_attributes(
|
|
attrs: &[ast::Attribute],
|
|
style: ast::AttrStyle,
|
|
) -> Vec<ast::Attribute> {
|
|
attrs
|
|
.iter()
|
|
.filter(|a| a.style == style)
|
|
.cloned()
|
|
.collect::<Vec<_>>()
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn inner_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
|
|
filter_attributes(attrs, ast::AttrStyle::Inner)
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn outer_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
|
|
filter_attributes(attrs, ast::AttrStyle::Outer)
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn is_single_line(s: &str) -> bool {
|
|
!s.chars().any(|c| c == '\n')
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn first_line_contains_single_line_comment(s: &str) -> bool {
|
|
s.lines().next().map_or(false, |l| l.contains("//"))
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn last_line_contains_single_line_comment(s: &str) -> bool {
|
|
s.lines().last().map_or(false, |l| l.contains("//"))
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn is_attributes_extendable(attrs_str: &str) -> bool {
|
|
!attrs_str.contains('\n') && !last_line_contains_single_line_comment(attrs_str)
|
|
}
|
|
|
|
/// The width of the first line in s.
|
|
#[inline]
|
|
pub(crate) fn first_line_width(s: &str) -> usize {
|
|
unicode_str_width(s.splitn(2, '\n').next().unwrap_or(""))
|
|
}
|
|
|
|
/// The width of the last line in s.
|
|
#[inline]
|
|
pub(crate) fn last_line_width(s: &str) -> usize {
|
|
unicode_str_width(s.rsplitn(2, '\n').next().unwrap_or(""))
|
|
}
|
|
|
|
/// The total used width of the last line.
|
|
#[inline]
|
|
pub(crate) fn last_line_used_width(s: &str, offset: usize) -> usize {
|
|
if s.contains('\n') {
|
|
last_line_width(s)
|
|
} else {
|
|
offset + unicode_str_width(s)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn trimmed_last_line_width(s: &str) -> usize {
|
|
unicode_str_width(match s.rfind('\n') {
|
|
Some(n) => s[(n + 1)..].trim(),
|
|
None => s.trim(),
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn last_line_extendable(s: &str) -> bool {
|
|
if s.ends_with("\"#") {
|
|
return true;
|
|
}
|
|
for c in s.chars().rev() {
|
|
match c {
|
|
'(' | ')' | ']' | '}' | '?' | '>' => continue,
|
|
'\n' => break,
|
|
_ if c.is_whitespace() => continue,
|
|
_ => return false,
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
#[inline]
|
|
fn is_skip(meta_item: &MetaItem) -> bool {
|
|
match meta_item.kind {
|
|
MetaItemKind::Word => {
|
|
let path_str = pprust::path_to_string(&meta_item.path);
|
|
path_str == skip_annotation().as_str() || path_str == depr_skip_annotation().as_str()
|
|
}
|
|
MetaItemKind::List(ref l) => {
|
|
meta_item.has_name(sym::cfg_attr) && l.len() == 2 && is_skip_nested(&l[1])
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn is_skip_nested(meta_item: &NestedMetaItem) -> bool {
|
|
match meta_item {
|
|
NestedMetaItem::MetaItem(ref mi) => is_skip(mi),
|
|
NestedMetaItem::Lit(_) => false,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn contains_skip(attrs: &[Attribute]) -> bool {
|
|
attrs
|
|
.iter()
|
|
.any(|a| a.meta().map_or(false, |a| is_skip(&a)))
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn semicolon_for_expr(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool {
|
|
// Never try to insert semicolons on expressions when we're inside
|
|
// a macro definition - this can prevent the macro from compiling
|
|
// when used in expression position
|
|
if context.is_macro_def {
|
|
return false;
|
|
}
|
|
|
|
match expr.kind {
|
|
ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => {
|
|
context.config.trailing_semicolon()
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn semicolon_for_stmt(
|
|
context: &RewriteContext<'_>,
|
|
stmt: &ast::Stmt,
|
|
is_last_expr: bool,
|
|
) -> bool {
|
|
match stmt.kind {
|
|
ast::StmtKind::Semi(ref expr) => match expr.kind {
|
|
ast::ExprKind::While(..) | ast::ExprKind::Loop(..) | ast::ExprKind::ForLoop { .. } => {
|
|
false
|
|
}
|
|
ast::ExprKind::Break(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Ret(..) => {
|
|
// The only time we can skip the semi-colon is if the config option is set to false
|
|
// **and** this is the last expr (even though any following exprs are unreachable)
|
|
context.config.trailing_semicolon() || !is_last_expr
|
|
}
|
|
_ => true,
|
|
},
|
|
ast::StmtKind::Expr(..) => false,
|
|
_ => true,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn stmt_expr(stmt: &ast::Stmt) -> Option<&ast::Expr> {
|
|
match stmt.kind {
|
|
ast::StmtKind::Expr(ref expr) => Some(expr),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Returns the number of LF and CRLF respectively.
|
|
pub(crate) fn count_lf_crlf(input: &str) -> (usize, usize) {
|
|
let mut lf = 0;
|
|
let mut crlf = 0;
|
|
let mut is_crlf = false;
|
|
for c in input.as_bytes() {
|
|
match c {
|
|
b'\r' => is_crlf = true,
|
|
b'\n' if is_crlf => crlf += 1,
|
|
b'\n' => lf += 1,
|
|
_ => is_crlf = false,
|
|
}
|
|
}
|
|
(lf, crlf)
|
|
}
|
|
|
|
pub(crate) fn count_newlines(input: &str) -> usize {
|
|
// Using bytes to omit UTF-8 decoding
|
|
bytecount::count(input.as_bytes(), b'\n')
|
|
}
|
|
|
|
// For format_missing and last_pos, need to use the source callsite (if applicable).
|
|
// Required as generated code spans aren't guaranteed to follow on from the last span.
|
|
macro_rules! source {
|
|
($this:ident, $sp:expr) => {
|
|
$sp.source_callsite()
|
|
};
|
|
}
|
|
|
|
pub(crate) fn mk_sp(lo: BytePos, hi: BytePos) -> Span {
|
|
Span::new(lo, hi, SyntaxContext::root(), None)
|
|
}
|
|
|
|
pub(crate) fn mk_sp_lo_plus_one(lo: BytePos) -> Span {
|
|
Span::new(lo, lo + BytePos(1), SyntaxContext::root(), None)
|
|
}
|
|
|
|
// Returns `true` if the given span does not intersect with file lines.
|
|
macro_rules! out_of_file_lines_range {
|
|
($self:ident, $span:expr) => {
|
|
!$self.config.file_lines().is_all()
|
|
&& !$self
|
|
.config
|
|
.file_lines()
|
|
.intersects(&$self.psess.lookup_line_range($span))
|
|
};
|
|
}
|
|
|
|
macro_rules! skip_out_of_file_lines_range {
|
|
($self:ident, $span:expr) => {
|
|
if out_of_file_lines_range!($self, $span) {
|
|
return None;
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! skip_out_of_file_lines_range_err {
|
|
($self:ident, $span:expr) => {
|
|
if out_of_file_lines_range!($self, $span) {
|
|
return Err(RewriteError::SkipFormatting);
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! skip_out_of_file_lines_range_visitor {
|
|
($self:ident, $span:expr) => {
|
|
if out_of_file_lines_range!($self, $span) {
|
|
$self.push_rewrite($span, None);
|
|
return;
|
|
}
|
|
};
|
|
}
|
|
|
|
// Wraps String in an Option. Returns Some when the string adheres to the
|
|
// Rewrite constraints defined for the Rewrite trait and None otherwise.
|
|
pub(crate) fn wrap_str(s: String, max_width: usize, shape: Shape) -> Option<String> {
|
|
if filtered_str_fits(&s, max_width, shape) {
|
|
Some(s)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) fn filtered_str_fits(snippet: &str, max_width: usize, shape: Shape) -> bool {
|
|
let snippet = &filter_normal_code(snippet);
|
|
if !snippet.is_empty() {
|
|
// First line must fits with `shape.width`.
|
|
if first_line_width(snippet) > shape.width {
|
|
return false;
|
|
}
|
|
// If the snippet does not include newline, we are done.
|
|
if is_single_line(snippet) {
|
|
return true;
|
|
}
|
|
// The other lines must fit within the maximum width.
|
|
if snippet
|
|
.lines()
|
|
.skip(1)
|
|
.any(|line| unicode_str_width(line) > max_width)
|
|
{
|
|
return false;
|
|
}
|
|
// A special check for the last line, since the caller may
|
|
// place trailing characters on this line.
|
|
if last_line_width(snippet) > shape.used_width() + shape.width {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn colon_spaces(config: &Config) -> &'static str {
|
|
let before = config.space_before_colon();
|
|
let after = config.space_after_colon();
|
|
match (before, after) {
|
|
(true, true) => " : ",
|
|
(true, false) => " :",
|
|
(false, true) => ": ",
|
|
(false, false) => ":",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
|
|
match e.kind {
|
|
ast::ExprKind::Call(ref e, _)
|
|
| ast::ExprKind::Binary(_, ref e, _)
|
|
| ast::ExprKind::Cast(ref e, _)
|
|
| ast::ExprKind::Type(ref e, _)
|
|
| ast::ExprKind::Assign(ref e, _, _)
|
|
| ast::ExprKind::AssignOp(_, ref e, _)
|
|
| ast::ExprKind::Field(ref e, _)
|
|
| ast::ExprKind::Index(ref e, _, _)
|
|
| ast::ExprKind::Range(Some(ref e), _, _)
|
|
| ast::ExprKind::Try(ref e) => left_most_sub_expr(e),
|
|
_ => e,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn starts_with_newline(s: &str) -> bool {
|
|
s.starts_with('\n') || s.starts_with("\r\n")
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn first_line_ends_with(s: &str, c: char) -> bool {
|
|
s.lines().next().map_or(false, |l| l.ends_with(c))
|
|
}
|
|
|
|
// States whether an expression's last line exclusively consists of closing
|
|
// parens, braces, and brackets in its idiomatic formatting.
|
|
pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr: &str) -> bool {
|
|
match expr.kind {
|
|
ast::ExprKind::MacCall(..)
|
|
| ast::ExprKind::FormatArgs(..)
|
|
| ast::ExprKind::Call(..)
|
|
| ast::ExprKind::MethodCall(..)
|
|
| ast::ExprKind::Array(..)
|
|
| ast::ExprKind::Struct(..)
|
|
| ast::ExprKind::While(..)
|
|
| ast::ExprKind::If(..)
|
|
| ast::ExprKind::Block(..)
|
|
| ast::ExprKind::ConstBlock(..)
|
|
| ast::ExprKind::Gen(..)
|
|
| ast::ExprKind::Loop(..)
|
|
| ast::ExprKind::ForLoop { .. }
|
|
| ast::ExprKind::TryBlock(..)
|
|
| ast::ExprKind::Match(..) => repr.contains('\n'),
|
|
ast::ExprKind::Paren(ref expr)
|
|
| ast::ExprKind::Binary(_, _, ref expr)
|
|
| ast::ExprKind::Index(_, ref expr, _)
|
|
| ast::ExprKind::Unary(_, ref expr)
|
|
| ast::ExprKind::Try(ref expr)
|
|
| ast::ExprKind::Yield(Some(ref expr)) => is_block_expr(context, expr, repr),
|
|
ast::ExprKind::Closure(ref closure) => is_block_expr(context, &closure.body, repr),
|
|
// This can only be a string lit
|
|
ast::ExprKind::Lit(_) => {
|
|
repr.contains('\n') && trimmed_last_line_width(repr) <= context.config.tab_spaces()
|
|
}
|
|
ast::ExprKind::AddrOf(..)
|
|
| ast::ExprKind::Assign(..)
|
|
| ast::ExprKind::AssignOp(..)
|
|
| ast::ExprKind::Await(..)
|
|
| ast::ExprKind::Break(..)
|
|
| ast::ExprKind::Cast(..)
|
|
| ast::ExprKind::Continue(..)
|
|
| ast::ExprKind::Dummy
|
|
| ast::ExprKind::Err(_)
|
|
| ast::ExprKind::Field(..)
|
|
| ast::ExprKind::IncludedBytes(..)
|
|
| ast::ExprKind::InlineAsm(..)
|
|
| ast::ExprKind::OffsetOf(..)
|
|
| ast::ExprKind::Let(..)
|
|
| ast::ExprKind::Path(..)
|
|
| ast::ExprKind::Range(..)
|
|
| ast::ExprKind::Repeat(..)
|
|
| ast::ExprKind::Ret(..)
|
|
| ast::ExprKind::Become(..)
|
|
| ast::ExprKind::Yeet(..)
|
|
| ast::ExprKind::Tup(..)
|
|
| ast::ExprKind::Type(..)
|
|
| ast::ExprKind::Yield(None)
|
|
| ast::ExprKind::Underscore => false,
|
|
}
|
|
}
|
|
|
|
/// Removes trailing spaces from the specified snippet. We do not remove spaces
|
|
/// inside strings or comments.
|
|
pub(crate) fn remove_trailing_white_spaces(text: &str) -> String {
|
|
let mut buffer = String::with_capacity(text.len());
|
|
let mut space_buffer = String::with_capacity(128);
|
|
for (char_kind, c) in CharClasses::new(text.chars()) {
|
|
match c {
|
|
'\n' => {
|
|
if char_kind == FullCodeCharKind::InString {
|
|
buffer.push_str(&space_buffer);
|
|
}
|
|
space_buffer.clear();
|
|
buffer.push('\n');
|
|
}
|
|
_ if c.is_whitespace() => {
|
|
space_buffer.push(c);
|
|
}
|
|
_ => {
|
|
if !space_buffer.is_empty() {
|
|
buffer.push_str(&space_buffer);
|
|
space_buffer.clear();
|
|
}
|
|
buffer.push(c);
|
|
}
|
|
}
|
|
}
|
|
buffer
|
|
}
|
|
|
|
/// Indent each line according to the specified `indent`.
|
|
/// e.g.
|
|
///
|
|
/// ```rust,compile_fail
|
|
/// foo!{
|
|
/// x,
|
|
/// y,
|
|
/// foo(
|
|
/// a,
|
|
/// b,
|
|
/// c,
|
|
/// ),
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// will become
|
|
///
|
|
/// ```rust,compile_fail
|
|
/// foo!{
|
|
/// x,
|
|
/// y,
|
|
/// foo(
|
|
/// a,
|
|
/// b,
|
|
/// c,
|
|
/// ),
|
|
/// }
|
|
/// ```
|
|
pub(crate) fn trim_left_preserve_layout(
|
|
orig: &str,
|
|
indent: Indent,
|
|
config: &Config,
|
|
) -> Option<String> {
|
|
let mut lines = LineClasses::new(orig);
|
|
let first_line = lines.next().map(|(_, s)| s.trim_end().to_owned())?;
|
|
let mut trimmed_lines = Vec::with_capacity(16);
|
|
|
|
let mut veto_trim = false;
|
|
let min_prefix_space_width = lines
|
|
.filter_map(|(kind, line)| {
|
|
let mut trimmed = true;
|
|
let prefix_space_width = if is_empty_line(&line) {
|
|
None
|
|
} else {
|
|
Some(get_prefix_space_width(config, &line))
|
|
};
|
|
|
|
// just InString{Commented} in order to allow the start of a string to be indented
|
|
let new_veto_trim_value = (kind == FullCodeCharKind::InString
|
|
|| (config.style_edition() >= StyleEdition::Edition2024
|
|
&& kind == FullCodeCharKind::InStringCommented))
|
|
&& !line.ends_with('\\');
|
|
let line = if veto_trim || new_veto_trim_value {
|
|
veto_trim = new_veto_trim_value;
|
|
trimmed = false;
|
|
line
|
|
} else {
|
|
line.trim().to_owned()
|
|
};
|
|
trimmed_lines.push((trimmed, line, prefix_space_width));
|
|
|
|
// Because there is a veto against trimming and indenting lines within a string,
|
|
// such lines should not be taken into account when computing the minimum.
|
|
match kind {
|
|
FullCodeCharKind::InStringCommented | FullCodeCharKind::EndStringCommented
|
|
if config.style_edition() >= StyleEdition::Edition2024 =>
|
|
{
|
|
None
|
|
}
|
|
FullCodeCharKind::InString | FullCodeCharKind::EndString => None,
|
|
_ => prefix_space_width,
|
|
}
|
|
})
|
|
.min()?;
|
|
|
|
Some(
|
|
first_line
|
|
+ "\n"
|
|
+ &trimmed_lines
|
|
.iter()
|
|
.map(
|
|
|&(trimmed, ref line, prefix_space_width)| match prefix_space_width {
|
|
_ if !trimmed => line.to_owned(),
|
|
Some(original_indent_width) => {
|
|
let new_indent_width = indent.width()
|
|
+ original_indent_width.saturating_sub(min_prefix_space_width);
|
|
let new_indent = Indent::from_width(config, new_indent_width);
|
|
format!("{}{}", new_indent.to_string(config), line)
|
|
}
|
|
None => String::new(),
|
|
},
|
|
)
|
|
.collect::<Vec<_>>()
|
|
.join("\n"),
|
|
)
|
|
}
|
|
|
|
/// Based on the given line, determine if the next line can be indented or not.
|
|
/// This allows to preserve the indentation of multi-line literals when
|
|
/// re-inserted a code block that has been formatted separately from the rest
|
|
/// of the code, such as code in macro defs or code blocks doc comments.
|
|
pub(crate) fn indent_next_line(kind: FullCodeCharKind, line: &str, config: &Config) -> bool {
|
|
if kind.is_string() {
|
|
// If the string ends with '\', the string has been wrapped over
|
|
// multiple lines. If `format_strings = true`, then the indentation of
|
|
// strings wrapped over multiple lines will have been adjusted while
|
|
// formatting the code block, therefore the string's indentation needs
|
|
// to be adjusted for the code surrounding the code block.
|
|
config.format_strings() && line.ends_with('\\')
|
|
} else if config.style_edition() >= StyleEdition::Edition2024 {
|
|
!kind.is_commented_string()
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
|
|
pub(crate) fn is_empty_line(s: &str) -> bool {
|
|
s.is_empty() || s.chars().all(char::is_whitespace)
|
|
}
|
|
|
|
fn get_prefix_space_width(config: &Config, s: &str) -> usize {
|
|
let mut width = 0;
|
|
for c in s.chars() {
|
|
match c {
|
|
' ' => width += 1,
|
|
'\t' => width += config.tab_spaces(),
|
|
_ => return width,
|
|
}
|
|
}
|
|
width
|
|
}
|
|
|
|
pub(crate) trait NodeIdExt {
|
|
fn root() -> Self;
|
|
}
|
|
|
|
impl NodeIdExt for NodeId {
|
|
fn root() -> NodeId {
|
|
NodeId::placeholder_from_expn_id(LocalExpnId::ROOT)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn unicode_str_width(s: &str) -> usize {
|
|
s.width()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_remove_trailing_white_spaces() {
|
|
let s = " r#\"\n test\n \"#";
|
|
assert_eq!(remove_trailing_white_spaces(s), s);
|
|
}
|
|
|
|
#[test]
|
|
fn test_trim_left_preserve_layout() {
|
|
let s = "aaa\n\tbbb\n ccc";
|
|
let config = Config::default();
|
|
let indent = Indent::new(4, 0);
|
|
assert_eq!(
|
|
trim_left_preserve_layout(s, indent, &config),
|
|
Some("aaa\n bbb\n ccc".to_string())
|
|
);
|
|
}
|
|
}
|