SwiftFormat Rules 使用说明

922 阅读6分钟

SwiftFormat 安装

  1. 直接去 github 下载安装,或者通过 Homebrew ,参考 下载地址 。装好后记得打开一下此应用,确保有权限使用。
  2. 安装此 app 提供的 Xcode Source Editor Extension:
    1. 点击状态栏左上角的 ,打开系统偏好设置,选择扩展
    2. 找到并选中 Xcode Source Editor,然后右边勾选 SwiftFormat
    3. 重新启动一下 Xcode,然后检查 Xcode 的 Editor 菜单底部,应该会出现 SwiftFormat 菜单
    4. 设置针对当前文件的快捷键:
    5. 从 Xcode 的菜单打开 Preferences... ,选中 Key Bindings
    6. 在右上的 Filter 里输入 swiftformat,然后选中第二个 SwiftFormat > Format File
    7. 双击高亮行对应 Key 的位置,然后同时按下键盘上的 Shift + Ctrl + I(如果你喜欢其它快捷键可自行选择)

经过上述步骤,我们就给 Xcode 安装好了 SwiftFormat 的 Source Editor Extension,而且可以用快捷键触发。现在你可以在 Xcode 里随意选中一个 Swift 文件,然后按下快捷键 Shift + Ctrl + I,SwiftFormat 就会根据它当前的规则对这个被选中的 Swift 文件进行代码风格的格式化。如果你的代码经过 Git 管理,应该能看到格式化前后的差异,比如一些空行、逗号、缩进之类

在实践上,比如你在做某个功能,修改了某些 Swift 文件,那就在改动这些文件后,按下快捷键 Shift + Ctrl + I 来格式化代码(必要时再手动做一些微调)。以此,可以循序渐进地改善整体代码的风格

配置

刚才提到规则,你可以打开 SwiftFormat for Xcode.app ,选中第二个 Rules Tab 进行调整

Swift version 选择 5.5(目前版本 0.49.8 所支持的最高 Swift 版本)

取消勾选 Infer Options Automatically,这样的话我们可以搜索并调整一些选项的参数,例如:

  • 勾选 acronyms,保持 Acronyms 其值为 ID,URL,UUID,VC,这样一些属性命名中的缩略词会被自动“纠正”,以符合 Swift 的 API 设计指南
  • 勾选 blankLinesBetweenImports,会移除 import 语句之间的空行
  • 勾选 blockComments,自动将注释换成  //  或  ///  风格
  • extensionAccessControl。将其 Extension Access Control Level Placement 改为 on-declarations,以将访问控制定义在具体的属性或方法上
  • fileHeader。将其 Header 改为 strip,以去除头部的文件注释
  • hoistPatternLet。将其 Pattern Let 改为 inline,以符合 Google 的规范
  • indent。将其 Ifdef Indent 改为 no-indentIndent Strings 改为 true
  • modifierOrder。将其 Modifier Oder 改为 open,public,final,fileprivate,private,private(set),override,convenience,lazy,static,weak,如果你不喜欢这个顺序可自行调整
  • spaceAroundOperators。将其 Ranges 改为 no-space,这样 ... 和 ..< 操作符前后的空格会被去除
  • unusedArguments。将其 Strip Unused Arguments 改为 closure-only,防止一些方法的实参被替换为下划线
  • wrap。将其 Max Width 改为 100,这样太长的代码会被折行(不过效果可能不好,最好是发现需要折行就手动调整)
  • 针对 wrapArguments,将其

    • Wrap Arguments 改为 before-first
    • Wrap Collections 改为 before-first
    • Wrap Conditions 改为 preserve
    • Wrap Parameters 改为 before-first
    • Wrap Return Type 改为 preserve
    • Wrap Typealiases 改为 preserve

