2 个不稳定版本

0.2.0 2023 年 9 月 21 日
0.0.0 2023 年 9 月 20 日

#43#parse-tree

Download history 158/week @ 2024-04-08 67/week @ 2024-04-15 33/week @ 2024-04-22 61/week @ 2024-04-29 46/week @ 2024-05-06 87/week @ 2024-05-13 49/week @ 2024-05-20 99/week @ 2024-06-03 23/week @ 2024-06-10 1/week @ 2024-06-17 8/week @ 2024-06-24 53/week @ 2024-07-01 23/week @ 2024-07-08 43/week @ 2024-07-15 87/week @ 2024-07-22

207 每月下载
用于 2 crates

MIT/Apache

760KB
16K SLoC

Rust 12K SLoC // 0.0% comments Solidity 4K SLoC // 0.5% comments

格式化工具 (fmt)

尊重(部分)风格指南的 Solidity 格式化工具,并在 Prettier Solidity 插件 情况下进行测试。

架构

格式化器分两步工作

  1. 使用 solang 将 Solidity 源代码解析为 PT(解析树)(不同于抽象语法树,查看区别)。
  2. 遍历 PT,输出符合提供配置和规则集的新源代码。

遍历树的技巧基于 访问者模式,工作方式如下

  1. 为每个 PT 节点类型实现 Formatter 回调函数。每个回调函数都应该为当前节点写入格式化输出,并调用 Visitable::visit 函数为子节点委派输出写入。
  2. 为每个 PT 节点类型实现 Visitable 特性和其 visit 函数。每个 visit 函数都应该调用相应的 Formatter 的回调函数。

输出

格式化输出以 的形式写入输出缓冲区。Chunk 结构体持有要写入的内容及其元数据。这包括内容周围的注释以及指定此 是否需要空间的 needs_space 标志。该标志覆盖了 Formatter::next_char_needs_space 方法的默认行为。

内容会被写入到包含当前缩进级别、缩进长度、当前状态以及其他决定如何写入内容的规则的 FormatBuffer 中。FormatBuffer 实现了 std::fmt::Write trait,用于评估当前信息并决定如何将内容写入目标位置。

注释

Solang 解析器不会将注释作为解析树节点类型输出,而是将它们与位置信息一起放在解析树的旁边。因此,在遍历解析树时,有必要推断出插入注释的位置以及如何格式化它们。

为了处理这个问题,格式化器预先解析注释,并将它们分为两类:前缀注释和后缀注释。前缀注释指的是它们后面的节点,后缀注释指的是它们前面的节点。例如

// This is a prefix comment
/* This is also a prefix comment */
uint variable = 1 + 2; /* this is postfix */ // this is postfix too
    // and this is a postfix comment on the next line

为了将注释插入到适当的位置,在将字符串写入缓冲区之前,会将其转换为块。块是任何不能由空白字符分割的字符串。块还携带周围注释信息。因此,在写入块时,可以在块前后以及周围添加注释。

为了构建一个块,将字符串和字符串的位置提供给格式化器,并将字符串开始和结束前的预解析注释关联到该字符串上。然后可以在将块写入缓冲区之前进一步对源代码进行分块。

要写入块,首先将与块开始关联的注释写入缓冲区。然后格式化器检查缓冲区中已写入的内容和块之间是否需要插入空格,并在适当的位置插入。如果块内容可以放在同一行上,它将直接写入缓冲区,否则它将写入下一行。最后,任何相关的后缀注释也会被写入。

示例

源代码

pragma   solidity ^0.8.10 ;
contract  HelloWorld {
    string   public message;
    constructor(  string memory initMessage) { message = initMessage;}
}


event    Greet( string  indexed  name) ;

解析树(简化版)

SourceUnit
 | PragmaDirective("solidity", "^0.8.10")
 | ContractDefinition("HelloWorld")
    | VariableDefinition("string", "message", null, ["public"])
    | FunctionDefinition("constructor")
       | Parameter("string", "initMessage", ["memory"])
 | EventDefinition("string", "Greet", ["indexed"], ["name"])

从解析树重新构建的格式化源代码

pragma solidity ^0.8.10;

contract HelloWorld {
    string public message;

    constructor(string memory initMessage) {
        message = initMessage;
    }
}

event Greet(string indexed name);

配置

格式化器支持在 FormatterConfig 中定义的多个配置选项。

选项 默认 描述
line_length 120 格式化器将尝试换行的最大行长度
tab_width 4 每个缩进级别的空格数
bracket_spacing false 在括号之间打印空格
int_types long uint/int256 类型的样式。可用选项:longshortpreserve
func_attrs_with_params_multiline true 如果函数参数是多行的,则始终将函数属性放在单独的行上
quote_style double 引号样式的样式。可用选项:doublesinglepreserve
number_underscore preserve 数字字面量中下划线的样式。可用选项:removethousandspreserve

待更新:^

禁用行

可以通过添加注释 // forgefmt: disable-next-line 来在特定行上禁用格式化器,如下所示

// forgefmt: disable-next-line
uint x = 100;

或者,注释也可以放在行末。在这种情况下,您需要使用 disable-line 而不是

uint x = 100; // forgefmt: disable-line

测试

测试位于 fmt/testdata 文件夹下,并指定了格式不正确的和预期的 Solidity 代码。源代码文件命名为 original.sol,预期文件按以下格式命名:({prefix}.)?fmt.sol。对于覆盖可用配置选项的测试,可能需要多个预期文件。

默认配置值可以通过在预期文件中添加以下格式的注释来覆盖:// config: {config_entry} = {config_value}。例如

// config: line_length = 160

使用 test_directory 宏来指定包含测试套件源文件的文件夹。每个测试套件的过程如下

  1. 预处理配置值注释
  2. 解析并比较源文件和预期文件的抽象语法树(AST)。
    • AstEq 特性定义了 AST 节点的比较规则
  3. 格式化源文件并断言输出与预期文件相等。
  4. 格式化预期文件并断言格式化操作的幂等性。

贡献

查看 foundry 贡献指南

forge fmt 的贡献指南

提交一个问题

  1. 创建一个简短、明确的标题来描述问题。
    • 不好的标题示例
      Forge fmt does not work
      Forge fmt breaks
      Forge fmt unexpected behavior
      
    • 好的标题示例
      Forge fmt postfix comment misplaced
      Forge fmt does not inline short yul blocks
      
  2. 填写包含 foundry 版本、平台和组件信息的模板字段。
  3. 提供显示当前行为和预期行为的代码片段。
  4. 如果是功能请求,请指定为什么需要此功能。
  5. 除了默认标签(错误为 T-Bug,功能为 T-feature)之外,添加 C-forgeCmd-forge-fmt 标签。

修复错误

  1. 在 PR 描述中指定正在解决的 issue。
  2. 在 PR 描述中添加解决方案的注释。
  3. 确保 PR 包含了验收测试。

开发功能

  1. 在 PR 描述中指定正在解决的 issue。
  2. 在 PR 描述中添加解决方案的注释。
  3. 提供新功能的测试覆盖率。这些应包括
    • fmt/testdata/$dir/ 下添加格式不正确的和预期的 Solidity 代码
    • 测试前缀和后缀注释的行为
    • 如果是新的配置值,则测试覆盖 所有 可用选项

依赖

~25–41MB
~695K SLoC