かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その33 )( ESLint を導入する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その32 )( npm の admin-lte package から jQuery がなくなっていたので対応する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Geb のテストを書き進める前に ESLint の導入の仕方を知りたくなったので、先に導入します。
    • 以下の内容で導入します。
      • eslint-config-airbnb-base を利用します。
      • jQuery 用の設定に変更します。

参照したサイト・書籍

  1. ESLint - Pluggable JavaScript linter
    https://eslint.org/

  2. 愚直にESLintを導入した話
    http://tech.mercari.com/entry/2017/07/31/170125

  3. eslint-config-airbnb-base
    https://www.npmjs.com/package/eslint-config-airbnb-base

  4. webpack から ESLint を使う設定
    https://www.pupha.net/archives/3289/

  5. eslint-loader
    https://www.npmjs.com/package/eslint-loader

  6. ESLintでReactとES2015の構文チェック(eslint-config-airbnb)
    http://dackdive.hateblo.jp/entry/2016/05/13/094000

  7. ESLint - Rules
    https://eslint.org/docs/rules/

  8. Can I use eslint-config-airbnb without eslint-plugin-react?
    https://github.com/airbnb/javascript/issues/451

  9. ESLint dollar($) is not defined. (no-undef)
    https://stackoverflow.com/questions/39510736/eslint-dollar-is-not-defined-no-undef

  10. Airbnb JavaScript Style Guide
    https://github.com/airbnb/javascript

目次

  1. ESLint をインストールする
  2. eslint --init を実行する
  3. ECMAScript 6 用の Rule を含まない airbnb-baseairbnb-base/legacy に変更する
  4. webpack から eslint が実行されるよう設定する
  5. 動作確認
  6. インデントと改行コードと文字列のクォーテーションを airbnb-base のものではなく独自ルールに変更する
  7. 修正を進める前に IntelliJ IDEA の ESLint の機能を有効にする
  8. 多めに出力されているメッセージに対応する
    1. jquery.autoKana.js をチェック対象から外す
    2. app.js を削除する
    3. '$' is not defined
    4. Unexpected unnamed function
    5. All 'var' declarations must be at the top of the function scope
    6. Expected a newline after '('
    7. Strings must use doublequote
    8. 'errmsg' used outside of binding context
    9. 'event' is defined but never used
    10. Missing semicolon
    11. Unexpected use of 'location'
  9. 各ファイル毎の出力されているメッセージに対応する
    1. src/main/assets/js/inquiry/input01.js
    2. src/main/assets/js/inquiry/input02.js
    3. src/main/assets/js/lib/class/Form.js
    4. src/main/assets/js/lib/util/converter.js
  10. 最後に

手順

ESLint をインストールする

npm install --save-dev eslint コマンドを実行してインストールします。

f:id:ksby:20171103080757p:plain f:id:ksby:20171103080915p:plain

eslint --init を実行する

git コマンドを実行するために使用している git-cmd.exe を起動してから ./node_modules/.bin/eslint --init コマンドを実行します。CUI で4つ質問事項が表示されますので、下の画像の上の方に表示されている内容を回答します。

  • How would you like to configure ESLint? --> Use a popular style guide
  • Which style guide do you want to follow? --> Airbnb
  • Do you use React? --> No
  • What format do you want your config file to be in? --> JavaScript

f:id:ksby:20171103081501p:plain

https://eslint.org/docs/user-guide/getting-started に書かれていたコマンドが ./node_modules/.bin/eslint --init だったので git-cmd.exe から実行しましたが、/ --> \ に変更してコマンドプロンプトから実行しても同じように動作します。

質問に回答すると npm で eslint-config-airbnb-base と eslint-plugin-import の package がインストールされて、package.json が以下のように変更され、

    ..........
    "cssnano": "^3.10.0",
    "eslint": "^4.10.0",
    "eslint-config-airbnb-base": "^12.1.0",
    "eslint-plugin-import": "^2.8.0",
    "http-proxy-middleware": "^0.17.4",
    ..........
  • 以下の2行が追加されていました。
    • "eslint-config-airbnb-base": "^12.1.0"
    • "eslint-plugin-import": "^2.8.0"

プロジェクトのルート直下に以下の内容の .eslintrc.js が生成されます。

module.exports = {
    "extends": "airbnb-base"
};

ECMAScript 6 用の Rule を含まない airbnb-baseairbnb-base/legacy に変更する

node_modules/eslint-config-airbnb-base/index.js は ECMAScript 6 用の Rule が記述されている './rules/es6' が含まれていますが、

module.exports = {
  extends: [
    './rules/best-practices',
    './rules/errors',
    './rules/node',
    './rules/style',
    './rules/variables',
    './rules/es6',
    './rules/imports',
  ].map(require.resolve),
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
    ecmaFeatures: {
      experimentalObjectRestSpread: true,
    },
  },
  rules: {
    strict: 'error',
  },
};

