🛠️

swift-format の Configuration

2021/06/22に公開
1

lineLengthを 1000 に設定してなるべくワンラインでコードを書いて検証。
改行状態が必要な場合はrespectsExistingLineBreaksを true にして検証。

version(number)

Configuration file のバージョン。今は常に1。

lineLength(number)

一行あたりの最長文字数。これを超えると改行される。基本的にはこれが最も強力なルールになる。キリのいいところで改行が入る。

  • 変数宣言の頭
  • =での代入の直後
  • クロージャーや関数の引数
  • チェーンメソッド
  • 開き中括弧{の直後
  • 閉じ中括弧}

など。

indentation(object)

一段階インデントする時の空白の種類と幅。spacestabsのどちらか一方のみ。

  • spaces(number): インデントは指定された個数のスペース
  • tabs(number): インデントは指定された個数のタブ

tabWidth(number)

一つのタブに相当するとみなされるスペースの数。タブをインデントに指定したときに有効。

maximumBlankLines(number)

連続した空白行の最大数。

respectsExistingLineBreaks(boolean)

ソースコード中の既存の改行を尊重するかどうか。

before
if value1 < value2 {
} else if value2 < value3 {
} else {
}

if value1 < value2 {

} else if value2 < value3 {

} else {

}
after(false)
if value1 < value2 {} else if value2 < value3 {} else {}

if value1 < value2 {

} else if value2 < value3 {

} else {

}

true の場合は before のまんま

lineBreakBeforeControlFlowKeywords(boolean)

elsecatchの直前の改行動作。true だとキーワードの前で改行が入る。

false
if value1 < value2 { } else if value2 < value3 { } else { }

do { let _ = try something() } catch { print(error) }
true
if value1 < value2 {
}
else if value2 < value3 {
}
else {
}

do { let _ = try something() }
catch { print(error) }

lineBreakBeforeEachArgument(boolean)

変数や引数の改行動作。変数宣言や関数の引数定義でlineLengthを超えた場合改行されるが、そのときの改行の仕方が変わる。true だと変数や引数ごとに改行される。false だとlineLengthを超えた場合のみ改行される。すなわち、lineLengthが非常に大きい数値だと機能しない。

lineLength=50のときの例

before
func arguments(a: String, b: String, c: String, d: String) -> Bool { return false }
after(false)
func arguments(
    a: String, b: String, c: String, d: String
) -> Bool { return false }
after(true)
func arguments(
    a: String,
    b: String,
    c: String,
    d: String
) -> Bool { return false }

lineBreakBeforeEachGenericRequirement(boolean)

ジェネリックキーワードの改行動作。lineLengthを超えた場合ジェネリックキーワードの区切りに改行が入るが、その時の改行の仕方が変わる。true にするとジェネリックキーワードごとに改行される。false だとlineLengthを超えた場合のみ改行される。すなわち、lineLengthが非常に大きい数値だと機能しない。

lineLength=50のときの例

before
class MyClass<S, T, U, W> where S: Collection, T: Equatable, U: Hashable, W: AnyObject { }
after(false)
class MyClass<S, T, U, W>
where
    S: Collection, T: Equatable, U: Hashable,
    W: AnyObject
{}
after(true)
class MyClass<S, T, U, W>
where
    S: Collection,
    T: Equatable,
    U: Hashable,
    W: AnyObject
{}

prioritizeKeepingFunctionOutputTogether(boolean)

関数の戻り値の定義において、閉じ括弧)とくっつけることを優先するかどうか。

lineLength=34(つまり->までの長さ)のときの例

before
func hoge(_ value1: Int) throws -> Int { return 0 }
after(false)
func hoge(_ value1: Int) throws
    -> Int
{ return 0 }
after(true)
func hoge(
    _ value1: Int
) throws -> Int { return 0 } // )とその後のthrows -> Intがひとまとまりになっている。

indentConditionalCompilationBlocks(boolean)

条件付きコンパイルのブロックをインデントするかどうか。

false
#if someCondition
let a = 123
#else
let c = 456
#endif
true
#if someCondition
    let a = 123
#else
    let c = 456
#endif

lineBreakAroundMultilineExpressionChainComponents(boolean)

lineLengthを超えた場合、キリのいいところで改行が入るが、チェーンメソッドにおいて改行した結果複数行に跨がる場合は、チェーンメソッド開始の.のところで改行するというフラグ。複数行に跨がることがない場合は無視される。癖がすごいんじゃ。下の例では.mapの改行のされ方に注目して欲しい。