可搜索并关闭如下规则:

  • markTypes。开启的话,会强制你写一些  // MARK:  标记
  • organizeDeclarations。开启的话,会按照更细致的规则对各种声明进行排序,可能不是你所希望的
  • sortedImports。开启的话,会将 import 的语句按照字母表排序
  • sortedSwitchCases。开启的话,会将 switch 的 case 语句按照字母表排序
  • wrapSwitchCases。开启的话,会强制 switch 时每个 case 单独一行

可进一步参考详细的规则文档

如果你觉得上面的设置比较繁琐,那也可以下载 default.swiftformat,然后在 SwiftFormat for Xcode.app 中,通过菜单 File -> Open...  打开它,即可导入上述规则

小技巧

如果你想将有多个参数的函数的参数进行折行,只需先将其尾部的括号换行(可在输入最后一个参数后进行),然后按下快捷键(比一个个手动换行要快不少)

// 例如

func test(id: Int, name: String
) {}

test(id: 42: name: "nix"
)

// 将变为

func test(
    id: Int,
    name: String
) {}

test(
    id: 42,
    name: "nix"
)

SwiftFormat Rules 参数说明

  • acronyms: 格式化属性定义
- let destinationUrl: URL
- let urlRouter: UrlRouter
- let screenId: String
- let entityUuid: UUID

+ let destinationURL: URL
+ let urlRouter: URLRouter
+ let screenID: String
+ let entityUUID: UUID
  • andOperator: 在 if, guard, while语句中,用 , 替代 &&