'./rules/es6' を含まない node_modules/eslint-config-airbnb-base/legacy.js というファイルもあります。

module.exports = {
  extends: [
    './rules/best-practices',
    './rules/errors',
    './rules/node',
    './rules/style',
    './rules/variables'
  ].map(require.resolve),
  env: {
    browser: true,
    node: true,
    amd: false,
    mocha: false,
    jasmine: false
  },
  rules: {
    'comma-dangle': ['error', 'never'],
    'prefer-numeric-literals': 'off',
    'no-restricted-properties': ['error', {
      object: 'arguments',
      property: 'callee',
      message: 'arguments.callee is deprecated',
    }, {
      property: '__defineGetter__',
      message: 'Please use Object.defineProperty instead.',
    }, {
      property: '__defineSetter__',
      message: 'Please use Object.defineProperty instead.',
    }],
  }
};

今回 ECMAScript 6 は使用していないので、legacy.js を使用するよう .eslintrc.js を以下のように変更します。

module.exports = {
    "extends": "airbnb-base/legacy"
};
  • airbnb-baseairbnb-base/legacy に変更します。

webpack から eslint が実行されるよう設定する

npm install --save-dev eslint-loader コマンドを実行して eslint-loader をインストールします。

f:id:ksby:20171103085120p:plain

webpack.config.js を以下のように変更します。

module.exports = {
    ..........
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: "eslint-loader"
            }
        ]
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery"
        })
    ]
};
  • module: { ... } を追加します。

動作確認

npm run spingboot コマンドを実行して webpack を実行してみると、なんか大量にメッセージが出力されました。。。

f:id:ksby:20171104034328p:plain

改行コードとインデントと文字列のクォーテーションで結構メッセージが表示されているようなので、まずは eslint のルールを今 js ファイルを書いているルール(4インデント+CRLF+ダブルクォーテーション)に変更できないか調べてみます。

インデントと改行コードと文字列のクォーテーションを airbnb-base のものではなく独自ルールに変更する

カスタマイズは .eslintrc.js に "rules": { ... } で記述すればできるようなので、.eslintrc.js を以下のように変更します。

module.exports = {
    "extends": "airbnb-base",
    "rules": {
        // this option sets a specific tab width for your code
        // http://eslint.org/docs/rules/indent
        indent: ['error', 4, {
            SwitchCase: 1,
            VariableDeclarator: 1,
            outerIIFEBody: 1,
            // MemberExpression: null,
            FunctionDeclaration: {
                parameters: 1,
                body: 1
            },
            FunctionExpression: {
                parameters: 1,
                body: 1
            },
            CallExpression: {
                arguments: 1
            },
            ArrayExpression: 1,
            ObjectExpression: 1,
            ImportDeclaration: 1,
            flatTernaryExpressions: false,
            ignoredNodes: ['JSXElement', 'JSXElement *']
        }],

        // disallow mixed 'LF' and 'CRLF' as linebreaks
        // http://eslint.org/docs/rules/linebreak-style
        'linebreak-style': ['error', 'windows'],

        // specify whether double or single quotes should be used
        quotes: ['error', 'double', { avoidEscape: true }]
    }
};
  • 改行コードとインデントと文字列のクォーテーションの設定は全て node_modules/eslint-config-airbnb-base/rules/style.js の中に書かれていたので、そこからコピーして設定を変更します。
  • indent の設定は 2-space indent 以外にもいろいろ設定が入っていたので、コピーして 24 のみ変更します。
  • linebreak-style の設定を unixwindows に変更します。
  • quotes の設定を singledouble に変更します。

