オフィシャル感のあるSwiftのformatter/linterであるところの、swift-formatを試した。
SwiftのGoogleフォークで生まれたものがベースになっているようだ。開発が進んでいるmaster
ブランチと、Swift 5.1に対応するswift-5.1-branch
ブランチがあり、後者を試している。
インストール
HomebrewでインストールしたかったのでFormulaを用意した。
$ brew install https://gist.githubusercontent.com/cockscomb/183acd19d2f5e127045dc43c6c472535/raw/63c935672b4e8c9d6f2056785283a6d6b7d31b77/swift-format.rb
ビルドに2分くらいかかった。
(もしかするとMintというのを使うといいのかもしれないが、試していない。)
ヘルプを見てみる。
$ swift-format --help
OVERVIEW: Format or lint Swift source code.
When no files are specified, it expects the source from standard input.
USAGE: swift-format [options] [filename or path ...]
OPTIONS:
--assume-filename When using standard input, the filename of the source to include in diagnostics.
--configuration The path to a JSON file containing the configuration of the linter/formatter.
--in-place, -i Overwrite the current file when formatting ('format' mode only).
--mode, -m The mode to run swift-format in. Either 'format', 'lint', or 'dump-configuration'.
--recursive, -r Recursively run on '.swift' files in any provided directories.
--version, -v Prints the version and exists
--help Display available options
POSITIONAL ARGUMENTS:
filenames or paths One or more input filenames
dump-configuration
swift-formatの設定は、JSONで表現される。デフォルトの設定は以下のようにdumpできる。
$ swift-format --mode dump-configuration > .swift-format
{
"blankLineBetweenMembers" : {
"ignoreSingleLineProperties" : true
},
"indentation" : {
"spaces" : 2
},
"indentConditionalCompilationBlocks" : true,
"lineBreakBeforeControlFlowKeywords" : false,
"lineBreakBeforeEachArgument" : false,
"lineLength" : 100,
"maximumBlankLines" : 1,
"respectsExistingLineBreaks" : true,
"rules" : {
"AllPublicDeclarationsHaveDocumentation" : true,
"AlwaysUseLowerCamelCase" : true,
"AmbiguousTrailingClosureOverload" : true,
"BeginDocumentationCommentWithOneLineSummary" : true,
"BlankLineBetweenMembers" : true,
"CaseIndentLevelEqualsSwitch" : true,
"DoNotUseSemicolons" : true,
"DontRepeatTypeInStaticProperties" : true,
"FullyIndirectEnum" : true,
"GroupNumericLiterals" : true,
"IdentifiersMustBeASCII" : true,
"MultiLineTrailingCommas" : true,
"NeverForceUnwrap" : true,
"NeverUseForceTry" : true,
"NeverUseImplicitlyUnwrappedOptionals" : true,
"NoAccessLevelOnExtensionDeclaration" : true,
"NoBlockComments" : true,
"NoCasesWithOnlyFallthrough" : true,
"NoEmptyTrailingClosureParentheses" : true,
"NoLabelsInCasePatterns" : true,
"NoLeadingUnderscores" : true,
"NoParensAroundConditions" : true,
"NoVoidReturnOnFunctionSignature" : true,
"OneCasePerLine" : true,
"OneVariableDeclarationPerLine" : true,
"OnlyOneTrailingClosureArgument" : true,
"OrderedImports" : true,
"ReturnVoidInsteadOfEmptyTuple" : true,
"UseEnumForNamespacing" : true,
"UseLetInEveryBoundCaseVariable" : true,
"UseShorthandTypeNames" : true,
"UseSingleLinePropertyGetter" : true,
"UseSynthesizedInitializer" : true,
"UseTripleSlashForDocumentationComments" : true,
"ValidateDocumentationComments" : true
},
"tabWidth" : 8,
"version" : 1
}
Documentation/Configuration.mdというドキュメントがある。
デフォルトでインデントがスペース2つだった。開発の最初期からスペース2つだったようで、要するにGoogleのSwift Style Guideに倣っているためである。SwiftUIやFunction buildersのようなDSL的なユースケースが増えてきたときに、インデントが深くなりやすいから、スペース2つの方がいいというトレンドになるかもしれない。しかしひとまず、一般的なスペース4つに変えた。
lint
$ swift-format --mode lint --configuration .swift-format --recursive .
けっこういろいろ出てくる。
--configuration .swift-format
は、ファイル名が.swift-format
の場合には省略できる。
SwiftLintを真似て、XcodeのBuild PhasesにRun Script Phaseを追加。
if which swift-format >/dev/null; then
swift-format --mode lint --recursive . || true
else
echo "warning: swift-format not installed"
fi
(lintが通らないと終了コードが1になり、後続のphaseに進まないので、|| true
しておくといい。)
こうすると、出力がXcodeの求める形式と合っているので、エディタにwarningが表示される。
OnlyOneTrailingClosureArgument
という、引数にクロージャが2つ以上あるときtrailing closureを許さないルールに引っかかる。可読性が落ちるので妥当なルールとも思うが、SwiftUIのTutorialをみると、Button
で使っているパターンである。action
のクロージャをメソッドとして切り出すのが正攻法だろうが、無効にしてもいいだろう。
NeverUseImplicitlyUnwrappedOptionals
というのでも引っかかる。var str: String!
のようなのは、あまり行儀がいいとはいえないものの、Xcodeのテンプレートでも使われるパターンである。部分的にswift-formatのルールを無効にできるといいのだが。
部分的なlintの無効化
masterにはDocumentation/IgnoringSource.mdというのがあった。以下のようなコメントを書くことで、コメントが書かれた次の行からASTで1ノード分、swift-formatが無視してくれるというものらしい。
使いたいけど、swift-5.1-branch
には入っておらず、Swift 5.1ではまだ使えなさそうだった。
format
swift-formatなので、もちろんformatできる。
$ swift-format --mode format --recursive --in-place .
Makefileも用意しておく。
.PHONY: format lint
format:
swift-format --mode format --recursive --in-place .
lint:
swift-format --mode lint --recursive .
おもしろいところでは、ネームスペース代わりのinit
を潰したstruct
をenum
に書き換えてくれた。
フォーマットの感じは悪くなく、(プロジェクトが小さいためか)パフォーマンスも特に気にならない。手元でちょっと使うのには適しているだろう。
CIで動かそうとするともうちょっと準備が必要で、競合するSwiftLintの方がノウハウが蓄積されていて便利かもしれない。
そのうち安定版がリリースされて、Swiftのツールチェーンにバンドルされたり、Xcodeとのインテグレーションが整備されたりすると、さらに使い勝手がよくなりそう。
追記
2019/12/19 18:50
--configuration [json file]
が、ファイル名が.swift-format
の場合に省略できる旨を追加し、全体的に省略するようにした。
参考