0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

scalafmtのはじめかた

この記事は ウェブクルー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に以下を追加
plugin.sbt
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0")
  • sbt scalafmtAllなどで全コードを整形できる
  • build.sbtにscalafmtOnCompile = trueでコンパイル時フォーマットされるが公式非推奨(undoのバッファーがぐちゃったり、コンパイル時間に影響するのでエディターの保存時フォーマットを使った方がよいらしい)

.scalafmt.confの書き方

とりあえずの推奨設定

scalafmt.conf
project.git = true 
project.excludeFilters = ["app/hoge"] 
maxColumn = 120 //初期設定だとmaxColumn=80

project.gitはトラッキングされているファイルのみフォーマット
project.excludeFiltersは指定したパス以下のファイルのフォーマットを無視するもの
自動生成されたコードを除いたりするための初期設定。
maxColumnは一行の上限文字数でデフォルトの80はgithub mobileやノートPCの画面にフィットするらしい
長いと後述するverticalMultilineで設定でフォーマットされる

自分がしている設定

scalafmt.conf
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 でコード整形&コミット

scalafmt.yml
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さんです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
0
Help us understand the problem. What are the problem?