再度 npm run spingboot コマンドを実行すると改行コードとインデントと文字列のクォーテーションではメッセージが出なくなりました。

f:id:ksby:20171104040602p:plain

修正を進める前に IntelliJ IDEA の ESLint の機能を有効にする

IntelliJ IDEA には ESLint を利用してソースコードをチェックする機能があるので、それを有効にします。

メインメニューから「File」-「Settings...」を選択します。「Settings」ダイアログを表示されるので、画面左上の検索フィールドに "eslint" と入力します。

ESLint の設定画面が表示されるので、以下の画面のように設定します。

f:id:ksby:20171104134640p:plain

  • 「Enable」をチェックします。
  • 「Node interpreter」に "node.exe" のパスを設定します。
  • 「Configuration file」はデフォルトの「Automatic search」を選択したままです。
  • 「Additional rules directory」「Extra eslint options」は何も設定しません。

「OK」ボタンを押してダイアログを閉じた後 js のソースコードを表示すると、以下の画像のように ESLint のチェックに引っかかった行の右側に赤線が表示され、マウスオーバーすると ESLint のメッセージが表示されます。

f:id:ksby:20171104135904p:plain

またエディタ上で右クリックしてコンテキストメニューを表示した後「Analyze」-「Inspect Code...」を選択し、

f:id:ksby:20171104140142p:plain

「Specify Inspection Scope」ダイアログが表示されたらそのまま「OK」を押すと、

f:id:ksby:20171104140341p:plain

画面下部に Inspection の結果が表示されます。この中に「Code quality tools」という項目があり、

f:id:ksby:20171104140639p:plain

展開すると ESLint のメッセージと該当箇所が表示されます。

f:id:ksby:20171104140943p:plain

多めに出力されているメッセージに対応する

ざっと見てまだ多めに出ているメッセージから対応します。

jquery.autoKana.js をチェック対象から外す

jquery.autoKana.js がチェックされてメッセージが出ていましたので、チェック対象から外します。webpack.config.js を以下のように変更します。

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: [
                    /node_modules/,
                    /jquery.autoKana.js$/
                ],
                loader: "eslint-loader"
            }
        ]
    },
  • exclude に /jquery.autoKana.js$/ を追加します。

app.js を削除する

src/main/assets/js/app.js もチェックされていましたが、このファイルは最初に作成したサンプルでもう不要なので削除します。

app.js を削除した後、webpack.config.js を以下のように変更します。

module.exports = {
    entry: {
        "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
        "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
        "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"],
        "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"]
    },
  • "js/app": ["./src/main/assets/js/app.js"] を削除します。

'$' is not defined

ESLint dollar($) is not defined. (no-undef) を見ると "jquery": true を定義すればチェックされないようになるようです。.eslintrc.js を以下のように変更します。

