2 个不稳定版本
0.2.0 | 2023 年 9 月 21 日 |
---|---|
0.0.0 | 2023 年 9 月 20 日 |
#43 在 #parse-tree
207 每月下载
用于 2 crates
760KB
16K SLoC
格式化工具 (fmt
)
尊重(部分)风格指南的 Solidity 格式化工具,并在 Prettier Solidity 插件 情况下进行测试。
架构
格式化器分两步工作
遍历树的技巧基于 访问者模式,工作方式如下
- 为每个 PT 节点类型实现
Formatter
回调函数。每个回调函数都应该为当前节点写入格式化输出,并调用Visitable::visit
函数为子节点委派输出写入。 - 为每个 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 类型的样式。可用选项:long 、short 、preserve |
func_attrs_with_params_multiline | true | 如果函数参数是多行的,则始终将函数属性放在单独的行上 |
quote_style | double | 引号样式的样式。可用选项:double 、single 、preserve |
number_underscore | preserve | 数字字面量中下划线的样式。可用选项:remove 、thousands 、preserve |
待更新:^
禁用行
可以通过添加注释 // 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
宏来指定包含测试套件源文件的文件夹。每个测试套件的过程如下
- 预处理配置值注释
- 解析并比较源文件和预期文件的抽象语法树(AST)。
AstEq
特性定义了 AST 节点的比较规则
- 格式化源文件并断言输出与预期文件相等。
- 格式化预期文件并断言格式化操作的幂等性。
贡献
查看 foundry 贡献指南。
对 forge fmt
的贡献指南
提交一个问题
- 创建一个简短、明确的标题来描述问题。
- 不好的标题示例
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
- 不好的标题示例
- 填写包含 foundry 版本、平台和组件信息的模板字段。
- 提供显示当前行为和预期行为的代码片段。
- 如果是功能请求,请指定为什么需要此功能。
- 除了默认标签(错误为
T-Bug
,功能为T-feature
)之外,添加C-forge
和Cmd-forge-fmt
标签。
修复错误
- 在 PR 描述中指定正在解决的 issue。
- 在 PR 描述中添加解决方案的注释。
- 确保 PR 包含了验收测试。
开发功能
- 在 PR 描述中指定正在解决的 issue。
- 在 PR 描述中添加解决方案的注释。
- 提供新功能的测试覆盖率。这些应包括
- 在
fmt/testdata/$dir/
下添加格式不正确的和预期的 Solidity 代码 - 测试前缀和后缀注释的行为
- 如果是新的配置值,则测试覆盖 所有 可用选项
- 在
依赖
~25–41MB
~695K SLoC