#formatter #source #rustfmt #style #configurable #write #line

app rsfmt

又一个 Rust 源代码格式化工具

1 个稳定版本

1.60.0 2022年2月2日

#8#rustfmt

MIT/Apache

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。只需添加以下外部工具。 IntelliJ 外部工具 rsfmt

其他编辑器的使用方法可以参考 在您的编辑器中运行 Rustfmt。我认为可能只需将 rustfmt 替换为 rsfmt

特性

rustfmt 相比,rsfmt 有一些主要的不同特性

  • 保留用户输入的换行。
  • 不同的对齐策略。
  • crateusemodattributes 分组并排序。
  • 格式化 doccommentstring。您可以使用 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的结果没有改变。

crateusemodattributes分组并排序

#![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;,则将其与其他项目分开。

不要格式化doccommentstring

关于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的大部分风格适合您的口味,您可以通过克隆并进行一些小修改来克隆和修改,例如LFmax widthindent
  • 仅支持某些类型的注释
    注释可以出现在源代码的任何位置,由于注释信息不存在于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