lineLength=45のときの例

before
let hoge = "abc,def,ghi"
hoge.components(separatedBy: ",").map({ (item) -> String in item.uppercased() }).filter { $0.contains("E") }.joined().forEach { (char) in if char < "e" { print(char) } else { print("-")} }
after(false)
let hoge = "abc,def,ghi"
hoge.components(separatedBy: ",").map({
    (item) -> String in item.uppercased()
}).filter { $0.contains("E") }.joined()
    .forEach { (char) in
        if char < "e" {
            print(char)
        } else {
            print("-")
        }
    }
after(true)
let hoge = "abc,def,ghi"
hoge.components(separatedBy: ",")
    .map({ (item) -> String in
        item.uppercased()
    })
    .filter { $0.contains("E") }.joined()
    .forEach { (char) in
        if char < "e" {
            print(char)
        } else {
            print("-")
        }
    }

indentSwitchCaseLabels(boolean)

switch 文の case で段落下げをするかどうか。

false
switch value {
case 100: print()
case 200 ..< 300: print()
case 300: print()
default: break
}
true
switch value {
    case 100: print()
    case 200 ..< 300: print()
    case 300: print()
    default: break
}

fileScopedDeclarationPrivacy(object)

rulesFileScopedDeclarationPrivacyを true に指定しているときに有効。

  • accessLevel(string): privateまたはfileprivateを指定する。privateを指定すると、fileprivateprivateになる。fileprivateを指定すると、privatefileprivateになる。

rules

AllPublicDeclarationsHaveDocumentation(boolean)

--mode lintで有効。
true にするとpublicまたはopenな宣言の直前にコメントがない場合に警告が出るようになる。

AlwaysUseLowerCamelCase(boolean)

--mode lintで有効。
true にするとローワーキャメルケースでない宣言がある場合に警告が出るようになる。

func GetValue() -> Int { } // GetValueはローワーキャメルケースじゃないよ

AmbiguousTrailingClosureOverload(boolean)

--mode lintで有効。
true にすると曖昧なクロージャーのオーバーロードを発見した場合に警告が出るようになる。

// 下の三つは曖昧で区別がつかないよ
func hoge(mad: () -> Int) {}
func hoge(bad: (Bool) -> Bool) {}
func hoge(sad: (String) -> Bool) {}

BeginDocumentationCommentWithOneLineSummary(boolean)

--mode lintで有効。
よくわかりませんが、コメントの書き方についての警告が出るみたいです。

DoNotUseSemicolons(boolean)

true にすると命令の末尾のセミコロンを禁止して、改行が必要な場合は改行を挿入します。

before
var value1: Int = 0;
var value2: Int = 1
var value3: Int = 2; var value4: Int = 3; var value5: Int = 4
after
var value1: Int = 0
var value2: Int = 1
var value3: Int = 2
var value4: Int = 3
var value5: Int = 4

DontRepeatTypeInStaticProperties(boolean)

--mode lintで有効。
true にすると static や class で宣言されている変数名について、型被りしてると警告が出る。

class UIColor {
    static let redColor: UIColor // redColor は冗長なので red を推奨
    class var blueColor: UIColor // blueColor は冗長なので blue を推奨
}

protocol Person {
    static let youngPerson: Person // youngPerson は冗長なので young を推奨
}

FileScopedDeclarationPrivacy(boolean)

上述

FullyIndirectEnum(boolean)

よくわかりませんが、indirect caseしかないときにindirect enumに書き換えるみたいです。

before
public enum Piyo {
    indirect case aaa(a: Piyo)
    indirect case bbb(bbb: Piyo)
}
after
public indirect enum Piyo {
    case aaa(a: Piyo)
    case bbb(bbb: Piyo)
}

GroupNumericLiterals(boolean)

true にすると数値をアンダースコアで分割して表記するようになる。

before
let a = 1234567890
let b = -1234567890
let c = 0x12345678
let d = -0x12345678
let e = 0b100101010110
let f = -0b100101010110
after
let a = 1_234_567_890
let b = -1_234_567_890
let c = 0x1234_5678
let d = -0x1234_5678
let e = 0b1001_01010110
let f = -0b1001_01010110

IdentifiersMustBeASCII(boolean)

--mode lintで有効。
true の場合、変数名や関数名などに ASCII 文字以外が含まれていると警告が出る。

letPattern: Int = 0 // △は禁止

NeverForceUnwrap(boolean)

--mode lintで有効。
true の場合、オプショナルを強制アンラップしている箇所に警告が出る。

