この記事は ウェブクルーAdvent Calendar 2020 11日目の記事です。
昨日は @tomoyuki-matsuda さんのエンジニア採用の知識0から採用担当として 今までやってきたことでした。
scalafmtとは
- scalaのソースコード整形ツール(類似ツール:prettierやrustfmtなど)
- intelliJやVSCodeなどのeditorやsbtなどから使用できる
導入方法
intelliJの場合
- .scalafmt.confをprojectディレクトリ下に配置してポップアップで出てくるUse scalafmt formatterをクリックする
- 保存時に自動整形するにはPreferences > Editor > Code Style > ScalaからReformat on saveをチェックする
VSCodeの場合
- Language serverとしてMetalsを使用し.scalafmt.confを書き、settigsのScalafmt config pathにパスを入力する
sbtの場合
- plugins.sbtに以下を追加
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0")
- sbt scalafmtAllなどで全コードを整形できる
- build.sbtにscalafmtOnCompile = trueでコンパイル時フォーマットされるが公式非推奨(undoのバッファーがぐちゃったり、コンパイル時間に影響するのでエディターの保存時フォーマットを使った方がよいらしい)
.scalafmt.confの書き方
とりあえずの推奨設定
project.git = true
project.excludeFilters = ["app/hoge"]
maxColumn = 120 //初期設定だとmaxColumn=80
project.gitはトラッキングされているファイルのみフォーマット
project.excludeFiltersは指定したパス以下のファイルのフォーマットを無視するもの
自動生成されたコードを除いたりするための初期設定。
maxColumnは一行の上限文字数でデフォルトの80はgithub mobileやノートPCの画面にフィットするらしい
長いと後述するverticalMultilineで設定でフォーマットされる
自分がしている設定
continuationIndent {
callSite = 2
defnSite = 2
extendSite = 2
}
align = some
align {
tokens.add = [
{ code = "=", owner = "Term.Assign" }
]
}
newlines {
alwaysBeforeTopLevelStatements = true
}
verticalMultiline {
atDefnSite = true
arityThreshold = 2
newlineAfterOpenParen = true
newlineBeforeImplicitKW = true
newlineAfterImplicitKW = true
excludeDanglingParens = []
}
includeCurlyBraceInSelectChains = false
includeNoParensInSelectChains = false
- continuationIndent.callSiteは呼び出し時の引数のインデント
function(
arg1, // 2文字分のインデント
arg2
)
- continuationIndent.defnSiteは定義時の引数インデント
def function(
argument1: Type1 // 2文字分のインデント
): ReturnType
- continuationIndent.extendSiteはextendsのインデント
書いていないがcontinuationIndent.withSiteRelativeToExtendsでwithのインデントを設定できextends A with B のインデントはデフォルトではextendsに揃えられる
trait Foo
extends A
with B
- align = none|some|more|mostは=や=>などを揃えられる。someの場合はcase arrowのみ揃えられる
x match {
case 2 => 22
case 22 => 222
}
- align.tokensは設定した字句について揃えてくれる。
例えばalign.tokens = [{code = "=>", owner = "Case"}]
はCase文内の"=>"を揃えてくれる
初期設定はAlignToken.scalaのdefaultを参照。
ownerはscalametaを見るとよい - align.tokens.addはdefaultのtokenなどにたいして別ルールを追加するときに使われる
置き換えをしたいときにはalign.tokensを宣言しなおすのがよい
align.tokens系は細かい設定をしたいとき以外に使わなくて良さそう - newlines.topLevelStatements = [after]はobjectなどのtoplevelStatementと呼ばれるものの後に(beforeなら前に)空行を入れる設定
import org.scalafmt
package core {
class C1 {} //beforeだとこの後に空行が入る
object O {
val x1 = 1
val x2 = 2
def A = "A"
def B = "B"
}
// ここの空行がフォーマット後に入る
class C2 {}
}
- verticalMultiline.arityThresholdはこれを超えた文字数の行がvertilacMultilineで設定されたフォーマットされる
- verticalMultiline.atDefnSiteはfalseだとvertivalMultilineの設定でフォーマットしない(多分)
- verticalMultiline.newlineAfterOpenParenはdef x()()の引数を改行する(多分)
- vertical.newlineBeforeImplicitKW,vertical.newlineAfterImplicitKWは(implicit xxx)のxxxの前後に空行の有無の設定
- vertical.excludeDanglingParens = []は"trait"や"class"を設定するとclass()の")"などを改行しないようになる。デフォルトでは"trait"と"class"が設定されているので")"を改行したい場合は[]を設定する必要がある
class X(
A:Type1,
B:Type2 //vertical.excludeDanglingParens=["class"]だと ")"がここに配置される
)
- includeCurlyBraceInSelectChainsはmapなどでの中括弧で改行するかを設定する
//true
List(1)
.map { x =>
x + 2
}
.filter(_ > 2)
//false
List(1).map { x =>
x + 2
}
.filter(_ > 2)
- includeNoParensInSelectChainsは()がないapplyなどで改行するかを設定できる
//true
List(1)
.toIterator
.buffered
.map(_ + 2)
.filter(_ > 2)
//false
List(1).toIterator.buffered
.map(_ + 2)
.filter(_ > 2)
このほかにもcommentやspace,書き換えなど(point free記法やimport a.{b,c}を分割)がある
公式doc
Github Actionsによる整形
githubでコードの差分が出ないようにPR時にGithub Actionでコードフォーマットをしてみます。
javascript+prettierでのコードフォーマットを参考にして実装してみます。
GitHub にコード整形してもらおう - GitHub Actions でコード整形&コミット
name: Scala PR format CI
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Run sbt format
run: sbt scalafmtAll
- uses: stefanzweifel/git-auto-commit-action@v3.0.0
with:
commit_message: Apply scalafmt Change
ref: ${{ github.head_ref }}
上記を.github/workflow以下に配置するとpull_request作成時やpull_request作成後push時にjobが走って整形後の差分をコミットしてくれます。
まとめ
scalafmtを使ってある程度自分の好みに合わせてコードフォーマッティングができるようになります。
またローカル環境毎に異なったscalafmt.confを使っていてもGithub Actionsで差分を減らすことができます。
Webcrew2020アドベントカレンダー12日目担当は@wc-kobayashiTさんです。