Elm
elm-analyse
Elm 2Day 3

Elm Analyse でコード品質を担保する

Elm2 アドベントカレンダー 2017 の3日目です。

Elm Analyse は Elm コードの良くない匂いを嗅ぎつけて指摘してくれるツールです。

使い方

npm (または yarn)でインストールします。

npm install -g elm-analyse

プロジェクトのルートディレクトリでコマンドを打つと警告が一覧で出力されます。

elm-analyse

(場合によっては多少待ちます)

チェック事項

以下の項目をチェックします。

CoreArrayUsage
Elm 0.18 の Array にはバグがあるので、今は Skinney/elm-array-exploration を使った方がいい(0.19 でコアにマージされます)。

DebugCrash
Debug.crash が使われている。

DebugLog
Debug.log が使われている。

DropConcatOfLists
リストを無駄な結合 [1,2,3] ++ [4,5][1,2,3,4,5] のようにまとめた方が良い。

DropConsOfItemAndList
リストを無駄な結合 1::[2,3,4][1,2,3,4] のようにまとめた方が良い。

DuplicateImport
同じモジュールを2回インポートしている。

-- BAD
import Html exposing (text)
import Html exposing (Html)

DuplicateImportedVariable
同じ値を2度インポートしている。

-- BAD
import Html exposing (Html, text, Html)

DuplicateRecordFieldUpdate
レコードの同じフィールドを重複して更新している。

-- BAD
{ person | name = "John", name = "Jane" }

ExposeAll
できる限りモジュールが公開するインターフェイスを絞った方が良い。

-- BAD
module Foo exposing (..)

NoUncurriedPrefix
(++) "Hello " "World" のように中置演算子を無駄に関数として使わず、素直に "Hello " ++ "World" とした方が良い。

FunctionInLet
let ~ in 中に関数を宣言する必要はあまりないので、トップレベルに置けるなら置いた方が良い。

-- BAD
foo : Int -> Int
foo x =
    let
        somethingIShouldDefineOnTopLevel : Int -> Int
        somethingIShouldDefineOnTopLevel y =
            y + 1
    in
        somethingIShouldDefineOnTopLevel x

ImportAll
ワイルドカードで import すると他の人が読んだときにどのモジュールの関数かわかりにくくなるので、避けた方がいい。

-- BAD
import Html exposing (..)

LineLengthExceeded
一行の文字数が長すぎる(デフォルトの閾値は 150 文字)

MultiLineRecordFormatting
レコードは複数行で宣言した方が読みやすい。

-- BAD
type alias Person =
    { name : String , age : string , address : Address }

-- GOOD
type alias Person =
    { name : String
    , age : string
    , address : Address
    }

NoTopLevelSignature
トップレベルの関数や値には型をつけた方がいい。

-- BAD
foo =
    1

NonStaticRegex
動的に正規表現を作ると実行時エラーを起こす可能性があるため、トップレベルに宣言した方がいい。

-- BAD
foo x =
    let
        myInvalidRegex = Regex.regex "["
    in
        (myInvalidRegex, x)

RedefineVariable
同じ変数名で外の変数を上書きしない方が混乱が少ない。

-- BAD
foo : Maybe Int -> Int
foo x =
    case x of
        Just x ->
            x
        Nothing ->
            1

SingleFieldRecord
1つのフィールドしかないレコードは obsolete (廃れた習慣?)だ。(意図が良くわからないが Haskell でそういう書き方をするから?)

-- BAD
type Model =
    Model { input : String }

TriggerWords
TODOFIXME のようなコメントを残したままにしない方が良い(単語は設定で変えられる)。

UnformattedFile
elm-format されていない。

UnnecessaryListConcat
不必要に List.concat している。単に ++ で繋げた方が良い。

-- BAD
foo : List Int
foo =
    List.concat [ [ 1, 2 ,3 ], [ a, b, c] ]

UnnecessaryParens
不必要な括弧は取り除いた方が良い。(それでも括弧が好きな人は Lisp をやろうと書いてある)

-- BAD
someCall =
    (foo 1) 2

UnnecessaryPortModule
port module と不必要に宣言されている。

-- BAD
port module Foo exposing (notAPort)

notAPort : Int
notAPort = 1

UnusedImport
インポートされているが使われていないモジュールがある。

-- BAD
import Unused

UnusedImportAlias
import ... as ... でエイリアスをつけたモジュール名が使われていない。

module Foo exposing (main)

-- BAD( H が使われていない)
import Html as H exposing (Html, text)

main : Html a
main =
    text "Hello"

UnusedImportedVariable

module Foo exposing (thing)

-- BAD ( div が使われていない)
import Html exposing (Html, div, text)

main : Html a
main =
    text "Hello World!"

UnusedPatternVariable
パターンマッチで束縛した変数が使われていない。

-- BAD ( age が使われていない)
sayHello {name, age} = "Hello " ++ name

UnusedTopLevel
トップレベルの関数が使われていない。

UnusedTypeAlias
type alias を宣言しているが使われていない。

UnusedVariable
変数が使われていない。

-- BAD
foo : String -> Int
foo x =
    1

UseConsOverConcat
[ a ] ++ foo ではなく a :: foo と書いた方がいい。

チェック項目の設定

異議あり!という項目が多々あったと思うので、プロジェクトのルートディレクトリに elm-anayse.json を置いてください。true のものだけがチェックされます。

{
  "checks": {
    "DebugCrash": false,
    "DebugLog": false,
    "DropConcatOfLists": false,
    "DropConsOfItemAndList": false,
    "DuplicateImport": true,
    "DuplicateImportedVariable": true,
    "ExposeAll": false,
    "ImportAll": false,
    "LineLengthExceeded": false,
    "MultiLineRecordFormatting": false,
    "NoTopLevelSignature": false,
    "NoUncurriedPrefix": true,
    "NonStaticRegex": true,
    "RedefineVariable": false,
    "SingleFieldRecord": false,
    "UnnecessaryListConcat": false,
    "UnnecessaryParens": true,
    "UnusedImport": true,
    "UnusedImportAlias": true,
    "UnusedImportedVariable": true,
    "UnusedPatternVariable": false,
    "UnusedTopLevel": true,
    "UnusedTypeAlias": true,
    "UnusedVariable": false,
    "UseConsOverConcat": false,
    "UnformattedFile": true
  },
  "TriggerWords": {
    "words": ["todo", "fixme"]
  }
}

レポートをブラウザで表示

--serve オプションで結果をブラウザで表示します。

elm-analyse --serve

localhost:3000 にアクセスすると、警告の一覧やモジュールの依存関係が表示されます。

Screen Shot 2017-12-03 at 1.10.26.png

問題のコードの位置を教えてくれるのが嬉しかったりします。

Screen Shot 2017-12-03 at 1.07.46.png

まとめ

チーム開発のときにプロジェクトでルールを決めて使うと、自動でチェックできて便利かもしれませんよ!