2016-05-28 00:58:25 +02:00
|
|
|
use std::borrow::Cow;
|
2015-09-07 21:34:37 +02:00
|
|
|
|
2020-03-27 22:29:12 -05:00
|
|
|
use rustc_ast::ast::{
|
2019-06-03 23:57:02 +09:00
|
|
|
self, Attribute, CrateSugar, MetaItem, MetaItemKind, NestedMetaItem, NodeId, Path, Visibility,
|
|
|
|
VisibilityKind,
|
2018-04-29 20:22:48 +08:00
|
|
|
};
|
2020-03-27 22:29:12 -05:00
|
|
|
use rustc_ast::ptr;
|
|
|
|
use rustc_ast_pretty::pprust;
|
2021-06-25 20:43:04 +02:00
|
|
|
use rustc_span::{sym, symbol, BytePos, LocalExpnId, Span, Symbol, SyntaxContext};
|
2019-01-15 08:41:09 +09:00
|
|
|
use unicode_width::UnicodeWidthStr;
|
|
|
|
|
2019-02-04 13:30:43 +03:00
|
|
|
use crate::comment::{filter_normal_code, CharClasses, FullCodeCharKind, LineClasses};
|
|
|
|
use crate::config::{Config, Version};
|
|
|
|
use crate::rewrite::RewriteContext;
|
|
|
|
use crate::shape::{Indent, Shape};
|
|
|
|
|
2019-06-03 23:57:02 +09:00
|
|
|
#[inline]
|
|
|
|
pub(crate) fn depr_skip_annotation() -> Symbol {
|
|
|
|
Symbol::intern("rustfmt_skip")
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub(crate) fn skip_annotation() -> Symbol {
|
|
|
|
Symbol::intern("rustfmt::skip")
|
|
|
|
}
|
2015-06-05 16:56:59 +02:00
|
|
|
|
2020-05-19 17:31:28 +09:00
|
|
|
pub(crate) fn rewrite_ident<'a>(context: &'a RewriteContext<'_>, ident: symbol::Ident) -> &'a str {
|
2018-06-25 23:36:45 +09:00
|
|
|
context.snippet(ident.span)
|
|
|
|
}
|
|
|
|
|
2016-03-07 13:41:32 -05:00
|
|
|
// Computes the length of a string's last line, minus offset.
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn extra_offset(text: &str, shape: Shape) -> usize {
|
2016-03-07 13:41:32 -05:00
|
|
|
match text.rfind('\n') {
|
|
|
|
// 1 for newline character
|
2018-05-14 21:58:57 +09:00
|
|
|
Some(idx) => text.len().saturating_sub(idx + 1 + shape.used_width()),
|
2016-03-07 13:41:32 -05:00
|
|
|
None => text.len(),
|
|
|
|
}
|
2015-12-27 14:25:37 +01:00
|
|
|
}
|
|
|
|
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn is_same_visibility(a: &Visibility, b: &Visibility) -> bool {
|
2020-09-22 22:22:38 -04:00
|
|
|
match (&a.kind, &b.kind) {
|
2018-07-20 16:18:45 +09:00
|
|
|
(
|
|
|
|
VisibilityKind::Restricted { path: p, .. },
|
|
|
|
VisibilityKind::Restricted { path: q, .. },
|
2020-09-22 22:22:38 -04:00
|
|
|
) => pprust::path_to_string(&p) == pprust::path_to_string(&q),
|
2018-07-20 16:18:45 +09:00
|
|
|
(VisibilityKind::Public, VisibilityKind::Public)
|
|
|
|
| (VisibilityKind::Inherited, VisibilityKind::Inherited)
|
|
|
|
| (
|
|
|
|
VisibilityKind::Crate(CrateSugar::PubCrate),
|
|
|
|
VisibilityKind::Crate(CrateSugar::PubCrate),
|
|
|
|
)
|
|
|
|
| (
|
|
|
|
VisibilityKind::Crate(CrateSugar::JustCrate),
|
|
|
|
VisibilityKind::Crate(CrateSugar::JustCrate),
|
|
|
|
) => true,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-28 00:58:25 +02:00
|
|
|
// Uses Cow to avoid allocating in the common cases.
|
2019-05-09 21:13:32 +02:00
|
|
|
pub(crate) fn format_visibility(
|
|
|
|
context: &RewriteContext<'_>,
|
|
|
|
vis: &Visibility,
|
|
|
|
) -> Cow<'static, str> {
|
2020-09-22 22:22:38 -04:00
|
|
|
match vis.kind {
|
2018-03-06 19:47:28 +09:00
|
|
|
VisibilityKind::Public => Cow::from("pub "),
|
|
|
|
VisibilityKind::Inherited => Cow::from(""),
|
|
|
|
VisibilityKind::Crate(CrateSugar::PubCrate) => Cow::from("pub(crate) "),
|
|
|
|
VisibilityKind::Crate(CrateSugar::JustCrate) => Cow::from("crate "),
|
|
|
|
VisibilityKind::Restricted { ref path, .. } => {
|
2017-01-19 10:47:07 +13:00
|
|
|
let Path { ref segments, .. } = **path;
|
2018-06-25 23:36:45 +09:00
|
|
|
let mut segments_iter = segments.iter().map(|seg| rewrite_ident(context, seg.ident));
|
2017-01-19 10:47:07 +13:00
|
|
|
if path.is_global() {
|
2017-06-16 08:49:49 +09:00
|
|
|
segments_iter
|
|
|
|
.next()
|
|
|
|
.expect("Non-global path in pub(restricted)?");
|
2017-01-19 10:47:07 +13:00
|
|
|
}
|
2017-05-12 22:25:26 +09:00
|
|
|
let is_keyword = |s: &str| s == "self" || s == "super";
|
2017-05-17 18:57:18 +12:00
|
|
|
let path = segments_iter.collect::<Vec<_>>().join("::");
|
2017-05-12 22:25:26 +09:00
|
|
|
let in_str = if is_keyword(&path) { "" } else { "in " };
|
2016-05-28 00:58:25 +02:00
|
|
|
|
2017-05-12 22:25:26 +09:00
|
|
|
Cow::from(format!("pub({}{}) ", in_str, path))
|
2016-05-28 00:58:25 +02:00
|
|
|
}
|
2015-05-29 12:41:26 +02:00
|
|
|
}
|
|
|
|
}
|
2015-06-08 19:40:22 +02:00
|
|
|
|
2018-07-29 07:37:24 -07:00
|
|
|
#[inline]
|
2020-03-26 15:26:58 -05:00
|
|
|
pub(crate) fn format_async(is_async: &ast::Async) -> &'static str {
|
2018-07-29 17:20:11 -07:00
|
|
|
match is_async {
|
2020-03-26 15:26:58 -05:00
|
|
|
ast::Async::Yes { .. } => "async ",
|
|
|
|
ast::Async::No => "",
|
2018-07-29 07:37:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-01 22:19:20 +09:00
|
|
|
#[inline]
|
2020-03-26 15:26:58 -05:00
|
|
|
pub(crate) fn format_constness(constness: ast::Const) -> &'static str {
|
2017-08-01 22:19:20 +09:00
|
|
|
match constness {
|
2020-03-26 15:26:58 -05:00
|
|
|
ast::Const::Yes(..) => "const ",
|
|
|
|
ast::Const::No => "",
|
2017-08-01 22:19:20 +09:00
|
|
|
}
|
|
|
|
}
|
2020-05-31 03:36:08 -05:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub(crate) fn format_constness_right(constness: ast::Const) -> &'static str {
|
|
|
|
match constness {
|
|
|
|
ast::Const::Yes(..) => " const",
|
|
|
|
ast::Const::No => "",
|
|
|
|
}
|
|
|
|
}
|
2017-08-01 22:19:20 +09:00
|
|
|
|
2017-07-27 09:43:35 +09:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn format_defaultness(defaultness: ast::Defaultness) -> &'static str {
|
2017-07-27 09:43:35 +09:00
|
|
|
match defaultness {
|
2020-03-26 17:20:24 -05:00
|
|
|
ast::Defaultness::Default(..) => "default ",
|
2017-07-27 09:43:35 +09:00
|
|
|
ast::Defaultness::Final => "",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-22 16:07:38 +01:00
|
|
|
#[inline]
|
2020-03-26 15:26:58 -05:00
|
|
|
pub(crate) fn format_unsafety(unsafety: ast::Unsafe) -> &'static str {
|
2015-11-22 16:07:38 +01:00
|
|
|
match unsafety {
|
2020-03-26 15:26:58 -05:00
|
|
|
ast::Unsafe::Yes(..) => "unsafe ",
|
|
|
|
ast::Unsafe::No => "",
|
2015-11-22 16:07:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-20 17:31:31 +03:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn format_auto(is_auto: ast::IsAuto) -> &'static str {
|
2018-04-20 17:31:31 +03:00
|
|
|
match is_auto {
|
|
|
|
ast::IsAuto::Yes => "auto ",
|
|
|
|
ast::IsAuto::No => "",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-24 19:54:38 +02:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn format_mutability(mutability: ast::Mutability) -> &'static str {
|
2015-07-24 19:54:38 +02:00
|
|
|
match mutability {
|
2020-02-08 22:21:37 -06:00
|
|
|
ast::Mutability::Mut => "mut ",
|
|
|
|
ast::Mutability::Not => "",
|
2015-07-24 19:54:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-22 16:07:38 +01:00
|
|
|
#[inline]
|
2020-02-08 22:21:37 -06:00
|
|
|
pub(crate) fn format_extern(
|
|
|
|
ext: ast::Extern,
|
|
|
|
explicit_abi: bool,
|
|
|
|
is_mod: bool,
|
|
|
|
) -> Cow<'static, str> {
|
|
|
|
let abi = match ext {
|
2020-03-31 01:30:26 -05:00
|
|
|
ast::Extern::None => "Rust".to_owned(),
|
|
|
|
ast::Extern::Implicit => "C".to_owned(),
|
|
|
|
ast::Extern::Explicit(abi) => abi.symbol_unescaped.to_string(),
|
2020-02-08 22:21:37 -06:00
|
|
|
};
|
|
|
|
|
2020-03-31 01:30:26 -05:00
|
|
|
if abi == "Rust" && !is_mod {
|
2017-09-29 15:08:48 +09:00
|
|
|
Cow::from("")
|
2020-03-31 01:30:26 -05:00
|
|
|
} else if abi == "C" && !explicit_abi {
|
2017-09-15 18:11:24 +09:00
|
|
|
Cow::from("extern ")
|
2016-04-18 18:39:40 +02:00
|
|
|
} else {
|
2020-03-31 01:30:26 -05:00
|
|
|
Cow::from(format!(r#"extern "{}" "#, abi))
|
2016-04-18 18:39:40 +02:00
|
|
|
}
|
2015-11-22 16:07:38 +01:00
|
|
|
}
|
|
|
|
|
2017-09-15 22:27:20 +09:00
|
|
|
#[inline]
|
2020-03-27 22:29:12 -05:00
|
|
|
// Transform `Vec<rustc_ast::ptr::P<T>>` into `Vec<&T>`
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn ptr_vec_to_ref_vec<T>(vec: &[ptr::P<T>]) -> Vec<&T> {
|
2017-09-15 22:27:20 +09:00
|
|
|
vec.iter().map(|x| &**x).collect::<Vec<_>>()
|
|
|
|
}
|
|
|
|
|
2017-08-05 15:21:46 +09:00
|
|
|
#[inline]
|
2019-05-09 21:13:32 +02:00
|
|
|
pub(crate) fn filter_attributes(
|
|
|
|
attrs: &[ast::Attribute],
|
|
|
|
style: ast::AttrStyle,
|
|
|
|
) -> Vec<ast::Attribute> {
|
2017-08-05 15:21:46 +09:00
|
|
|
attrs
|
|
|
|
.iter()
|
|
|
|
.filter(|a| a.style == style)
|
|
|
|
.cloned()
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn inner_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
|
2017-08-05 15:21:46 +09:00
|
|
|
filter_attributes(attrs, ast::AttrStyle::Inner)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn outer_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> {
|
2017-08-05 15:21:46 +09:00
|
|
|
filter_attributes(attrs, ast::AttrStyle::Outer)
|
|
|
|
}
|
|
|
|
|
2018-04-28 13:59:03 +09:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn is_single_line(s: &str) -> bool {
|
2021-07-25 22:57:19 -05:00
|
|
|
!s.chars().any(|c| c == '\n')
|
2018-04-28 13:59:03 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn first_line_contains_single_line_comment(s: &str) -> bool {
|
2018-04-28 13:59:03 +09:00
|
|
|
s.lines().next().map_or(false, |l| l.contains("//"))
|
|
|
|
}
|
|
|
|
|
2017-08-11 17:52:13 +09:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn last_line_contains_single_line_comment(s: &str) -> bool {
|
2017-08-11 17:52:13 +09:00
|
|
|
s.lines().last().map_or(false, |l| l.contains("//"))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn is_attributes_extendable(attrs_str: &str) -> bool {
|
2017-08-29 22:16:04 +09:00
|
|
|
!attrs_str.contains('\n') && !last_line_contains_single_line_comment(attrs_str)
|
2017-08-11 17:52:13 +09:00
|
|
|
}
|
|
|
|
|
2019-10-04 17:22:01 +02:00
|
|
|
/// The width of the first line in s.
|
2015-08-14 20:00:22 +12:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn first_line_width(s: &str) -> usize {
|
2019-01-15 08:41:09 +09:00
|
|
|
unicode_str_width(s.splitn(2, '\n').next().unwrap_or(""))
|
2015-08-14 20:00:22 +12:00
|
|
|
}
|
|
|
|
|
2019-10-04 17:22:01 +02:00
|
|
|
/// The width of the last line in s.
|
2015-08-14 20:00:22 +12:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn last_line_width(s: &str) -> usize {
|
2019-01-15 08:41:09 +09:00
|
|
|
unicode_str_width(s.rsplitn(2, '\n').next().unwrap_or(""))
|
2015-08-14 20:00:22 +12:00
|
|
|
}
|
2017-06-15 16:26:41 +09:00
|
|
|
|
2019-10-04 17:22:01 +02:00
|
|
|
/// The total used width of the last line.
|
2017-08-01 22:19:20 +09:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn last_line_used_width(s: &str, offset: usize) -> usize {
|
2017-08-01 22:19:20 +09:00
|
|
|
if s.contains('\n') {
|
|
|
|
last_line_width(s)
|
|
|
|
} else {
|
2019-01-15 08:41:09 +09:00
|
|
|
offset + unicode_str_width(s)
|
2017-08-01 22:19:20 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-12 10:30:57 +12:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn trimmed_last_line_width(s: &str) -> usize {
|
2019-01-15 08:41:09 +09:00
|
|
|
unicode_str_width(match s.rfind('\n') {
|
|
|
|
Some(n) => s[(n + 1)..].trim(),
|
|
|
|
None => s.trim(),
|
|
|
|
})
|
2016-04-12 10:30:57 +12:00
|
|
|
}
|
2015-08-14 20:00:22 +12:00
|
|
|
|
2017-06-17 21:11:55 +09:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn last_line_extendable(s: &str) -> bool {
|
2017-10-07 22:17:01 +09:00
|
|
|
if s.ends_with("\"#") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
for c in s.chars().rev() {
|
|
|
|
match c {
|
2018-02-06 09:36:29 +09:00
|
|
|
'(' | ')' | ']' | '}' | '?' | '>' => continue,
|
2017-10-07 22:17:01 +09:00
|
|
|
'\n' => break,
|
|
|
|
_ if c.is_whitespace() => continue,
|
|
|
|
_ => return false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
2017-06-17 21:11:55 +09:00
|
|
|
}
|
|
|
|
|
2015-08-14 20:00:22 +12:00
|
|
|
#[inline]
|
2015-06-23 15:58:58 +02:00
|
|
|
fn is_skip(meta_item: &MetaItem) -> bool {
|
2019-10-05 23:40:24 +09:00
|
|
|
match meta_item.kind {
|
2018-05-14 16:11:55 +12:00
|
|
|
MetaItemKind::Word => {
|
2020-02-08 22:21:37 -06:00
|
|
|
let path_str = pprust::path_to_string(&meta_item.path);
|
2021-07-25 22:57:19 -05:00
|
|
|
path_str == *skip_annotation().as_str() || path_str == *depr_skip_annotation().as_str()
|
2018-05-14 16:11:55 +12:00
|
|
|
}
|
2016-12-23 11:13:00 -08:00
|
|
|
MetaItemKind::List(ref l) => {
|
2020-09-02 10:14:23 -05:00
|
|
|
meta_item.has_name(sym::cfg_attr) && l.len() == 2 && is_skip_nested(&l[1])
|
2016-09-16 15:19:18 +12:00
|
|
|
}
|
2015-06-23 15:58:58 +02:00
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-16 15:19:18 +12:00
|
|
|
#[inline]
|
|
|
|
fn is_skip_nested(meta_item: &NestedMetaItem) -> bool {
|
2019-06-03 23:57:02 +09:00
|
|
|
match meta_item {
|
|
|
|
NestedMetaItem::MetaItem(ref mi) => is_skip(mi),
|
|
|
|
NestedMetaItem::Literal(_) => false,
|
2016-09-16 15:19:18 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-23 15:58:58 +02:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn contains_skip(attrs: &[Attribute]) -> bool {
|
2017-06-16 08:49:49 +09:00
|
|
|
attrs
|
|
|
|
.iter()
|
|
|
|
.any(|a| a.meta().map_or(false, |a| is_skip(&a)))
|
2015-06-23 15:58:58 +02:00
|
|
|
}
|
|
|
|
|
2015-11-17 22:53:06 -06:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn semicolon_for_expr(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool {
|
2020-11-10 20:07:42 -06:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2019-10-05 23:40:24 +09:00
|
|
|
match expr.kind {
|
2017-07-11 22:41:38 +09:00
|
|
|
ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => {
|
|
|
|
context.config.trailing_semicolon()
|
|
|
|
}
|
2015-11-17 22:53:06 -06:00
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn semicolon_for_stmt(context: &RewriteContext<'_>, stmt: &ast::Stmt) -> bool {
|
2019-10-05 23:40:24 +09:00
|
|
|
match stmt.kind {
|
|
|
|
ast::StmtKind::Semi(ref expr) => match expr.kind {
|
2019-07-29 05:52:45 +09:00
|
|
|
ast::ExprKind::While(..) | ast::ExprKind::Loop(..) | ast::ExprKind::ForLoop(..) => {
|
|
|
|
false
|
|
|
|
}
|
2017-07-11 22:41:38 +09:00
|
|
|
ast::ExprKind::Break(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Ret(..) => {
|
|
|
|
context.config.trailing_semicolon()
|
|
|
|
}
|
2017-07-11 21:53:10 +09:00
|
|
|
_ => true,
|
|
|
|
},
|
2016-03-01 17:27:19 -05:00
|
|
|
ast::StmtKind::Expr(..) => false,
|
2015-11-17 22:53:06 -06:00
|
|
|
_ => true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-16 15:19:18 +12:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn stmt_expr(stmt: &ast::Stmt) -> Option<&ast::Expr> {
|
2019-10-05 23:40:24 +09:00
|
|
|
match stmt.kind {
|
2016-09-16 15:19:18 +12:00
|
|
|
ast::StmtKind::Expr(ref expr) => Some(expr),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-15 22:41:56 +09:00
|
|
|
/// 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)
|
|
|
|
}
|
|
|
|
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn count_newlines(input: &str) -> usize {
|
2018-10-13 17:39:19 -03:00
|
|
|
// Using bytes to omit UTF-8 decoding
|
|
|
|
bytecount::count(input.as_bytes(), b'\n')
|
2017-12-05 15:17:40 +09:00
|
|
|
}
|
|
|
|
|
2016-07-26 06:20:01 +01:00
|
|
|
// 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 {
|
2018-03-22 16:09:21 +09:00
|
|
|
($this:ident, $sp:expr) => {
|
2017-06-06 06:54:22 +02:00
|
|
|
$sp.source_callsite()
|
2018-02-04 12:09:03 +00:00
|
|
|
};
|
2017-06-06 06:54:22 +02:00
|
|
|
}
|
|
|
|
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn mk_sp(lo: BytePos, hi: BytePos) -> Span {
|
2021-04-18 14:27:04 +02:00
|
|
|
Span::new(lo, hi, SyntaxContext::root(), None)
|
2016-07-26 06:20:01 +01:00
|
|
|
}
|
2016-04-06 10:04:29 +05:30
|
|
|
|
2021-03-14 13:15:48 -05:00
|
|
|
pub(crate) fn mk_sp_lo_plus_one(lo: BytePos) -> Span {
|
2021-04-18 14:27:04 +02:00
|
|
|
Span::new(lo, lo + BytePos(1), SyntaxContext::root(), None)
|
2021-03-14 13:15:48 -05:00
|
|
|
}
|
|
|
|
|
2019-02-19 02:56:42 +00:00
|
|
|
// Returns `true` if the given span does not intersect with file lines.
|
2017-07-29 12:51:45 +09:00
|
|
|
macro_rules! out_of_file_lines_range {
|
2018-03-22 16:09:21 +09:00
|
|
|
($self:ident, $span:expr) => {
|
2018-10-15 11:52:27 +13:00
|
|
|
!$self.config.file_lines().is_all()
|
|
|
|
&& !$self
|
|
|
|
.config
|
|
|
|
.file_lines()
|
2020-03-26 21:25:34 -05:00
|
|
|
.intersects(&$self.parse_sess.lookup_line_range($span))
|
2018-02-04 12:09:03 +00:00
|
|
|
};
|
2017-07-29 12:51:45 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! skip_out_of_file_lines_range {
|
2018-03-22 16:09:21 +09:00
|
|
|
($self:ident, $span:expr) => {
|
2017-07-29 12:51:45 +09:00
|
|
|
if out_of_file_lines_range!($self, $span) {
|
|
|
|
return None;
|
|
|
|
}
|
2018-02-04 12:09:03 +00:00
|
|
|
};
|
2017-07-29 12:51:45 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! skip_out_of_file_lines_range_visitor {
|
2018-03-22 16:09:21 +09:00
|
|
|
($self:ident, $span:expr) => {
|
2017-07-29 12:51:45 +09:00
|
|
|
if out_of_file_lines_range!($self, $span) {
|
2017-07-29 17:06:50 +02:00
|
|
|
$self.push_rewrite($span, None);
|
2017-07-29 12:51:45 +09:00
|
|
|
return;
|
|
|
|
}
|
2018-02-04 12:09:03 +00:00
|
|
|
};
|
2017-07-29 12:51:45 +09:00
|
|
|
}
|
|
|
|
|
2017-10-07 22:14:33 +09:00
|
|
|
// Wraps String in an Option. Returns Some when the string adheres to the
|
2018-07-07 12:24:09 +02:00
|
|
|
// Rewrite constraints defined for the Rewrite trait and None otherwise.
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn wrap_str(s: String, max_width: usize, shape: Shape) -> Option<String> {
|
2018-08-05 10:50:34 +09:00
|
|
|
if is_valid_str(&filter_normal_code(&s), max_width, shape) {
|
2017-10-07 22:14:33 +09:00
|
|
|
Some(s)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2015-09-04 18:09:05 +02:00
|
|
|
|
2017-10-07 22:14:33 +09:00
|
|
|
fn is_valid_str(snippet: &str, max_width: usize, shape: Shape) -> bool {
|
|
|
|
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.
|
2019-01-15 08:41:09 +09:00
|
|
|
if is_single_line(snippet) {
|
2017-10-07 22:14:33 +09:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// The other lines must fit within the maximum width.
|
2019-01-15 08:41:09 +09:00
|
|
|
if snippet
|
|
|
|
.lines()
|
|
|
|
.skip(1)
|
|
|
|
.any(|line| unicode_str_width(line) > max_width)
|
|
|
|
{
|
2017-10-07 22:14:33 +09:00
|
|
|
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;
|
2015-09-04 18:09:05 +02:00
|
|
|
}
|
|
|
|
}
|
2017-10-07 22:14:33 +09:00
|
|
|
true
|
2015-09-04 18:09:05 +02:00
|
|
|
}
|
|
|
|
|
2017-03-28 23:14:48 +09:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn colon_spaces(config: &Config) -> &'static str {
|
2019-04-03 11:16:54 +02:00
|
|
|
let before = config.space_before_colon();
|
|
|
|
let after = config.space_after_colon();
|
2017-03-28 23:14:48 +09:00
|
|
|
match (before, after) {
|
|
|
|
(true, true) => " : ",
|
|
|
|
(true, false) => " :",
|
|
|
|
(false, true) => ": ",
|
|
|
|
(false, false) => ":",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
chains: prefer to use the next line for an expression, if using the same line would introduce an open block or similar
This problem came to light due to the chains changes, but effects other code too. It only happens rarely, e.g.,
before this fix:
```
match foo {
MacroArgKind::Delimited(ref delim_tok, ref args) => rewrite_delimited_inner(
delim_tok,
args,
).map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs)),
};
```
after:
```
match foo {
MacroArgKind::Delimited(ref delim_tok, ref args) => {
rewrite_delimited_inner(delim_tok, args)
.map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs))
}
}
```
2018-07-11 21:01:40 +12:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
|
2019-10-05 23:40:24 +09:00
|
|
|
match e.kind {
|
2018-04-06 23:09:45 +09:00
|
|
|
ast::ExprKind::Call(ref e, _)
|
2017-11-16 16:42:07 +09:00
|
|
|
| ast::ExprKind::Binary(_, ref e, _)
|
|
|
|
| ast::ExprKind::Cast(ref e, _)
|
|
|
|
| ast::ExprKind::Type(ref e, _)
|
2020-02-08 22:21:37 -06:00
|
|
|
| ast::ExprKind::Assign(ref e, _, _)
|
2017-11-16 16:42:07 +09:00
|
|
|
| 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),
|
2016-04-14 08:36:59 +12:00
|
|
|
_ => e,
|
|
|
|
}
|
|
|
|
}
|
2017-08-15 16:49:02 +09:00
|
|
|
|
chains: prefer to use the next line for an expression, if using the same line would introduce an open block or similar
This problem came to light due to the chains changes, but effects other code too. It only happens rarely, e.g.,
before this fix:
```
match foo {
MacroArgKind::Delimited(ref delim_tok, ref args) => rewrite_delimited_inner(
delim_tok,
args,
).map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs)),
};
```
after:
```
match foo {
MacroArgKind::Delimited(ref delim_tok, ref args) => {
rewrite_delimited_inner(delim_tok, args)
.map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs))
}
}
```
2018-07-11 21:01:40 +12:00
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn starts_with_newline(s: &str) -> bool {
|
2017-10-05 00:23:17 +09:00
|
|
|
s.starts_with('\n') || s.starts_with("\r\n")
|
|
|
|
}
|
chains: prefer to use the next line for an expression, if using the same line would introduce an open block or similar
This problem came to light due to the chains changes, but effects other code too. It only happens rarely, e.g.,
before this fix:
```
match foo {
MacroArgKind::Delimited(ref delim_tok, ref args) => rewrite_delimited_inner(
delim_tok,
args,
).map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs)),
};
```
after:
```
match foo {
MacroArgKind::Delimited(ref delim_tok, ref args) => {
rewrite_delimited_inner(delim_tok, args)
.map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs))
}
}
```
2018-07-11 21:01:40 +12:00
|
|
|
|
|
|
|
#[inline]
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn first_line_ends_with(s: &str, c: char) -> bool {
|
chains: prefer to use the next line for an expression, if using the same line would introduce an open block or similar
This problem came to light due to the chains changes, but effects other code too. It only happens rarely, e.g.,
before this fix:
```
match foo {
MacroArgKind::Delimited(ref delim_tok, ref args) => rewrite_delimited_inner(
delim_tok,
args,
).map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs)),
};
```
after:
```
match foo {
MacroArgKind::Delimited(ref delim_tok, ref args) => {
rewrite_delimited_inner(delim_tok, args)
.map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs))
}
}
```
2018-07-11 21:01:40 +12:00
|
|
|
s.lines().next().map_or(false, |l| l.ends_with(c))
|
|
|
|
}
|
2018-10-15 14:06:52 +13:00
|
|
|
|
|
|
|
// States whether an expression's last line exclusively consists of closing
|
|
|
|
// parens, braces, and brackets in its idiomatic formatting.
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr: &str) -> bool {
|
2019-10-05 23:40:24 +09:00
|
|
|
match expr.kind {
|
2020-03-26 23:18:16 -05:00
|
|
|
ast::ExprKind::MacCall(..)
|
2018-10-15 14:06:52 +13:00
|
|
|
| ast::ExprKind::Call(..)
|
|
|
|
| ast::ExprKind::MethodCall(..)
|
|
|
|
| ast::ExprKind::Array(..)
|
|
|
|
| ast::ExprKind::Struct(..)
|
|
|
|
| ast::ExprKind::While(..)
|
|
|
|
| ast::ExprKind::If(..)
|
|
|
|
| ast::ExprKind::Block(..)
|
2020-10-19 20:26:56 -05:00
|
|
|
| ast::ExprKind::ConstBlock(..)
|
2019-09-10 19:55:18 -05:00
|
|
|
| ast::ExprKind::Async(..)
|
2018-10-15 14:06:52 +13:00
|
|
|
| ast::ExprKind::Loop(..)
|
|
|
|
| ast::ExprKind::ForLoop(..)
|
2019-09-10 19:55:18 -05:00
|
|
|
| ast::ExprKind::TryBlock(..)
|
2018-10-15 14:06:52 +13:00
|
|
|
| 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::Closure(_, _, _, _, ref expr, _)
|
|
|
|
| ast::ExprKind::Try(ref expr)
|
|
|
|
| ast::ExprKind::Yield(Some(ref expr)) => is_block_expr(context, expr, repr),
|
|
|
|
// This can only be a string lit
|
|
|
|
ast::ExprKind::Lit(_) => {
|
|
|
|
repr.contains('\n') && trimmed_last_line_width(repr) <= context.config.tab_spaces()
|
|
|
|
}
|
2019-09-10 19:55:18 -05:00
|
|
|
ast::ExprKind::AddrOf(..)
|
|
|
|
| ast::ExprKind::Assign(..)
|
|
|
|
| ast::ExprKind::AssignOp(..)
|
|
|
|
| ast::ExprKind::Await(..)
|
|
|
|
| ast::ExprKind::Box(..)
|
|
|
|
| ast::ExprKind::Break(..)
|
|
|
|
| ast::ExprKind::Cast(..)
|
|
|
|
| ast::ExprKind::Continue(..)
|
|
|
|
| ast::ExprKind::Err
|
|
|
|
| ast::ExprKind::Field(..)
|
2020-06-11 13:22:37 -05:00
|
|
|
| ast::ExprKind::InlineAsm(..)
|
2020-03-30 12:02:39 -05:00
|
|
|
| ast::ExprKind::LlvmInlineAsm(..)
|
2019-09-10 19:55:18 -05:00
|
|
|
| ast::ExprKind::Let(..)
|
|
|
|
| ast::ExprKind::Path(..)
|
|
|
|
| ast::ExprKind::Range(..)
|
|
|
|
| ast::ExprKind::Repeat(..)
|
|
|
|
| ast::ExprKind::Ret(..)
|
|
|
|
| ast::ExprKind::Tup(..)
|
|
|
|
| ast::ExprKind::Type(..)
|
2020-11-27 21:18:39 -06:00
|
|
|
| ast::ExprKind::Yield(None)
|
|
|
|
| ast::ExprKind::Underscore => false,
|
2018-10-15 14:06:52 +13:00
|
|
|
}
|
|
|
|
}
|
2018-10-18 11:45:55 +13:00
|
|
|
|
2019-02-19 02:56:42 +00:00
|
|
|
/// Removes trailing spaces from the specified snippet. We do not remove spaces
|
2018-10-18 11:45:55 +13:00
|
|
|
/// inside strings or comments.
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn remove_trailing_white_spaces(text: &str) -> String {
|
2018-10-18 11:45:55 +13:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-10-30 01:09:09 +01:00
|
|
|
/// Indent each line according to the specified `indent`.
|
|
|
|
/// e.g.
|
|
|
|
///
|
2019-07-16 19:36:23 +09:00
|
|
|
/// ```rust,compile_fail
|
2018-10-30 01:09:09 +01:00
|
|
|
/// foo!{
|
|
|
|
/// x,
|
|
|
|
/// y,
|
|
|
|
/// foo(
|
|
|
|
/// a,
|
|
|
|
/// b,
|
|
|
|
/// c,
|
|
|
|
/// ),
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// will become
|
|
|
|
///
|
2019-07-16 19:36:23 +09:00
|
|
|
/// ```rust,compile_fail
|
2018-10-30 01:09:09 +01:00
|
|
|
/// foo!{
|
|
|
|
/// x,
|
|
|
|
/// y,
|
|
|
|
/// foo(
|
|
|
|
/// a,
|
|
|
|
/// b,
|
|
|
|
/// c,
|
|
|
|
/// ),
|
|
|
|
/// }
|
|
|
|
/// ```
|
2019-05-09 21:13:32 +02:00
|
|
|
pub(crate) fn trim_left_preserve_layout(
|
|
|
|
orig: &str,
|
|
|
|
indent: Indent,
|
|
|
|
config: &Config,
|
|
|
|
) -> Option<String> {
|
2018-10-30 01:09:09 +01:00
|
|
|
let mut lines = LineClasses::new(orig);
|
2018-12-18 03:21:31 +01:00
|
|
|
let first_line = lines.next().map(|(_, s)| s.trim_end().to_owned())?;
|
2018-10-30 01:09:09 +01:00
|
|
|
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
|
2018-10-18 22:58:45 +02:00
|
|
|
} else {
|
2018-10-30 01:09:09 +01:00
|
|
|
Some(get_prefix_space_width(config, &line))
|
|
|
|
};
|
|
|
|
|
2019-02-07 00:05:05 +01:00
|
|
|
// just InString{Commented} in order to allow the start of a string to be indented
|
|
|
|
let new_veto_trim_value = (kind == FullCodeCharKind::InString
|
|
|
|
|| (config.version() == Version::Two
|
|
|
|
&& kind == FullCodeCharKind::InStringCommented))
|
2019-01-16 23:06:28 +01:00
|
|
|
&& !line.ends_with('\\');
|
2019-01-10 14:25:07 +01:00
|
|
|
let line = if veto_trim || new_veto_trim_value {
|
|
|
|
veto_trim = new_veto_trim_value;
|
2018-10-30 01:09:09 +01:00
|
|
|
trimmed = false;
|
|
|
|
line
|
|
|
|
} else {
|
|
|
|
line.trim().to_owned()
|
|
|
|
};
|
|
|
|
trimmed_lines.push((trimmed, line, prefix_space_width));
|
|
|
|
|
2019-01-10 14:25:07 +01:00
|
|
|
// 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.
|
2018-10-30 01:09:09 +01:00
|
|
|
match kind {
|
2019-01-16 23:06:28 +01:00
|
|
|
FullCodeCharKind::InStringCommented | FullCodeCharKind::EndStringCommented
|
|
|
|
if config.version() == Version::Two =>
|
|
|
|
{
|
|
|
|
None
|
|
|
|
}
|
2018-10-30 01:09:09 +01:00
|
|
|
FullCodeCharKind::InString | FullCodeCharKind::EndString => None,
|
|
|
|
_ => prefix_space_width,
|
2018-10-18 22:58:45 +02:00
|
|
|
}
|
|
|
|
})
|
2018-10-30 01:09:09 +01:00
|
|
|
.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"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-02-07 00:05:05 +01:00
|
|
|
/// 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.
|
2019-10-19 17:19:47 +08:00
|
|
|
pub(crate) fn indent_next_line(kind: FullCodeCharKind, _line: &str, config: &Config) -> bool {
|
2019-02-07 00:05:05 +01:00
|
|
|
!(kind.is_string() || (config.version() == Version::Two && kind.is_commented_string()))
|
|
|
|
}
|
|
|
|
|
2019-05-09 20:37:51 +02:00
|
|
|
pub(crate) fn is_empty_line(s: &str) -> bool {
|
2018-10-30 01:09:09 +01:00
|
|
|
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
|
2018-10-18 22:58:45 +02:00
|
|
|
}
|
|
|
|
|
2018-12-08 14:39:52 +09:00
|
|
|
pub(crate) trait NodeIdExt {
|
|
|
|
fn root() -> Self;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NodeIdExt for NodeId {
|
|
|
|
fn root() -> NodeId {
|
2021-06-25 20:43:04 +02:00
|
|
|
NodeId::placeholder_from_expn_id(LocalExpnId::ROOT)
|
2018-12-08 14:39:52 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-15 08:41:09 +09:00
|
|
|
pub(crate) fn unicode_str_width(s: &str) -> usize {
|
|
|
|
s.width()
|
|
|
|
}
|
|
|
|
|
2018-10-30 01:09:09 +01:00
|
|
|
#[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!(
|
2018-11-07 01:49:53 -07:00
|
|
|
trim_left_preserve_layout(&s, indent, &config),
|
2018-10-30 01:09:09 +01:00
|
|
|
Some("aaa\n bbb\n ccc".to_string())
|
|
|
|
);
|
|
|
|
}
|
2018-10-18 11:45:55 +13:00
|
|
|
}
|