let a: String? = nil
let b = a! // 強制アンラップ禁止

let c: Int? = nil
let d = c as! Double // 強制案ラップ禁止

NeverUseForceTry(boolean)

--mode lintで有効。
true の場合、強制 try している箇所に警告が出る。

func throwFunc() throws -> Int {
    throw NSError()
}
let result = try! throwFunc() // try!は禁止

NeverUseImplicitlyUnwrappedOptionals(boolean)

--mode lintで有効。
true の場合、暗黙的アンラップ型の宣言箇所に警告が出る。

var num: Int! // 暗黙的アンラップ禁止
var str: String! = nil // 暗黙的アンラップ禁止
@IBOutlet var button: UIButton! // このパターンでは警告は出ない

NoAccessLevelOnExtensionDeclaration(boolean)

true の場合、extension についている open 以外のアクセス修飾子を削除する。

before
open extension String {}
public extension String {}
internal extension String {}
fileprivate extension String {}
private extension String {}
after
open extension String {}
extension String {}
extension String {}
extension String {}
extension String {}

NoBlockComments(boolean)

--mode lintで有効。
true の場合、ブロックコメント/* */を禁止。

var num: /* IOByteCout is UInt64 */ IOByteCount = 0 // ブロックコメント禁止

/*  // ブロックコメント禁止
 This open source software is created by kyome.
*/

NoCasesWithOnlyFallthrough(boolean)

true の場合、switch 文においてfallthroughだけしている case をなくす。

before
enum Type {
    case ready
    case start
    case pause
    case stop
}

var type = Type.ready
switch type {
case .ready:
    print("ready")
case .start:
    fallthrough
case .pause:
    fallthrough
case .stop:
    print("stop")
}
after
// (省略)

switch type {
case .ready:
    print("ready")
case .start, .pause, .stop:
    print("stop")
}

NoEmptyTrailingClosureParentheses(boolean)

true の場合、Trailing Clousure の空括弧を取り除く。

before
DispatchQueue.main.async() {
    print("Hi")
}

func meu(_ callback: () -> Void) {
    callback()
}

meu() {
    print("Bye")
}
after
DispatchQueue.main.async { // ()が取り除かれた
    print("Hi")
}

// (中略)

meu { // ()が取り除かれた
    print("Bye")
}

NoLabelsInCasePatterns(boolean)

true の場合、Associated Value ありの case 文において、ラベルをなくす。

before
enum Type {
    case one(left: Int, right: Int)
}
var type = Type.one(left: 0, right: 1)

switch type {
case .one(left: let left, right: let right):
    print(left, right)
}
after
// (中略)
switch type {
case .one(let left, let right):
    print(left, right)
}

NoLeadingUnderscores(boolean)

--mode lintで有効。
true の場合、変数名の前にアンダースコアがついていると警告が出る。

let _foo = "foo" // _fooの_は取り除くこと
func _piyo() { } // _piyoの_は取り除くこと

NoParensAroundConditions(boolean)

true の場合、条件式の前後の不要な括弧を取り除く。括弧が必要な場合は除かれない。

before
if (x) { }

guard (x), (y), (z == 3) else { }

while (x) { }

repeat {

} while(x)

switch (4) {
default: break
}
after
if x {}

guard x, y, z == 3 else {}

while x {}

repeat {

} while x

switch 4 {
default: break
}

NoVoidReturnOnFunctionSignature(boolean)

true の場合、Void を return する関数の-> Void-> ()を取り除く。クロージャーの定義など必要な場合は除かれない。

before
func foo() -> () { }

func test() -> Void { }
after
func foo() { }

func test() { }
例外
func hoge(_ callback: () -> Void) { }

let hoge = { () -> Void in print("") }

OneCasePerLine(boolean)

true の場合、Associated Value ありの case、または rawValue を指定してある case は独立させる。

before
enum Type {
    case typeA = "A", typeB = "B", typeC, typeD, typeE
    case typeF(Int), typeG, typeH
}
after
enum Type {
    case typeA = "A"
    case typeB = "B"
    case typeC, typeD, typeE
    case typeF(Int)
    case typeG, typeH
}

OneVariableDeclarationPerLine(boolean)

true の場合、一行につき一つの変数を宣言するように変更する。

before
var a = 0, b = 1, (c, d) = (2, "3")

let e, f, g: Int

var h: Int, i: String, j: Float
after
var a = 0
var b = 1
var (c, d) = (2, "3")

let e: Int
let f: Int
let g: Int

var h: Int
var i: String
var j: Float

