Spring Boot + npm + Geb で入力フォームを作ってテストする ( その33 )( ESLint を導入する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
ESLint - Pluggable JavaScript linter
https://eslint.org/愚直にESLintを導入した話
http://tech.mercari.com/entry/2017/07/31/170125eslint-config-airbnb-base
https://www.npmjs.com/package/eslint-config-airbnb-basewebpack から ESLint を使う設定
https://www.pupha.net/archives/3289/eslint-loader
https://www.npmjs.com/package/eslint-loaderESLintでReactとES2015の構文チェック(eslint-config-airbnb)
http://dackdive.hateblo.jp/entry/2016/05/13/094000ESLint - Rules
https://eslint.org/docs/rules/Can I use eslint-config-airbnb without eslint-plugin-react?
https://github.com/airbnb/javascript/issues/451ESLint dollar($) is not defined. (no-undef)
https://stackoverflow.com/questions/39510736/eslint-dollar-is-not-defined-no-undefAirbnb JavaScript Style Guide
https://github.com/airbnb/javascript
目次
- ESLint をインストールする
eslint --init
を実行する- ECMAScript 6 用の Rule を含まない
airbnb-base
→airbnb-base/legacy
に変更する - webpack から eslint が実行されるよう設定する
- 動作確認
- インデントと改行コードと文字列のクォーテーションを airbnb-base のものではなく独自ルールに変更する
- 修正を進める前に IntelliJ IDEA の ESLint の機能を有効にする
- 多めに出力されているメッセージに対応する
- jquery.autoKana.js をチェック対象から外す
- app.js を削除する
'$' is not defined
Unexpected unnamed function
All 'var' declarations must be at the top of the function scope
Expected a newline after '('
Strings must use doublequote
'errmsg' used outside of binding context
'event' is defined but never used
Missing semicolon
Unexpected use of 'location'
- 各ファイル毎の出力されているメッセージに対応する
- 最後に
手順
ESLint をインストールする
npm install --save-dev eslint
コマンドを実行してインストールします。
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
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-base
→ airbnb-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-base
→airbnb-base/legacy
に変更します。
webpack から eslint が実行されるよう設定する
npm install --save-dev eslint-loader
コマンドを実行して eslint-loader をインストールします。
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 を実行してみると、なんか大量にメッセージが出力されました。。。
改行コードとインデントと文字列のクォーテーションで結構メッセージが表示されているようなので、まずは 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 以外にもいろいろ設定が入っていたので、コピーして
2
→4
のみ変更します。 linebreak-style
の設定をunix
→windows
に変更します。quotes
の設定をsingle
→double
に変更します。
再度 npm run spingboot
コマンドを実行すると改行コードとインデントと文字列のクォーテーションではメッセージが出なくなりました。
修正を進める前に IntelliJ IDEA の ESLint の機能を有効にする
IntelliJ IDEA には ESLint を利用してソースコードをチェックする機能があるので、それを有効にします。
メインメニューから「File」-「Settings...」を選択します。「Settings」ダイアログを表示されるので、画面左上の検索フィールドに "eslint" と入力します。
ESLint の設定画面が表示されるので、以下の画面のように設定します。
- 「Enable」をチェックします。
- 「Node interpreter」に "node.exe" のパスを設定します。
- 「Configuration file」はデフォルトの「Automatic search」を選択したままです。
- 「Additional rules directory」「Extra eslint options」は何も設定しません。
「OK」ボタンを押してダイアログを閉じた後 js のソースコードを表示すると、以下の画像のように ESLint のチェックに引っかかった行の右側に赤線が表示され、マウスオーバーすると ESLint のメッセージが表示されます。
またエディタ上で右クリックしてコンテキストメニューを表示した後「Analyze」-「Inspect Code...」を選択し、
「Specify Inspection Scope」ダイアログが表示されたらそのまま「OK」を押すと、
画面下部に Inspection の結果が表示されます。この中に「Code quality tools」という項目があり、
展開すると ESLint のメッセージと該当箇所が表示されます。
多めに出力されているメッセージに対応する
ざっと見てまだ多めに出ているメッセージから対応します。
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 に引っかかりエラーになるようです。
ソースコードを修正して対応します。location
→ window.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
という変数を使用していたので、validator
→validateFunction
に変更します。
src/main/assets/js/inquiry/input02.js
'validator' is already declared in the upper scope
は input01.js と同様にvalidator
→validateFunction
に変更します。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 scope
はevent
→e
に変更します。
src/main/assets/js/lib/class/Form.js
'Form' was used before it was defined
はmodule.exports = Form;
の後にfunction Form(idList) { ... }
の記述があるために出力されていますが、module.exports
の記述を先頭にしたいので .eslintrc.js にno-use-before-define
の設定を node_modules/eslint-config-airbnb-base/rules/variables.js からコピーしてfunctions: true
→functions: 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 からコピーして100
→120
に変更します。
"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 で設定済なので削除しました。