Elixir语言中 Code.format_string!
相关用法介绍如下。
用法:
format_string!(string, opts \\ [])
(从 1.6.0 开始)
@spec format_string!(
binary(),
keyword()
) :: iodata()
格式化给定的代码 string
。
格式化程序接收表示 Elixir 代码的字符串,并根据预定义的规则返回表示格式化代码的 iodata。
选项
-
:file
- 包含字符串的文件,用于错误报告 -
:line
- 字符串开始的行,用于错误报告 -
:line_length
- 格式化文档时的目标行长。默认为 98。请注意,此值用作指导,但在某些情况下不强制执行。有关详细信息,请参阅下面的 "Line length" 部分 -
:locals_without_parens
- 名称和数量对的关键字列表,应尽可能保持不带括号。元数可能是原子:*
,这意味着该名称的所有元数。格式化程序已经包含一个函数列表,这个选项扩充了这个列表。 -
:force_do_end_blocks
(从 v1.9.0 开始) - 当true
时,将do: ...
、else: ...
和朋友的所有内联用法转换为do
-end
块。默认为false
。请注意,此选项是收敛的:一旦将其设置为true
,所有关键字都将被转换。如果稍后将其设置为false
,则do
-end
块将不会转换回关键字。
设计原则
格式化程序是根据三个原则设计的。
首先,格式化程序默认不会改变代码的语义。这意味着输入 AST 和输出 AST 是等价的。
第二个原则是提供尽可能少的配置。这通过消除争用点来简化格式化程序的采用,同时确保整个社区始终遵循单一样式。
格式化程序不会硬编码名称。格式化程序不会有特殊行为,因为函数名为 defmodule
、 def
等。这一原则反映了 Elixir 成为可扩展语言的目标,开发人员可以使用新结构扩展语言,就好像它们是语言的一部分一样。当绝对需要根据名称更改行为时,此行为应该是可配置的,例如 :locals_without_parens
选项。
运行格式化程序
格式化程序尝试在单行上尽可能地适应,并在不能时尽可能引入换行符。
在某些情况下,这可能会导致不希望的格式。因此,格式化程序生成的某些代码可能不美观,可能需要开发人员的明确干预。这就是我们不建议在现有代码库中盲目运行格式化程序的原因。相反,您应该对每个格式化文件进行格式化和完整性检查。
例如,格式化程序可能会破坏多个子句的长函数定义:
def my_function(
%User{name: name, age: age, ...},
arg1,
arg2
) do
...
end
虽然上面的代码完全有效,但您可能更喜欢匹配函数体内的结构变量,以便将定义保持在一行:
def my_function(%User{} = user, arg1, arg2) do
%{name: name, age: age, ...} = user
...
end
在某些情况下,您可以使用格式化程序不会生成优雅代码的事实作为重构的提示。拿这个代码:
def board?(board_id, %User{} = user, available_permissions, required_permissions) do
Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
required_permissions == Enum.to_list(MapSet.intersection(MapSet.new(required_permissions), MapSet.new(available_permissions)))
end
上面的代码行很长,运行格式化程序并不能解决这个问题。事实上,格式化程序可能会让你有复杂的表达式更加明显:
def board?(board_id, %User{} = user, available_permissions, required_permissions) do
Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
required_permissions ==
Enum.to_list(
MapSet.intersection(
MapSet.new(required_permissions),
MapSet.new(available_permissions)
)
)
end
将这种情况作为您的代码应该重构的建议:
def board?(board_id, %User{} = user, available_permissions, required_permissions) do
Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
matching_permissions?(required_permissions, available_permissions)
end
defp matching_permissions?(required_permissions, available_permissions) do
intersection =
required_permissions
|> MapSet.new()
|> MapSet.intersection(MapSet.new(available_permissions))
|> Enum.to_list()
required_permissions == intersection
end
总结一下:由于格式化程序无法更改代码的语义,因此有时需要调整或重构代码以获得最佳格式。为了帮助更好地理解如何控制格式化程序,我们将在接下来的部分中说明格式化程序保持用户编码的情况以及如何控制多行表达式。
线长
关于格式化程序的另一点是 :line_length
配置是一个指南。在许多情况下,格式化程序不可能将您的代码分开,这意味着它将超过行长。例如,如果您有一个长字符串:
"this is a very long string that will go over the line length"
格式化程序不知道如何在不更改代码底层语法表示的情况下将其分解,因此您可以介入:
"this is a very long string " <>
"that will go over the line length"
字符串连接使代码适合单行,也为格式化程序提供了更多选项。
这也可能出现在 do/end 块中,其中 do
关键字(或 ->
)可能超过行长,因为格式化程序没有机会以可读的方式引入换行符。例如,如果您这样做:
case very_long_expression() do
end
只有 do
关键字在行长之上,Elixir 不会发出这个:
case very_long_expression()
do
end
因此,它宁愿根本不接触线,并将do
留在线限制之上。
保持用户的格式
在某些情况下,格式化程序尊重输入格式。下面列出了这些:
-
数字中的无关紧要的数字保持原样。然而,格式化程序总是为超过 5 位的十进制数字插入下划线,并将十六进制数字转换为大写
-
字符串、charlists、atoms 和 sigils 保持原样。没有字符会自动转义或未转义。分隔符的选择也从输入中得到尊重
-
块内的换行符与输入一样保留,除了:
- 包含多行的表达式在前后总是有一个空行,并且 2) 空行总是被挤在一起形成一个空行
-
:do
关键字和do
-end
块之间的选择留给用户 -
如果列表、元组、位串、映射、结构和函数调用后跟在左括号中的换行符和在右括号中的换行符之前,它们将被分成多行
-
某些运算符(如管道运算符)和其他运算符(如比较运算符)之前的换行符
不保证上述行为。我们将来可能会删除或添加新规则。记录它们的目的是更好地理解格式化程序的期望。
多行列表、映射、元组等
您可以强制列表、元组、位串、映射、结构和函数调用每行有一个条目,方法是在左括号之后添加一个换行符,在右括号行之前添加一个新行。例如:
[
foo,
bar
]
如果括号周围没有换行符,则格式化程序将尝试将所有内容放在一行中,例如下面的代码片段
[foo,
bar]
将被格式化为
[foo, bar]
您还可以通过将每个条目放在自己的行上来强制在多行上呈现函数调用和关键字:
defstruct name: nil,
age: 0
上面的代码将由格式化程序每行保留一个关键字条目。为避免这种情况,只需将所有内容压缩成一行。
函数调用中的括号和无括号
Elixir 有两种函数调用语法。有括号和没有括号。默认情况下,Elixir 将为所有调用添加括号,除了:
- 具有
do
-end
块的调用 - 没有括号的本地调用,其中本地调用的名称和数量也列在
:locals_without_parens
下(除了数量为 0 的调用,编译器总是需要括号)
选择括号和不选择括号也会影响缩进。当带括号的函数调用不适合同一行时,格式化程序会在括号周围引入换行符,并用两个空格缩进参数:
some_call(
arg1,
arg2,
arg3
)
另一方面,没有括号的函数调用总是由函数调用长度本身缩进,如下所示:
some_call arg1,
arg2,
arg3
如果最后一个参数是一个数据结构,例如映射和列表,并且数据结构的开头与函数调用位于同一行,则不会发生缩进,这允许这样的代码:
Enum.reduce(some_collection, initial_value, fn element, acc ->
# code
end)
some_function_without_parens %{
foo: :bar,
baz: :bat
}
代码注释
格式化程序还处理代码注释以保证在注释的开头 (#) 和下一个字符之间始终添加一个空格。
格式化程序还将所有尾随注释提取到其上一行。例如下面的代码
hello #world
将被改写为
# world
hello
因为代码注释是在代码表示 (AST) 之外处理的,所以在某些情况下代码注释被代码格式化程序视为模棱两可。例如下面匿名函数中的注释
fn
arg1 ->
body1
# comment
arg2 ->
body2
end
在这个
fn
arg1 ->
body1
# comment
arg2 ->
body2
end
被认为是等效的(嵌套与大多数用户格式一起被丢弃)。在这种情况下,代码格式化程序将始终格式化为后者。
换行符
格式化程序将代码中的所有换行符从 \r\n
转换为 \n
。
相关用法
- Elixir Code.fetch_docs用法及代码示例
- Elixir Code.prepend_path用法及代码示例
- Elixir Code.compiler_options用法及代码示例
- Elixir Code.quoted_to_algebra用法及代码示例
- Elixir Code.put_compiler_option用法及代码示例
- Elixir Code.ensure_compiled用法及代码示例
- Elixir Code.required_files用法及代码示例
- Elixir Code.get_compiler_option用法及代码示例
- Elixir Code.Fragment.cursor_context用法及代码示例
- Elixir Code.available_compiler_options用法及代码示例
- Elixir Code.ensure_loaded?用法及代码示例
- Elixir Code.eval_quoted用法及代码示例
- Elixir Code.require_file用法及代码示例
- Elixir Code.Fragment.container_cursor_to_quoted用法及代码示例
- Elixir Code.Fragment.surround_context用法及代码示例
- Elixir Code.delete_path用法及代码示例
- Elixir Code.append_path用法及代码示例
- Elixir Code.ensure_loaded用法及代码示例
- Elixir Code.unrequire_files用法及代码示例
- Elixir Code.string_to_quoted_with_comments用法及代码示例
- Elixir Code.eval_string用法及代码示例
- Elixir Code用法及代码示例
- Elixir Config.config_env用法及代码示例
- Elixir Config.config用法及代码示例
- Elixir Config.Reader用法及代码示例
注:本文由纯净天空筛选整理自elixir-lang.org大神的英文原创作品 Code.format_string!(string, opts \\ [])。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。