OnlyOneTrailingClosureArgument(boolean)

--mode lintで有効。
true の場合、通常の Closure と Trailing Closure を混ぜて使うと警告が出る。
(警告が出るのは func の引数に複数のクロージャーがある場合。)

func call(successHandler: () -> Void, failureHandler: () -> Void) {}

// これは禁止
call(successHandler: { }) {
    // ...
}

// 以下はOK
call(successHandler: {}, failureHandler: {})
call {
    // ...
} failureHandler: {
    // ...
}

OrderedImports(boolean)

true の場合、import の順序が整頓される。ただの import だとアルファベット順。@testableimport funcimport enumなどはそれぞれまとめられる。ソースコードの下のほうに import を書いていても、ソースコードの一番上に移動する。コメントが import の直前に書かれていた場合はそれも一緒についてくる。

before
import Foundation
// Starts Imports
import Core


// Comment with new lines
import UIKit

@testable import SwiftFormatRules
import enum Darwin.D.isatty
// Starts Test
@testable import MyModuleUnderTest
// Starts Ind
import func Darwin.C.isatty

let a = 3
import SwiftSyntax
after
// Starts Imports
import Core
import Foundation
import SwiftSyntax
// Comment with new lines
import UIKit

import func Darwin.C.isatty
import enum Darwin.D.isatty

// Starts Test
@testable import MyModuleUnderTest
@testable import SwiftFormatRules

let a = 3

ReturnVoidInsteadOfEmptyTuple(boolean)

true の場合、戻り値の型が()になっているところをVoidに統一する。

before
let callback: () -> ()

typealias x = Int -> ()

func y() -> Int -> () { return }

func z(d: Bool -> ()) {}
after
let callback: () -> Void

typealias x = Int -> Void

func y() -> Int -> Void { return }

func z(d: Bool -> Void) {}

UseLetInEveryBoundCaseVariable(boolean)

--mode lintで有効。
true の場合、case 文においてそれぞれの定数前に let をつける書き方でない場合警告がでる。switch 文だけでなくif caseguard caseなども対象。

enum Label {
    case value(String, Int)
}
switch Label.value("hello", 100) {
case let .value(label, value): break       // ダメな書き方
case .value(let label, let value): break // 良い書き方
}

switch ("hello", 100) {
case let (label, value): break         // ダメな書き方
case (let label, let value): break // 良い書き方
}

UseShorthandTypeNames(boolean)

true の場合、型指定において省略形式で表記できる箇所を省略形式に書き換える。
細かい挙動については本家のテストコードを見てください。

before
var a: Array<Int>
var b: Dictionary<String, Int>
var c: Optional<String>
var d: Array<[Int]>
var e: Array<Array<Int>>
var f: Optional<Int>?
after
var a: [Int]
var b: [String: Int]
var c: String?
var d: [[Int]]
var e: [[Int]]
var f: Int??

UseSingleLinePropertyGetter(boolean)

true の場合、Computed Property でかつ get しか記述されていない箇所の get スコープを省略表記に変える。

before
var a: Int {
    get {
        return 1
    }
}
after
var a: Int {
    return 1
}

UseSynthesizedInitializer(boolean)

--mode lintで有効。
true の場合、可能な限り Synthesized Initializer を使用するように勧めてくる警告を出す。

public struct Person {
    var name: String
    let phoneNumber: String
    let address: String

    // ↓ Synthesized Initializer があるからこれは不要
    init(name: String, phoneNumber: String, address: String) {
        self.name = name
        self.address = address
        self.phoneNumber = phoneNumber
    }
}

UseTripleSlashForDocumentationComments(boolean)

true の場合、関数直前に書かれたコメントがドキュメンテーションコメントであれば、///を用いた形式に統一する。

before
/**
 * Returns a docLineComment.
 *
 * - Parameters:
 *   - withOutStar: Indicates if the comment start with a star.
 * - Returns: docLineComment.
 */
func foo(withOutStar: Bool) {}
after
/// Returns a docLineComment.
///
/// - Parameters:
///   - withOutStar: Indicates if the comment start with a star.
/// - Returns: docLineComment.
func foo(withOutStar: Bool) {}

ValidateDocumentationComments(boolean)

--mode lintで有効。
true の場合、ドキュメンテーションコメントの構文チェックをしてくれる。間違いがあれば警告が出る。

Discussion

KyomeKyome
$ swift run swift-format --configuration .swift-format.json --mode lint --recursive ファイルパス
$ swift run swift-format --configuration .swift-format.json --mode format --recursive ファイルパス