#formatter #rustfmt #source #ast #comments #style #user

nightly app rfmt

另一款Rust源代码格式化工具

1个不稳定版本

使用旧Rust 2015

0.1.0 2016年4月25日

#5#comment

Apache-2.0/MIT

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复制)

实际上,我现在只使用rfmt进行Vim。我没有测试其他编辑器。这只是将rustfmt替换为rfmt。例如,Vim

let g:formatdef_rfmt = '"rfmt"'
let g:formatters_rust = ['rfmt']

功能

rustfmt相比,rfmt有一些主要的不同功能

  • 不要解析子模块。
  • 保留用户输入的换行。
  • 不同的对齐策略。
  • crateusemodattributes分组并排序。
  • 不要格式化doccommentstring。您可以使用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 的代码风格。

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

不要 格式化 doccommentstring

关于 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