Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Format things with scalafmt

Format things with scalafmt

Rikito Taniguchi

November 10, 2018
Tweet

More Decks by Rikito Taniguchi

Other Decks in Technology

Transcript

  1. Format things
    with scalafmt
    Scala 関西 Summit 2018
    @tanishiking

    View full-size slide

  2. こんにちは
    - Twitter: tanishiking
    - GitHub: tanishiking
    - Hatena: tanishiking24
    株式会社はてな(京都オフィス)で働いています。
    Perl/TypeScript/Scala/Golang
    LinterやFormatterなどの開発支援ツールに興味があります。

    View full-size slide

  3. Agenda
    1. コードフォーマッタを導入する利点
    2. scalafmt の紹介
    ○ scalariform との違い / scalameta
    3. scalafmt の使い方
    4. Configuration
    ○ とりあえずこの辺設定すればOKというものをいくつか紹介
    します。

    View full-size slide

  4. Coding style どうしてる?
    - 特にルールはない
    - コードレビューでスタイルについて議論し
    てる?
    - チームにスタイルガイドがある?
    - フォーマッタをすでに導入している?

    View full-size slide

  5. 特にルールはない
    - 誰も気にしてないならそれはそれで良い
    - プロジェクトに人が増えるとカオスになるかも...?
    - 個々人が暗黙的なルールに従っている?

    View full-size slide

  6. コードレビューで何とかする
    レビュアーによって好みが違うかも
    スタイルに関する議論がレビューのたびに起こる
    - 「スペースよりタブを使うべきだ」
    - 「この行長すぎるので適当なところで改行してほしいです」
    - 「if の後にスペースがほしいです」
    - 「インデントの数がおかしそう」
    - 「import の順番アルファベット順にしたくないですか?」
    - 「=> を上の行の => と揃えてほしい」

    View full-size slide

  7. スタイルガイドを整備しても
    “any style guide written in English is
    either so brief that it’s ambiguous, or so
    long that no one reads it.”
    Bob Nystrom
    The Hardest Program I've Ever Written
    http://journal.stuffwithstuff.com/2015/09/08/the-hardest-program-ive-ever-written/

    View full-size slide

  8. エディタの設定を共有する?
    人によって使っているエディタや
    IDEは違う!

    View full-size slide

  9. Code Formatter!!
    “Spend more time discussing important
    issues in code review and less time on
    code style. Scalafmt formats code so that
    it looks consistent between people on
    your team.”
    scalafmt.org

    View full-size slide

  10. scalafmt
    - scala(とsbt)のためのコードフォーマッター
    - scalaプログラムの解析にscalametaを利用
    - 多機能!
    - プログラムの最大カラム数を指定できる(JSのprettierとか
    にもあるよね)
    - コメントとかのalignもできる!(gofmtとかもできるよね)
    - (両方あってすごい)

    View full-size slide

  11. scalariform or scalafmt
    - scalariform
    - 独自実装されたscalaのparserを利用
    - 2.12のsyntaxには未対応
    - scalafmt
    - scalametaというscalaのコードを解析するライブラリを利用
    - 最新のscalaのsyntaxにも対応、Dottyにも対応予定
    - maxColumn

    View full-size slide

  12. Install scalafmt
    - CLI
    - coursier
    - brew
    - SBT Plugin
    - sbt-scalafmt (こっちが公式)
    - neo-sbt-scalafmt
    - Editor/IDE plugin
    - IntelliJ plugin
    - vim-autofmt

    View full-size slide

  13. Install scalafmt (おすすめ)
    - CLI
    - coursier (coursier bootstrap で作ったlauncherをgit管理)
    - brew
    - SBT Plugin
    - sbt-scalafmt (こっちが公式)
    - neo-sbt-scalafmt
    - Editor/IDE plugin
    - IntelliJ plugin
    - vim-autofmt

    View full-size slide

  14. Configurations
    https://scalameta.org/scalafmt/docs/configuration.html
    - かなり多い。主要なものだけ紹介します。
    - maxColumn
    - continuationIndent
    - align
    - RewriteRules
    - verticalMultiline
    - trailingCommas

    View full-size slide

  15. maxColumn (default: 80)
    // before
    class ExtendTest extends A with B with C with D
    // after (maxCoulumn=20)
    class ExtendTest
    extends A
    with B
    with C
    with D
    ※必ず指定した値以内に収まるわけではないことに注意

    View full-size slide

  16. continuationIndent
    インデントのスペース数に関する設定
    - callSite (default: 2)
    - defnSite (default: 4)
    - extendsSite (default: 4)

    View full-size slide

  17. continuationIndent.callSite (default: 2)
    // before
    function(
    foo: Type,
    bar: Type
    )
    // after
    function(
    foo: Type,
    bar: Type
    )

    View full-size slide

  18. continuationIndent.defnSite (default: 4)
    // before
    def function(
    foo: Type,
    bar: Type
    )
    // after
    def function(
    foo: Type,
    bar: Type
    )

    View full-size slide

  19. continuationIndent.extendSite (default: 4)
    // before
    class UserProfile
    extends Profile
    with SomeTrait
    // after
    class UserProfile
    extends Profile
    with SomeTrait

    View full-size slide

  20. align
    「=」「<-」「%%」「//」などを縦方向で揃える設定
    - none
    - some (default)
    - more
    - most
    align.tokens で詳細を設定できるが、上記の4種類で十分

    View full-size slide

  21. align = none
    // before
    x match {
    case "foo" => 1 // comment
    case "hoge" => 123 // comment
    case "foobar" => 12345 // comment
    }
    // after
    x match {
    case "foo" => 1 // comment
    case "hoge" => 123 // comment
    case "foobar" => 12345 // comment
    }

    View full-size slide

  22. align = some (default)
    // before
    x match {
    case "foo" => 1 // comment
    case "hoge" => 123 // comment
    case "foobar" => 12345 // comment
    }
    // after (case match の => のみ align する)
    x match {
    case "foo" => 1 // comment
    case "hoge" => 123 // comment
    case "foobar" => 12345 // comment
    }

    View full-size slide

  23. align = more
    // before
    x match {
    case "foo" => 1 -> 1 // comment
    case "hoge" => 123 -> 123 // comment
    case "foobar" => 12345 -> 12345 // comment
    }
    // after (// や -> も)
    x match {
    case "foo" => 1 -> 1 // comment
    case "hoge" => 123 -> 123 // comment
    case "foobar" => 12345 -> 12345 // comment
    }

    View full-size slide

  24. align = more
    // before
    val foo = 1
    val hoge = 2
    libraryDependencies ++= Seq(
    "org.scala-lang" % "scala-compiler" % scalaVersion.value,
    "com.tanishiking" %% "scalaunfmt" % "0.0.1"
    )
    // after (= や sbtの % も)
    val foo = 1
    val hoge = 2
    libraryDependencies ++= Seq(
    "org.scala-lang" % "scala-compiler" % scalaVersion.value,
    "com.tanishiking" %% "scalaunfmt" % "0.0.1"
    )

    View full-size slide

  25. align = most
    // before
    for {
    x <- List()
    yyy = 2
    } yield {
    // ...
    }
    // after (<- と = をもalign)
    for {
    x <- List()
    yyy = 2
    } yield {
    // ...
    }

    View full-size slide

  26. RewriteRules
    - AvoidInfix
    - RedundantBraces
    - RedundantParens
    - SortModifiers
    - SortImports
    他にもいくつか...

    View full-size slide

  27. RewriteRules - AvoidInfix
    // before
    foo toList map { x =>
    x should have length 5
    }
    // after
    foo.toList.map { x =>
    (x should have).length(5)
    }

    View full-size slide

  28. RewriteRules - RedundantBraces
    // before
    val x: Int => String = {
    case 2 => { "two" }
    case _ => { "other" }
    }
    // after
    val x: Int => String = {
    case 2 => "two"
    case _ => "other"
    }

    View full-size slide

  29. RewriteRules - RedundantParens
    // before
    for {
    a <- b
    if ((b ++ b).length >= 2)
    } yield a
    // after
    for {
    a <- b
    if (b ++ b).length >= 2
    } yield a

    View full-size slide

  30. RewriteRules - SortModifiers
    // before
    final lazy private implicit val x = 42
    lazy final implicit private val y = 42
    // after
    implicit final private lazy val x = 42
    implicit final private lazy val y = 42
    sortModifiers.order = [“implicit”, “final” … ] で順番の設定も可能

    View full-size slide

  31. RewriteRules - SortImports
    // before
    import a.b.{c, a, b}, k.{
    g, f
    }
    // after
    import a.b.{a, b, c}, k.{f, g}

    View full-size slide

  32. verticalMultiline
    1.6.0-RC1 からの機能
    カラム数が `maxColumn` を超えたときのための設定
    - newlineBeforeImplicitKW
    - newlineAfterImplicitKW
    - newlineAfterOpenParen

    View full-size slide

  33. newlineBeforeImplicitKW (default: false)
    // before
    override def load()(implicit taskCtx: Context,
    ec: ExecutionContext
    ): Future[Seq[A] Or B]
    // after
    override def load()(
    implicit taskCtx: Context,
    ec: ExecutionContext
    ): Future[Seq[A] Or B]

    View full-size slide

  34. newlineAfterImplicitKW (default: false)
    // before
    override def load()(implicit taskCtx: Context,
    ec: ExecutionContext
    ): Future[Seq[A] Or B]
    // after
    override def load()(implicit
    taskCtx: Context,
    ec: ExecutionContext
    ): Future[Seq[A] Or B]

    View full-size slide

  35. trailingCommas
    - 1.6.0-RC1 からの新機能
    - 1.6.0-RC4時点ではまだ不安定、1.6.0 までお待ちを...
    - never
    - preserve (default)
    - always

    View full-size slide

  36. 結局どういう設定が良いのか
    作者いわくおすすめはdefault
    個人的には今日紹介した設定を好みに合わせて設定するのがオ
    ススメ

    View full-size slide

  37. diffをできるだけ少なくしたい?
    https://github.com/mikr/whatstyle
    $ python whatstyle.py --formatter scalafmt
    --ignoreopts IGNOREOPTS
    - diffの少ない設定を生成してくれる
    - 他のフォーマッタ(rustfmt, scalariform, clang-format な
    ど)にも対応
    - フォーマッタ本体と独立している

    View full-size slide

  38. diffをできるだけ少なくしたい?
    https://github.com/tanishiking/scalaunfmt
    maxColumn = [80, 100]
    align = [some, more, most]
    continuationIndent.callSite = [2, 4]
    continuationIndent.defnSite = [2, 4]
    verticalMultiline.newlineAfterImplicitKW = [true, false]
    $ scalaunfmt --config .scalaunfmt.conf test1.scala test2.scala
    - 設定の候補を(ホワイトリスト形式で)選べる
    - JVM warmup によるボトルネックがなく高速(whatstyle比)

    View full-size slide

  39. scalafmt is not perfect
    - https://gitter.im/scalameta/scalafmt
    - https://github.com/scalameta/scalafmt/issues
    質問はGitter、バグ報告はissueで

    View full-size slide

  40. まとめ
    - コードフォーマッタを導入することでstyleに関する議論や注意
    を減らし、本質的な問題に注目できる。
    - scalafmtの導入は
    - coursier bootstrap
    - sbt-scalafmt
    - おすすめ設定は基本default、好みで align,
    continuationIndent など
    - 質問はgitterまで!

    View full-size slide

  41. FAQ
    - JVM起動がボトルネックでscalafmtの起動が遅い
    - nailgun
    - SubstrateVM
    - githookでscalafmtしたい
    - https://medium.com/zyseme-technology/code-formatting-scalafmt-a
    nd-the-git-pre-commit-hook-3de71d099514
    - sbtマルチプロジェクトで設定共有したい
    - common project で scalafmt.conf を生成するタスクを scalafmtConfigに依存させると吉
    - コンパイル時にscalafmt走らせたい
    - scalafmtOnCompile (sbt-scalafmt)

    View full-size slide