#formatter #linter #parser

biome_js_formatter

Biome的JavaScript格式化工具

6个版本 (破坏性更新)

0.5.7 2024年3月12日
0.4.0 2024年1月9日
0.3.1 2023年11月26日
0.1.0 2023年9月28日
0.0.2 2023年9月28日

#1512 in 开发工具

Download history 116/week @ 2024-04-06 60/week @ 2024-04-13 33/week @ 2024-04-20 55/week @ 2024-04-27 46/week @ 2024-05-04 60/week @ 2024-05-11 152/week @ 2024-05-18 244/week @ 2024-05-25 262/week @ 2024-06-01 155/week @ 2024-06-08 38/week @ 2024-06-15 39/week @ 2024-06-22 31/week @ 2024-06-29 51/week @ 2024-07-06 152/week @ 2024-07-13 2218/week @ 2024-07-20

每月下载量 2,455
用于 dprint-plugin-biome

MIT/Apache

6MB
140K SLoC

Biome - Toolchain of the web

Discord chat cargo version

biome_js_formatter

Biome的JavaScript格式化工具实现。请参考文档


lib.rs:

Biome的官方JavaScript格式化工具。

实现格式化工具

我们的格式化工具基于node。这意味着每个AST节点都知道如何格式化自己。为了实现格式化,节点必须实现FormatNode特质。

biome具有自动代码生成功能,可以自动从语法中创建文件。默认情况下,所有实现都将进行逐字格式化,这意味着格式化工具将按照原样打印标记和 trivia(format_verbatim)。

我们的格式化工具有自己的内部IR,它从AST中创建自己的抽象。

开发者不会直接创建这个IR,但他们将使用一系列工具,这些工具将帮助创建这个IR。整个IR由enum FormatElement表示。

最佳实践

  1. 使用*Fields结构提取所有标记/节点

    #[derive(Debug, Clone, Default)]
    pub struct FormatJsExportDefaultExpressionClause;
    
    impl FormatNodeRule<JsExportDefaultExpressionClause> for FormatJsExportDefaultExpressionClauses {
        fn fmt_fields(&self, node: &JsExportDefaultExpressionClause, f: &mut JsFormatter) -> FormatResult<()> {
            let JsExportDefaultExpressionClauseFields {
                default_token,
                expression,
                semicolon_token,
            }  = node.as_fields();
       }
    }
    
  2. 当使用.as_fields()进行解构时,不要使用..功能。优先提取所有字段并使用_忽略它们

    #[derive(Debug, Clone, Default)]
    pub struct FormatJsExportDefaultExpressionClause;
    
    impl FormatNodeRule<JsExportDefaultExpressionClause> for FormatJsExportDefaultExpressionClauses {
        fn fmt_fields(&self, node: &JsExportDefaultExpressionClause, f: &mut JsFormatter) -> FormatResult<()> {
             let JsExportDefaultExpressionClauseFields {
                 default_token,
                 expression: _,
                 semicolon_token
             } = node.as_fields();
         }
    }
    

    我们推广这种模式的原因是我们希望明确表示某个标记/节点被排除;

  3. 使用builders.rsformatterformat_extensions.rs提供的API。

    1. builders.rs公开了一系列工具来构建格式化IR;请参阅其内部文档了解这些工具的用途;
    2. formatter 提供了一组函数来帮助格式化一些常见的模式;请参阅它们的内部文档以了解如何使用它们以及何时使用;
    3. format_extensions.rs:通过这些特质,我们赋予节点和标记根据其类型实现某些方法的能力。如果您有一个良好的 IDE 支持,这个功能将帮助您。例如
    #[derive(Debug, Clone, Default)]
    pub struct FormatJsExportDefaultExpressionClause;
    
    impl FormatNodeRule<JsExportDefaultExpressionClause> for FormatJsExportDefaultExpressionClauses{
         fn fmt_fields(&self, node: &JsExportDefaultExpressionClause, f: &mut JsFormatter) -> FormatResult<()> {
             let JsExportDefaultExpressionClauseFields {
                 default_token,
                 expression, // it's a mandatory node
                 semicolon_token, // this is not a mandatory node
             } = node.as_fields();
             let element = expression.format();
    
             if let Some(expression) = &expression? {
                 write!(f, [expression.format(), space()])?;
             }
    
             if let Some(semicolon) = &semicolon_token {
                 write!(f, [semicolon.format()])?;
             } else {
                 write!(f, [space()])?;
             }
         }
    }
    
  4. 使用 playground 来检查您想要格式化的代码。这有助于您了解需要实现/修改哪些节点才能实现格式化。或者,您可以根据以下说明在本地运行 playground:playground 指令

  5. 使用 tests/ 目录中的 quick_test.rs 文件来测试您的代码片段,而无需运行整个测试套件。这个测试是故意忽略的,所以您不必担心 CI 会中断。