- if true && true {

+ if true, true {

- guard true && true else {

+ guard true, true else {

- if functionReturnsBool() && true {

+ if functionReturnsBool(), true {

- if functionReturnsBool() && variable {

+ if functionReturnsBool(), variable {
  • anyObjectProtocol: 在 protocolAnyObject 替代 class
- protocol Foo: class {}

+ protocol Foo: AnyObject {}

NOTE: Swift4.1 之后,官方推荐使用 AnyObject 替代 class

  • applicationMain: Swift 5.3之后,用 @main 替代 @UIApplicationMain@NSApplicationMain

  • assertionFailures: 替代所有 assert(false, ...)assertionFailure(...)precondition(false, ...)preconditionFailure(...)

  • blankLineAfterImports: 在最后一个 import 之后添加换行

  import A
  import B
  @testable import D
+
  class Foo {
    // foo
  }
  • blankLinesAroundMark: 在 MARK 前后添加换行
  func foo() {
    // foo
  }
  // MARK: bar
  func bar() {
    // bar
  }

  func foo() {
    // foo
  }
+
  // MARK: bar
+
  func bar() {
    // bar
  }
  • blankLinesAtEndOfScope: 删除作用域末尾的换行
  func foo() {
    // foo
-
  }

  func foo() {
    // foo
  }
  
    array = [
    foo,
    bar,
    baz,
-
  ]

  array = [
    foo,
    bar,
    baz,
  ]
  • blankLinesAtStartOfScope: 删除作用域开头的换行
  func foo() {
-
    // foo
  }

  func foo() {
    // foo
  }
  
    array = [
-
    foo,
    bar,
    baz,
  ]

  array = [
    foo,
    bar,
    baz,
  ]
  • blankLinesBetweenChainedFunctions: 删除链式调用(func)中的空行,但是保留换行符
test1()
    .test2()
-
    .test3()
  • blankLinesBetweenImports:删除 import 之间的换行
  import A
-
  import B
  import C
-
-
  @testable import D
  import E
  • blankLinesBetweenScopes:在 class, struct, enum, extension, protocol or function 之间插入空行
  func foo() {
    // foo
  }
  func bar() {
    // bar
  }
  var baz: Bool
  var quux: Int

  func foo() {
    // foo
  }
+
  func bar() {
    // bar
  }
+
  var baz: Bool
  var quux: Int
  • blockComments:将注释块变为连续的单行注释
- /*
-  * foo
-  * bar
-  */

+ // foo
+ // bar
  • braces:大括号使用特定的包裹格式
- if x
- {
    // foo
  }
- else
- {
    // bar
  }

+ if x {
    // foo
  }
+ else {
    // bar
  }
  • conditionalAssignment:使用 if / switch 表达式分配属性
- let foo: String
- if condition {
+ let foo = if condition {
-     foo = "foo"
+     "foo"
  } else {
-     bar = "bar"
+     "bar"
  }
  
  
- let foo: String
- switch condition {
+ let foo = switch condition {
  case true:
-     foo = "foo"
+     "foo"
  case false:
-     foo = "bar"
+     "bar"
  }
  • consecutiveBlankLines:用单个空行来替代连续多个空行
  func foo() {
    let x = "bar"
-

    print(x)
  }

  func foo() {
    let x = "bar"

    print(x)
  }
  • consecutiveSpaces:用单个空格来替换多个连续空格
- let     foo = 5
+ let foo = 5
  • docComments:对 API 使用 doc 注释,否则就使用常规注释
- // A placeholder type used to demonstrate syntax rules
+ /// A placeholder type used to demonstrate syntax rules
  class Foo {
-     // This function doesn't really do anything
+     /// This function doesn't really do anything
      func bar() {
-         /// TODO: implement Foo.bar() algorithm
+         // TODO: implement Foo.bar() algorithm
      }
  }
  • duplicateImports:删除重复的 import 声明
  import Foo
  import Bar
- import Foo


  import B
  #if os(iOS)
    import A
-   import B
  #endif
  • elseOnSameLine:else / catch / while 和 前面的大括号在同一行
  if x {
    // foo
- }
- else {
    // bar
  }

  if x {
    // foo
+ } else {
    // bar
  }
  do {
    // try foo
- }
- catch {
    // bar
  }

  do {
    // try foo
+ } catch {
    // bar
  }
  repeat {
    // foo
- }
- while {
    // bar
  }

  repeat {
    // foo
+ } while {
    // bar
  }
  • emptyBraces:在空的大括号中,删除多余的换行和空格
- func foo() {
-
- }

+ func foo() {}
  • enumNamespaces:将仅用于承载静态成员的类型转换为枚举(空枚举是Swift中创建命名空间的规范方式,因为它不能被实例化)。

  • extensionAccessControl:配置扩展的访问控制关键字的位置。

- extension Foo {
-     public func bar() {}
-     public func baz() {}
  }

+ public extension Foo {
+     func bar() {}
+     func baz() {}
  }
  • fileHeader:对所有文件使用指定的源文件头模板。我们一般将其 Header 改为 strip,以去除头部的文件注释

--header \n {file}\n\n Copyright © {created.year} CompanyName.\n

- // SomeFile.swift

+ //
+ //  SomeFile.swift
+ //  Copyright © 2023 CompanyName.
+ //
  • genericExtensions:对于泛型类型扩展使用尖括号(Array),而不是类型的约束(Array where Element == Foo)。
- extension Array where Element == Foo {}
- extension Optional where Wrapped == Foo {}
- extension Dictionary where Key == Foo, Value == Bar {}
- extension Collection where Element == Foo {}
+ extension Array<Foo> {}
+ extension Optional<Foo> {}
+ extension Dictionary<Key, Value> {}
+ extension Collection<Foo> {}

// With `typeSugar` also enabled:
- extension Array where Element == Foo {}
- extension Optional where Wrapped == Foo {}
- extension Dictionary where Key == Foo, Value == Bar {}
+ extension [Foo] {}
+ extension Foo? {}
+ extension [Key: Value] {}

// Also supports user-defined types!
- extension LinkedList where Element == Foo {}
- extension Reducer where
-     State == FooState,
-     Action == FooAction,
-     Environment == FooEnvironment {}
+ extension LinkedList<Foo> {}
+ extension Reducer<FooState, FooAction, FooEnvironment> {}    
  • headerFileName:确保头注释中的文件名与实际文件名匹配。

。。。未完待续