1 个稳定版本
1.60.0 | 2022年2月2日 |
---|
#8 在 #rustfmt
220KB
6K SLoC
rsfmt ---- Rust 源代码格式化工具
https://github.com/zBaitu/rsfmt
从旧仓库重新激活: rfmt
概述
rsfmt 是一个 Rust 源代码格式化工具。是的,已经有官方工具 rustfmt。那么为什么还要写一个呢?
- rustfmt 对于可配置性很好,但还有一些风格我不喜欢。
- 编写一个 Rust 代码格式化工具可以让我更深入地学习 Rust,例如 Rust 的 AST。
- 为了乐趣 :)
版本
支持 Rust 1.60 nightly
安装,构建
- 安装
cargo install rsfmt
- 构建
git clone [email protected]:zBaitu/rsfmt.git
cargo build --release
使用方法
rsfmt 1.60.0
USAGE:
rsfmt [FLAGS] [input]
FLAGS:
-a, --ast Prints the rust original syntax ast debug info
-c, --check Only check without output or overwrite
-d, --debug Prints the rsfmt ir debug info
-h, --help Prints help information
-k, --keep Keep going when error occurred
-o, --overwrite Overwrite the source file
-p, --print Prints the rsfmt ir simple format
-V, --version Prints version information
ARGS:
<input> Input file or dir. If `input` is a dir, rsfmt will do action for all files in this dir recursively.
If neither `options` nor `input` is specified, rsfmt will format source code from stdin
在您的编辑器中运行 rsfmt
实际上,我现在只使用 rsfmt 与 IntelliJ。只需添加以下外部工具。
其他编辑器的使用方法可以参考 在您的编辑器中运行 Rustfmt。我认为可能只需将 rustfmt
替换为 rsfmt
。
特性
与 rustfmt 相比,rsfmt 有一些主要的不同特性
- 保留用户输入的换行。
- 不同的对齐策略。
- 将
crate
、use
、mod
、attributes
分组并排序。 - 不 格式化
doc
、comment
、string。您可以使用 check 函数显示超出行数和尾随空白行。
- 提供检查、目录递归、AST 导出、调试。
- 夜间功能。
以下部分将详细介绍这些功能,并展示一些来自 rustfmt 的问题。
保留用户输入的换行
对于问题: rustfmt 重新格式化位操作。
fn main() {
let (a, b, c, d) = (0, 0, 0, 0);
let _ = u32::from_be(((a as u32) << 24) |
((b as u32) << 16) |
((c as u32) << 8) |
(d as u32) << 0);
}
- rustfmt
fn main() {
let (a, b, c, d) = (0, 0, 0, 0);
let _ =
u32::from_be(((a as u32) << 24) | ((b as u32) << 16) | ((c as u32) << 8) | (d as u32) << 0);
}
当然,您可以使用 #[rustfmt_skip]
来避免此类代码,但在我看来,我真的不喜欢为了源代码格式化工具而添加其他代码。
- rsfmt
fn main() {
let (a, b, c, d) = (0, 0, 0, 0);
let _ = u32::from_be(((a as u32) << 24) |
((b as u32) << 16) |
((c as u32) << 8) |
(d as u32) << 0);
}
看起来不错,不是吗?为什么rsfmt可以保持用户的缩进?这是因为rsfmt ir。Rust AST的自定义ir尽可能地记录每个元素的记录位置信息。看看另一个例子
fn main() {
let ref_packet = [0xde, 0xf0, 0x12, 0x34, 0x45, 0x67,
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
0x86, 0xdd];
}
- rustfmt
fn main() {
let ref_packet = [
0xde, 0xf0, 0x12, 0x34, 0x45, 0x67, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x86, 0xdd,
];
}
- rsfmt
fn main() {
let ref_packet = [0xde, 0xf0, 0x12, 0x34, 0x45, 0x67,
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
0x86, 0xdd];
}
不同的对齐策略
我更喜欢尽可能地把参数放在一行。
fn main() {
f(123456789, "abcdefg", "hijklmn", 0987654321, "opqrst", "uvwxyz", 123456789, "abcdefg", "hijklmn", 0987654321, "opqrst", "uvwxyz");
}
- rustfmt
fn main() {
f(
123456789, "abcdefg", "hijklmn", 0987654321, "opqrst", "uvwxyz", 123456789, "abcdefg",
"hijklmn", 0987654321, "opqrst", "uvwxyz",
);
}
- rsfmt
fn main() {
f(123456789, "abcdefg", "hijklmn", 0987654321, "opqrst", "uvwxyz", 123456789, "abcdefg", "hijklmn", 0987654321,
"opqrst", "uvwxyz");
}
如果左对齐位置超出限制(现在为40),rsfmt更倾向于使用双重缩进进行函数调用对齐。我认为rsfmt使源代码向左倾斜,而rustfmt则向右倾斜。一个现有问题:[rustfmt应避免向右漂移大块代码](https://github.com/rust-lang-nursery/rustfmt/issues/439)
fn main() {
let mut arms = variants.iter().enumerate().map(|(i, &(ident, v_span, ref summary))| {
let i_expr = cx.expr_usize(v_span, i);
let pat = cx.pat_lit(v_span, i_expr);
let path = cx.path(v_span, vec![substr.type_ident, ident]);
let thing = rand_thing(cx, v_span, path, summary, |cx, sp| rand_call(cx, sp));
cx.arm(v_span, vec![ pat ], thing)
}).collect::<Vec<ast::Arm> >();
}
- rustfmt
fn main() {
let mut arms = variants
.iter()
.enumerate()
.map(|(i, &(ident, v_span, ref summary))| {
let i_expr = cx.expr_usize(v_span, i);
let pat = cx.pat_lit(v_span, i_expr);
let path = cx.path(v_span, vec![substr.type_ident, ident]);
let thing = rand_thing(cx, v_span, path, summary, |cx, sp| rand_call(cx, sp));
cx.arm(v_span, vec![pat], thing)
})
.collect::<Vec<ast::Arm>>();
}
- rsfmt
fn main() {
let mut arms = variants.iter().enumerate().map(|(i, &(ident, v_span, ref summary))| {
let i_expr = cx.expr_usize(v_span, i);
let pat = cx.pat_lit(v_span, i_expr);
let path = cx.path(v_span, vec![substr.type_ident, ident]);
let thing = rand_thing(cx, v_span, path, summary, |cx, sp| rand_call(cx, sp));
cx.arm(v_span, vec![pat], thing)
}).collect::<Vec<ast::Arm>>();
}
由于此源代码符合rsfmt的代码风格,因此rsfmt的结果没有改变。
将crate
、use
、mod
、attributes
分组并排序
#![feature(custom_derive)]
#![deny(warnings)]
#![feature(question_mark)]
#![feature(iter_arith)]
#![feature(rustc_private)]
extern crate rst;
extern crate getopts;
extern crate walkdir;
use std::env;
use getopts::Options;
#[macro_use]
mod ts;
mod ir;
mod ft;
mod tr;
mod rsfmt;
- rsfmt
#![deny(warnings)]
#![feature(custom_derive)]
#![feature(iter_arith)]
#![feature(question_mark)]
#![feature(rustc_private)]
extern crate getopts;
extern crate rst;
extern crate walkdir;
use getopts::Options;
use std::env;
#[macro_use]
mod ts;
mod ft;
mod ir;
mod rsfmt;
mod tr;
rsfmt仅对连续出现的项目进行分组。如果某个项目是特殊的,必须保持其顺序,例如mod ts;
,则将其与其他项目分开。
不要格式化doc
、comment
、string
关于rustfmt的doc、comment、string、raw string有很多问题。我认为这些元素可以由用户自由编写,任何他们想要的格式。
提供检查、目录递归、AST转储
如果您想检查是否有某些行中断了代码风格限制,rsfmt提供了检查功能。
// aaaaa
// bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
fn main() {
let a = r#"aaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbb"#;
}
rsfmt -c g.rs
"g.rs"
trailing_ws_lines: {1, 4}
您可以对目录中的所有文件进行检查或覆盖。
rsfmt -c rust/src/libcore
rsfmt -o rust/src/libstd
您可能想查看源代码的Rust AST。
// AST
fn main() {}
rsfmt -a a.rs
Crate {
attrs: [],
items: [
Item {
attrs: [],
id: NodeId(4294967040),
span: Span {
lo: BytePos(
7,
),
hi: BytePos(
19,
),
ctxt: #0,
},
vis: Visibility {
kind: Inherited,
span: Span {
lo: BytePos(
7,
),
hi: BytePos(
7,
),
ctxt: #0,
},
tokens: None,
},
ident: main#0,
kind: Fn(
FnKind(
Final,
FnSig {
header: FnHeader {
unsafety: No,
asyncness: No,
constness: No,
ext: None,
},
decl: FnDecl {
inputs: [],
output: Default(
Span {
lo: BytePos(
17,
),
hi: BytePos(
17,
),
ctxt: #0,
},
),
},
span: Span {
lo: BytePos(
7,
),
hi: BytePos(
16,
),
ctxt: #0,
},
},
Generics {
params: [],
where_clause: WhereClause {
has_where_token: false,
predicates: [],
span: Span {
lo: BytePos(
16,
),
hi: BytePos(
16,
),
ctxt: #0,
},
},
span: Span {
lo: BytePos(
14,
),
hi: BytePos(
14,
),
ctxt: #0,
},
},
Some(
Block {
stmts: [],
id: NodeId(4294967040),
rules: Default,
span: Span {
lo: BytePos(
17,
),
hi: BytePos(
19,
),
ctxt: #0,
},
tokens: None,
},
),
),
),
tokens: None,
},
],
span: Span {
lo: BytePos(
7,
),
hi: BytePos(
19,
),
ctxt: #0,
},
proc_macros: [],
}
------------------------------------------------------------------------------------------------------------------------
0: Isolated [
"// AST",
]
缺点
由于rsfmt是我为日常开发编写的个人工具(玩具),因此它目前缺少一些常见功能。
- 无配置
rustfmt提供了许多配置选项,但rsfmt没有提供。代码风格就像食物,每个人都有自己的口味。尽管rustfmt现在有很多配置选项,但仍有新的配置需要通过问题开放。如果rsfmt的大部分风格适合您的口味,您可以通过克隆并进行一些小修改来克隆和修改,例如LF、max width、indent。 - 仅支持某些类型的注释
注释可以出现在源代码的任何位置,由于注释信息不存在于AST节点上,因此支持所有类型的注释是困难的。另一方面,我认为一些复杂的注释并不真正需要。以下带有注释的源代码,其中消失的注释表示它们不受rsfmt支持。
// aaaaa
// bbbbb
struct A { // ccccc-DISAPPEARED
// ddddd
a: bool, // eeeee
b: i32, // ffff
// ggggg
} // hhhhh
// iiiii
fn f(a: bool, /* jjjjj-DISAPPEARED */ b: i32, /* kkkkk-DISAPPEARED */) -> bool { // lllll-DISAPPEARED
// mmmmm
const b: bool = false; // nnnnn
let mut a = true; // ooooo
a = false; // ppppp
a!();// qqqqq
a // rrrrr
} // sssss
// ttttt
// uuuuu
- rsfmt
// aaaaa
// bbbbb
struct A {
// ddddd
a: bool, // eeeee
b: i32, // ffff
// ggggg
} // hhhhh
// iiiii
fn f(a: bool, b: i32) -> bool {
// mmmmm
const b: bool = false; // nnnnn
let mut a = true; // ooooo
a = false; // ppppp
a!(); // qqqqq
a // rrrrr
} // sssss
// ttttt
// uuuuu
依赖项
~12–22MB
~360K SLoC