1个不稳定版本
使用旧Rust 2015
0.1.0 | 2016年4月25日 |
---|
#5 在 #comment
190KB
5.5K SLoC
rFmt ---- Rust源代码格式化工具
https://github.com/zBaitu/rfmt
概览
rfmt是一个Rust源代码格式化工具。是的,已经有来自Rust Nursery的官方工具rustfmt。那么为什么还要写另一个呢?
- 虽然rustfmt很棒,但还有一些我不喜欢的风格。
- 为Rust编写代码格式化工具可以使我更深入地学习Rust,例如Rust的AST。
- 为了乐趣: )
安装、构建
- 安装
cargo install rfmt
- 构建
git clone [email protected]:zBaitu/rfmt.git
cargo build --release
用法
Usage: rfmt [options] [path]
If `path` is a dir, rfmt will do action for all files in this dir recursively.
If `path` is not specified, use the current dir by default.
If neither `options` nor `path` is specified, rfmt will format source code from stdin.
Options:
-a, --ast print the rust original syntax ast debug info
-c, --check check exceed lines and trailing white space lines
-d, --debug print the rfmt ir debug info
-o, --overwrite overwrite the source file
-v, --version show version
-h, --help show help
从您的编辑器运行rfmt(从rustfmt复制)
- Vim
- Emacs
- Sublime Text 3
- Atom
- Visual Studio Code使用RustyCode或vsc-rustfmt
实际上,我现在只使用rfmt进行Vim。我没有测试其他编辑器。这只是将rustfmt
替换为rfmt
。例如,Vim
let g:formatdef_rfmt = '"rfmt"'
let g:formatters_rust = ['rfmt']
功能
与rustfmt相比,rfmt有一些主要的不同功能
- 不要解析子模块。
- 保留用户输入的换行。
- 不同的对齐策略。
- 将
crate
、use
、mod
、attributes
分组并排序。 - 不要格式化
doc
、comment
、string
。您可以使用check
函数来显示超过行数和尾随空白行。 - 提供检查、递归目录、AST转储。
- 夜间功能,如
expr?
、default fn
。
以下部分将详细介绍这些功能,并展示一些来自rustfmt的现有问题。
不要解析子模块
当您在编辑器中编辑以下源代码并使用rustfmt进行格式化时会发生什么。
// lib.rs
pub mod a;
pub mod b;
pub mod c;
pub mod d;
...
它会解析所有子模块,这是Rust解析器的默认操作。但实际上,在大多数情况下,我只想格式化我现在正在编辑的文件。
rfmt 使用自定义的 Rust 解析器,rSyntax,它从 Rust 的 libsyntax 中克隆而来。rSyntax 和 Rust libsyntax 之间的主要区别在于,rSyntax 跳过了子模块解析。因此,rfmt 可以在编辑器场景中快速格式化。如果您想格式化项目中的所有源代码,只需指定项目目录作为 rfmt 命令参数即可。
rfmt project_dir
保持用户输入的换行
关于问题: 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]
来避免此类代码,但就我个人而言,我真的很不喜欢为了源代码格式化工具而添加其他代码。
- rfmt
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);
}
看起来不错,不是吗?为什么 rfmt 可以保持用户换行?因为 rfmt 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];
}
- rfmt
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");
}
- rfmt
fn main() {
f(123456789, "abcdefg", "hijklmn", 0987654321, "opqrst", "uvwxyz", 123456789, "abcdefg",
"hijklmn", 0987654321, "opqrst", "uvwxyz");
}
我更喜欢尽可能将参数放在同一行。这只是我个人的喜好。但还有一种情况,我认为它看起来真的很糟糕。
fn main() {
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(123456789, "abcdefg", "hijklmn", 0987654321, "opqrst", "uvwxyz", 123456789, "abcdefg", "hijklmn", 0987654321, "opqrst", "uvwxyz");
}
- rustfmt
fn main() {
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(123456789,
"abcdefg",
"hijklmn",
0987654321,
"opqrst",
"uvwxyz",
123456789,
"abcdefg",
"hijklmn",
0987654321,
"opqrst",
"uvwxyz");
}
- rfmt
fn main() {
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(123456789,
"abcdefg", "hijklmn", 0987654321, "opqrst", "uvwxyz", 123456789, "abcdefg",
"hijklmn", 0987654321, "opqrst", "uvwxyz");
}
如果左对齐位置超出限制(目前是 50),rfmt 更倾向于使用双缩进对齐到函数调用。rfmt 使源代码向左倾斜,而我认为 rustfmt 是向右倾斜。一个现有的问题: 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> >();
}
- 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>>();
}
- rfmt
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>>();
}
rfmt 的结果没有变化,因为这段源代码符合 rfmt 的代码风格。
将 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 rfmt;
- rfmt
#![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 rfmt;
mod tr;
rfmt 只组合连续出现的项。如果某个项特别特殊,必须保持其顺序,例如 mod ts;
,则将其与其他项分开。
不要 格式化 doc
、comment
、string
关于 doc、comment、string、raw string 的 rustfmt 有许多问题。我认为这样的元素可以由用户自由地编写任何内容,任何他们想要的格式。
提供检查、递归目录、ast 溢出
如果您想检查是否有某些行超出了代码风格限制,rfmt 提供了检查功能。
// aaaaa
// bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
fn main() {
let a = r#"aaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbb"#;
}
rfmt -c main.rs
a.rs
exceed_lines: {2}
trailing_ws_lines: {1, 4}
----------------------------------------
您可以对目录中的所有文件进行检查或覆盖。
rfmt -c rust/src/libcore
rfmt -o rust/src/libstd
也许您想看看源代码的 Rust AST。
// AST
fn main() {}
rfmt -a a.rs
Crate {
module: Mod {
inner: Span { lo: BytePos(7), hi: BytePos(19), expn_id: ExpnId(4294967295) },
items: [
Item {
ident: main#0,
attrs: [],
id: 4294967295,
node: Fn(
FnDecl {
......
}
}
}
]
},
attrs: [],
config: [],
span: Span { lo: BytePos(7), hi: BytePos(18), expn_id: ExpnId(4294967295) },
exported_macros: []
}
----------------------------------------
0: Isolated [
"// AST"
]
----------------------------------------
夜间功能,如 expr?
、default fn
rSyntax 是从 Rust 夜间(1.10.0-nightly)克隆的,因此它支持最新的语言功能。
struct A;
impl A {
default fn f() -> bool { true }
}
fn f() -> Result<bool, String> { Ok() }
fn ff() -> Result<bool, String> {
f()?
}
fn main() {
ff();
}
- rfmt
struct A;
impl A {
default fn f() -> bool {
true
}
}
fn f() -> Result<bool, String> {
Ok()
}
fn ff() -> Result<bool, String> {
f()?
}
fn main() {
ff();
}
缺点
由于 rfmt 是作为我日常开发的个人工具(玩具)编写的,它目前缺少一些常用功能。
- 没有配置
rustfmt 提供了大量的配置选项,但 rfmt 没有提供。代码风格就像食物一样,每个人都有自己的口味。尽管 rustfmt 现在有大量的配置选项,但仍然有一些新的配置需要在问题中打开。如果 rfmt 的样式的大部分适合您的口味,您可以将它克隆并做一些小的修改,例如 LF、最大宽度、缩进。 - 仅支持某些类型的注释
注释可以出现在源代码的任何地方,支持所有类型的注释可能有些困难,因为注释信息不存在于抽象语法树(AST)节点上。另一方面,我认为一些复杂的注释并不是真的需要。以下带有注释的源代码,其中消失的注释表示目前rfmt不支持。
// 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
- rfmt
// aaaaa
// bbbbb
struct A {
// ddddd
a: bool, // eeeee
b: i32, // ffff
} // 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
依赖项
约2MB
约36K SLoC