module.exports = {
    "extends": "airbnb-base/legacy",
    "env": {
        "jquery": true
    },
    ..........
  • "env": { "jquery": true } を追加します。

Unexpected unnamed function

無名関数は多用するので、この Rule は無効にします。.eslintrc.js を以下のように変更します。

    "rules": {
        // require function expressions to have a name
        // http://eslint.org/docs/rules/func-names
        'func-names': 'off',
  • 'func-names': 'off' を追加します。

All 'var' declarations must be at the top of the function scope

変数宣言は関数スコープ内の最初に行うこと、というメッセージのようですが、エラーが出ている原因は require("vendor/autokana/jquery.autoKana.js");require("jquery-ui/ui/widgets/autocomplete.js"); を上に書いているので、その下の var で宣言している部分でチェックに引っかかっているためのようです。

var より require を上にまとめておきたいので、この Rule は無効にします。.eslintrc.js を以下のように変更します。

    "rules": {
        // requires to declare all vars on top of their containing scope
        'vars-on-top': 'off',
  • 'vars-on-top': 'off' を追加します。

Expected a newline after '('

https://eslint.org/docs/rules/function-paren-newline を見ると、( の後に続けて引数を記述するのではなく、一旦改行をして次の行から書くこと、というメッセージのようです。なんか縦に長くなってソースが見にくくなる気しかしないので、これは無効にします。

    "rules": {
        ..........

        // require function expressions to have a name
        // http://eslint.org/docs/rules/func-names
        'func-names': 'off',

        // enforce consistent line breaks inside function parentheses
        // https://eslint.org/docs/rules/function-paren-newline
        'function-paren-newline': 'off',

        // this option sets a specific tab width for your code
        // http://eslint.org/docs/rules/indent
        indent: ['error', 4, {
  • 'function-paren-newline': 'off' を追加します。

Strings must use doublequote

文字列のクォーテーションはダブルクォーテーションを使用するルールを指定しましたが、シングルクォーテーションになっているところがありました。こちらは全てダブルクォーテーションに修正します。

'errmsg' used outside of binding context

src/main/assets/js/inquiry/input02.js で errmsg 変数を block スコープ毎に var で宣言していたので引っかかっているようです。関数の最初で var で宣言するように修正します。

'event' is defined but never used

イベントハンドラの無名関数の引数に使用している/いないに関わらず event という引数を書いていたので、そこで引っかかっているようです。使用されていない event 引数は削除します。

Missing semicolon

単純に末尾のセミコロンのつけ忘れです。修正します。

Unexpected use of 'location'

node_modules/eslint-config-airbnb-base/rules/variables.js を見ると以下のように定義されており、

const restrictedGlobals = require('eslint-restricted-globals');

module.exports = {
  rules: {
    ..........

    // disallow specific globals
    'no-restricted-globals': ['error', 'isFinite', 'isNaN'].concat(restrictedGlobals),

node_modules/eslint-restricted-globals/index.js には以下のように定義されていました。

module.exports = [
    ..........
    'length',
    'location',
    'locationbar',

locationグローバル変数として使用すると no-restricted-globals の Rule に引っかかりエラーになるようです。

ソースコードを修正して対応します。locationwindow.location に変更します。window は node_modules/eslint-restricted-globals/index.js に定義されておらず、グローバル変数として利用しても Rule に引っかかりません。

各ファイル毎の出力されているメッセージに対応する

src/main/assets/js/inquiry/input01.js

  • 'idList' is assigned a value but never used は宣言した変数を関数内で使用していないことが原因なので、変数を宣言している行を削除します。
  • 'validator' is already declared in the upper scope はソースの上で宣言している var validator = require("lib/util/validator.js"); と同じ validator という変数を使用していたので、validatorvalidateFunction に変更します。

src/main/assets/js/inquiry/input02.js

  • 'validator' is already declared in the upper scope は input01.js と同様に validatorvalidateFunction に変更します。
  • A space is required after '{'A space is required before '}'IntelliJ IDEA のフォーマッターがスペースを入れないようになっているので、この Rule を無効に変更します。.eslintrc.js を以下のように変更します。
    "rules": {
        ..........

        // require padding inside curly braces
        'object-curly-spacing': ['error', 'never']
    }
  • 'event' is already declared in the upper scopeevente に変更します。

src/main/assets/js/lib/class/Form.js

  • 'Form' was used before it was definedmodule.exports = Form; の後に function Form(idList) { ... } の記述があるために出力されていますが、module.exports の記述を先頭にしたいので .eslintrc.js に no-use-before-define の設定を node_modules/eslint-config-airbnb-base/rules/variables.js からコピーして functions: truefunctions: false に変更します。
    "rules": {
        ..........

        // disallow use of variables before they are defined
        'no-use-before-define': ['error', { functions: false, classes: true, variables: true }]
    }
  • Line 21 exceeds the maximum line length of 100 は1行の最大値を 120 に変更したいので、.eslintrc.js に max-len の設定を node_modules/eslint-config-airbnb-base/rules/style.js からコピーして 100120 に変更します。
    "rules": {
        ..........

        // specify the maximum length of a line in your program
        // http://eslint.org/docs/rules/max-len
        'max-len': ['error', 120, 2, {
            ignoreUrls: true,
            ignoreComments: false,
            ignoreRegExpLiterals: true,
            ignoreStrings: true,
            ignoreTemplateLiterals: true
        }]
    }
  • Assignment to property of function parameter 'form' は関数の引数で渡された変数を変更しようとしているから出ているメッセージらしいのですが、form を引数に渡さないようにする方法が今だによく分かっていないので、ソースの先頭に /* eslint-disable no-param-reassign,lines-around-directive */ を記述して Form.js だけこの Rule を無効にします。

src/main/assets/js/lib/util/converter.js

  • Multiple spaces found before ... はソースの右側にスペース複数を入れてからコメントを書いていたためでした。コメントは移動してコメントだけの行になるようにします。

以上で全ての対応が完了です。

最後に

動きはしていたので今回はリンターを導入してもそんなに指摘は受けないかな、と思っていましたが、いつものように結構エラーが出ました。。。 ESLint は eslint-config-airbnb や eslint-config-airbnb-base といった使える設定が公開されているし、導入例の記事もいろいろ公開されていたので導入しやすかったです。また Airbnb JavaScript Style Guide は有名なようなので、普段 Javascript を実装しない自分としては読んでおきたいと思います。

また今回の調査をしていた時に prettier という Javascript のフォーマッターの記事を見かけました。Using External tools: ESLint autofix, React Native and Prettier を見ると IntelliJ IDEA でもサポートされているようなので、時間があれば見てみたいと思います。

最後に .eslintrc.js の最終版を載せておきます。

module.exports = {
    "extends": "airbnb-base/legacy",
    "env": {
        "browser": true,
        "jquery": true
    },
    "rules": {
        // requires to declare all vars on top of their containing scope
        'vars-on-top': 'off',

        // require function expressions to have a name
        // http://eslint.org/docs/rules/func-names
        'func-names': 'off',

        // enforce consistent line breaks inside function parentheses
        // https://eslint.org/docs/rules/function-paren-newline
        'function-paren-newline': 'off',

        // this option sets a specific tab width for your code
        // http://eslint.org/docs/rules/indent
        indent: ['error', 4, {
            SwitchCase: 1,
            VariableDeclarator: 1,
            outerIIFEBody: 1,
            // MemberExpression: null,
            FunctionDeclaration: {
                parameters: 1,
                body: 1
            },
            FunctionExpression: {
                parameters: 1,
                body: 1
            },
            CallExpression: {
                arguments: 1
            },
            ArrayExpression: 1,
            ObjectExpression: 1,
            ImportDeclaration: 1,
            flatTernaryExpressions: false,
            ignoredNodes: ['JSXElement', 'JSXElement *']
        }],

        // disallow mixed 'LF' and 'CRLF' as linebreaks
        // http://eslint.org/docs/rules/linebreak-style
        'linebreak-style': ['error', 'windows'],

        // specify whether double or single quotes should be used
        quotes: ['error', 'double', { avoidEscape: true }],

        // require padding inside curly braces
        'object-curly-spacing': ['error', 'never'],

        // disallow use of variables before they are defined
        'no-use-before-define': ['error', { functions: false, classes: true, variables: true }],

        // specify the maximum length of a line in your program
        // http://eslint.org/docs/rules/max-len
        'max-len': ['error', 120, 2, {
            ignoreUrls: true,
            ignoreComments: false,
            ignoreRegExpLiterals: true,
            ignoreStrings: true,
            ignoreTemplateLiterals: true
        }]
    }
};

履歴

2017/11/05
初版発行。
2017/11/06
* "browser": true, は node_modules/eslint-config-airbnb-base/legacy.js で設定済なので削除しました。