测试

我们使用 insta.rs 进行快照测试,请确保您阅读了它的文档以了解快照测试的基础。您应该安装辅助命令 cargo-insta 以帮助进行快照审查。

目录按语言划分,因此在创建新的测试文件时,请确保将正确的文件放在正确的文件夹下

  • JavaScript => js/ 目录
  • TypeScript => ts/ 目录
  • JSX => jsx/ 目录
  • TSX => ts/ 目录

要为 JavaScript 创建新的快照测试,请创建一个新的文件到 crates/biome_js_formatter/tests/specs/js/,例如 arrow_with_spaces..js

const foo     = ()    => {
    return bar
}

作为模块处理的文件必须放在 module/ 目录中,作为脚本处理的文件必须放在 script/ 目录中。

运行以下命令以生成新的快照(快照测试是通过过程宏生成的,因此我们需要重新编译测试)

touch crates/biome_js_formatter/tests/spec_tests.rs && cargo test -p biome_js_formatter formatter

为了更好的测试驱动开发流程,使用 cargo-watch 启动格式化测试

cargo watch -i '*.new' -x 'test -p biome_js_formatter formatter'

测试执行后,您将得到一个新的 arrow.js.snap.new 文件。

要实际更新快照,运行 cargo insta review 以交互式审查并接受挂起的快照。arrow.js.snap.new 将被替换为 arrow.js.snap

有时,您需要验证不同的情况/选项的格式化。为了做到这一点,创建一个包含您需要验证的情况的文件夹。例如,如果我们需要遵循前面的例子

  1. 创建一个名为 arrow_with_spaces/ 的文件夹,并将 JS 文件移到那里;
  2. 然后创建一个名为 options.json 的文件
  3. 内容可能是这样的
    {
        "cases": [
            {
                "line_width": 120,
                "indent_style": {"Space": 4}
            }
        ]
    }
    
  4. cases 关键字是必需的;
  5. 然后数组的每个对象都将包含您想要测试的选项矩阵。在这种情况下,测试套件将运行一个带有 line_width 为 120 和 ident_style 为 4 个空格的 第二个测试用例
  6. 当测试套件运行时,您将在快照中获得两个输出:默认输出和自定义输出

调试测试失败

测试不正确的情况有四种

  • 你尝试多次打印/格式化相同的标记;格式化器将在测试运行时进行检查;

  • 一些标记尚未打印;通常你会在快照中的“未实现标记/节点”部分找到这个信息,如下所示:"Unimplemented tokens/nodes";为了有效,测试不能包含该部分;

    如果删除标记是实际的行为(删除一些括号或分号),那么正确的方式是使用格式化器API biome_formatter::trivia::format_removed

  • 生成的代码不再是有效的程序,测试套件将再次解析生成的代码,如果存在语法错误则将失败;

  • 再次格式化后的生成代码与原始代码不同;这通常发生在删除/添加新元素且分组设置不正确的情况下;

依赖关系

~9-19MB
~247K SLoC