JavaScript Standard Style を試す

2017年2月17日 0 開発 , ,

話題の JavaScript Standard Style を試してみた。

背景

以前、以下の記事とはてブで JavaScript Standard Style を知った。

はてブではセミコロンの省略に抵抗感のある人が多く、私もそうだった。しかし同はてブで id:mysticatea さんが指摘されているように ESLint の no-unexpected-multiline でセミコロン省略時に問題のおきるコードを検出できる。また、

  • 昨年末に Swift 入門してセミコロンのないコードに慣れた
  • Electron の JavaScript コードがセミコロンなしルールで読みやすかった

という理由もあり、セミコロンなしも案外よいものじゃないかと考えるようになった。

私のコーディング スタイルは世間の標準からみると独特で、これは過去に在籍していたプロジェクトのルールを踏襲している。主な特徴としては

  • 括弧の内側にスペースを入れる
  • ifwhile などのキーワードと関数名の後にはスペースを入れない

というもの。C 言語や JavaScript でよく見られる K&R 系だとこんな感じのコードも

function isArray (arg) {
  if (Array.isArray) {
    return Array.isArray(arg);
  }

  return Object.prototype.toString.call(arg) === '[object Array]';
}

私のスタイルだとこうなる。

function isArray( arg ) {
  if( Array.isArray ) {
    return Array.isArray( arg );
  }

  return Object.prototype.toString.call( arg ) === '[object Array]';
}

これはこれで気に入っていたのだが GitHub で OSS を運用にするようになり、第三者からの PR は一般的な方のスタイルでくるため扱いに困っていた。スタイルの違いを理由に断るとか直すのも面倒なので今はそのまま merge しているけれど、そもそも自分の好みより世に迎合するほうがよいんじゃないか?と思い始めた。

あと Xcode の Editor におけるコーディング スタイル設定が貧弱というのもある。他の IDE だと私のスタイルを再現するのに十分な設定があるためそうしてきた。しかし Xcode の Text Editing は驚くほど設定がない。まともにいじれるのは Indentation ぐらいである。

これまでは仕方なく根性で手動整形してきたが、Swift 入門を機にあきらめた。iOS で SQLite – FMDB の使い方 2017のサンプルでは Xcode の提示するスニペットそのままに書いている。

この経験を経て、自分のスタイルへ固執することをやめることにした。プラットフォーム標準があればそれに従い、IDE や Editor、Linter の補助を最大限に享受する方針へ転換する。

というわけで、まずは公私ともに書く機会の多い JavaScript のコーディング スタイルから変更してみる。

JavaScript Standard Style

JavaScript のコーディング スタイルとしては

あたりが有名どころらしい。どれを選ぶか迷ったが Electron のようなセミコロンなしスタイルを採用している JavaScript Standard Style にしてみた。Standard と銘打つ度胸と GitHub の star 数も判断材料である。セミコロン以外はよく見るスタイルなので、ここを受け入れられるかが重要。

JavaScript Standard Style への準拠にあたり、それを保証する仕組みがほしいので ESLint を利用。今回は akabekobeko/npm-wpxml2md プロジェクトで試す。

feross/eslint-config-standard を参考にプロジェクトのローカルに必要な npm をインストール。

$ npm i -D eslint-config-standard eslint-plugin-standard eslint-plugin-promise

次にプロジェクトのルートで .eslintrc を定義。

{
  "extends": "standard",
  "env": {
    "mocha": true
  },
  "rules": {
    "no-multi-spaces": 0,
    "yoda": 0
  }
}

JavaScript Standard Style を使用するだけなら "extends": "standard" だけでよい。しかし mocha で書いたユニット テストも対象にしたいのと、

  • 連続した複数行の変数宣言などで縦位置をスペースで揃えたい
  • if 文で不等号による範囲チェックを if (0 <= value && value < max) のように書きたい

のでそれらの設定を追加した。JavaScript Standard Style はスタイルに準拠していることを示す証として

Standard - JavaScript Style Guide

というバッヂを提供している。ルール緩和した場合でもこれをつけてよいものか迷ったけれど緩和は極小なので README へ掲示することにした。第三者が README をながめたとき、基本となるコーディング スタイルを視認できるのはよいことだ。

私は JavaScript のコーディングに Atom を使用しており、ESLint によるリアル タイムなチェックのため

を採用している。これまで linter-eslint はグローバルにインストールした ESLint とプラグインを使用して設定も ~/.eslintrc を参照するようにしていたが、このプラグインはプロジェクトのローカルに ESLint と .eslintrc を検出するとそちらを優先してくれる。

そのため既存プロジェクトは現行のスタイルを維持しつつ、個別に JavaScript Standard Style を採用する運用が可能である。いきなりグローバルを書き換えてもよいけど、少しずつ移行するほうが安全だろう。

スタイルのチェックは基本的に Atom 上で確認 & 修正するのだがファイル単位で個別に作業していると抜けも出やすいため、一括チェック可能な仕組みも用意する。私は npm-scripts に

{
  "scripts": {
    "eslint": "eslint ./src"
  }
}

を定義して

$ npm run eslint

を実行している。これは AltJS/AltCSS の transpile のようにバックグラウンドでファイル変更の検出と自動チェックさせるほうがよいのかもしれない。

セミコロンなき世界

旧スタイルから JavaScript Standard Style へこのように書き換えてみた。これらの中で比較的、短めのコードを引用する。

#!/usr/bin/env node

'use strict'

const CLI = require('./cli.js').CLI
const WpXml2Md = require('../lib/index.js')

/**
 * Entry point of the CLI.
 *
 * @param {Array.<String>} argv   Arguments of the command line.
 * @param {WritableStream} stdout Standard output.
 *
 * @return {Promise} Promise object.
 */
function main (argv, stdout) {
  return new Promise((resolve, reject) => {
    const options = CLI.parseArgv(argv)
    if (options.help) {
      CLI.printHelp(stdout)
      return resolve()
    }

    if (options.version) {
      CLI.printVersion(stdout)
      return resolve()
    }

    if (!(options.input)) {
      return reject(new Error('"-i" or "--input" has not been specified. This parameter is required.'))
    }

    if (!(options.output)) {
      return reject(new Error('"-o" or "--output" has not been specified. This parameter is required.'))
    }

    return WpXml2Md(options.input, options.output, {
      noGFM: options.noGFM,
      noMELink: options.noMELink,
      report: options.report
    })
  })
}

main(process.argv.slice(2), process.stdout)
.then()
.catch((err) => {
  console.error(err)
})

実にスッキリ。見慣れるまでは JavaScript に見えないかもしれない。

これまで C 言語系の構文をもつプログラミング言語に慣れ親しんできたためセミコロン入力は手癖になっていたけど、いざ不要になるとこれがどれだけ負担だったかを認識させられる。

はじめは、ほんの 1 文字だしプログラミングでは書くより考える時間のほうが長いのだから気にするほどのことか?と考えていた。しかし ; + EnterEnter に置き換わることは、実際に体験してみると実に大きい。正確に構文の末尾へセミコロンを置くことと、単に改行するだけというのはかなり違う。セミコロンなし派が一定数いる意味を身をもって知った。

なおセミコロン省略により起き得る問題は前述のように no-unexpected-multiline が検出してくれる。実際の eslint-config-standard/eslintrc.json でも "no-unexpected-multiline": "error" と設定されているため安心だ。

所感

JavaScript Standard Style 導入の所感をまとめる。

  • 一般的な JavaScript と自身のコードを交互にながめても違和感をおぼえにくくなった
  • Atom のスニペットをそのまま利用できるようになった
  • 括弧のスペースを詰めてもそれなりに読める
  • まともな Editor なら構文強調のおかげで括弧とそれ以外を区別しやすいので困らない
  • セミコロンなしはスッキリしてかなり読みやすい
  • セミコロンを入力するのがどれだけ手間だったか実感できる

結論。JavaScript Standard Style は素晴らしかった。今後、他のプロジェクトでも採用する予定。

ESLint を試す

2015年4月27日 0 開発 , , ,

これまで JavaScript の構文チェックに JSHint を利用していたが、ES6 と React JSX に対応していること、より細かく柔軟なルール設定が可能なことから ESLint へ移行してみる。

インストール

ESLint は npm で配布されている。Sublime Text など、様々なアプリから参照することになるだろうから、グローバルにインストールしておく。

$ npm i -g eslint

CLI を提供しているので、コマンドラインから実行できる。バージョン情報は eslint -v、オプション全般は eslint -h で確認。

$ eslint -v
v0.20.0
$ eslint -h
eslint [options] file.js [file.js] [dir]

Options:
  -h, --help                  Show help
  -c, --config path::String   Use configuration from this file
  --rulesdir [path::String]   Use additional rules from this directory
  -f, --format String         Use a specific output format - default: stylish
  -v, --version               Outputs the version number
  --reset                     Set all default rules to off - default: false
  --no-eslintrc               Disable use of configuration from .eslintrc
  --env [String]              Specify environments
  --ext [String]              Specify JavaScript file extensions - default: .js
  --plugin [String]           Specify plugins
  --global [String]           Define global variables
  --rule Object               Specify rules
  --ignore-path path::String  Specify path of ignore file
  --no-ignore                 Disable use of .eslintignore
  --no-color                  Disable color in piped output
  -o, --output-file path::String  Specify file to write report to
  --quiet                     Report errors only - default: false
  --stdin                     Lint code provided on <STDIN> - default: false
  --stdin-filename String     Specify filename to process STDIN as

ルールの定義

ESLint のルールを定義する方法は以下。

JavaScript コメント

構文チェックしたい JavaScript コメント としてルールを定義する方法。 設定は JSON 風に記述する。プロパティ名をダブルクォートで囲わなくてもエラーにならないから JSON ではないかも。

/*eslint eqeqeq:2, curly: 2, quotes: [2, "single"]*/

var arr = [
  "a",
  'b',
];

if( arr.length == 2 ) {
  // test
}

この JavaScript ファイルを適当な場所へ保存して eslint コマンドでチェックすると、定義に基づき以下のエラーが出力されるはず。

$ eslint temp.js

temp.js
  4:2   error  Strings must use singlequote         quotes
  5:5   error  Unexpected trailing comma            comma-dangle
  8:15  error  Expected '===' and instead saw '=='  eqeqeq

✖ 3 problems (3 errors, 0 warnings)

エラー情報は親切でチェックに対応する設定も教えてくれる。ESLint のドキュメントを読んでもピンとこない設定があれば、このように小さなサンプルを作成して試すと理解しやすい。

.eslintrc

ユーザー HOME 直下、またはプロジェクトのディレクトリ直下に .eslintrc というファイルを作成する。前者はグローバル、後者はプロジェクト限定の設定になる。

ファイル書式は JSON または YAML から選ぶ。YAML は Ansible ぐらいでしか使わないので馴染みなく、設定をグローバルに定義するのに抵抗があるため、私は JSON 形式でプロジェクトのディレクトリ直下に定義する方法を採用。

ES6 と React JSX で利用するための設定として .eslintrc を以下のように定義してみた。

{
  "env": {
    "browser": true,
    "node": true
  },
  "rules": {
    "curly": 2,
    "eqeqeq": 2,
    "no-multi-spaces": false,
    "no-underscore-dangle": false,
    "quotes": [2, "single"],
    "strict" : false
  },
  "ecmaFeatures": {
    "arrowFunctions": true,
    "binaryLiterals": true,
    "blockBindings": true,
    "classes": true,
    "defaultParams": true,
    "forOf": true,
    "generators": true,
    "modules": true,
    "objectLiteralComputedProperties": true,
    "objectLiteralDuplicateProperties": true,
    "objectLiteralShorthandMethods": true,
    "objectLiteralShorthandProperties": true,
    "octalLiterals": true,
    "regexUFlag": true,
    "regexYFlag": true,
    "superInFunctions": true,
    "templateStrings": true,
    "unicodeCodePointEscapes": true,
    "globalReturn": true,
    "jsx": true
  }
}

ルールは最小、ES6 関連は現時点のフル機能を利用するようにしてある。それにしても、なぜ ecmaFeatures に JSX の設定があるのだろうか。Babel もだけど、React JSX は ECMAScript 界隈で特別視されている?

余談だが *.jsx という拡張子はは DeNA の JSX や Adobe の ExtendScript でも採用されており、これらも ECMAScript/JavaScript 系なのでややこしい。

package.json

Node モジュール、または npm を利用するプロジェクトであれば package.json を用意することになるが、この中に ESLint の設定も定義できる。プロパティ名は eslintConfig となる。記法は JSON 形式の .eslintrc と同様。

{
  "name": "eslint-sample",
  "version": "1.0.0",
  "description": "Sample for ESLint.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "akabeko",
  "license": "MIT",
  "eslintConfig": {
    "rules": {
      "curly": 2,
      "eqeqeq": 2,
      "no-multi-spaces": false,
      "no-underscore-dangle": false,
      "quotes": [2, "single"],
      "strict" : false
    }
  }
}

Web フロントエンドのビルドに gulp などのタスクランナーを利用せず package.json の scripts やシェルで完結するスタイルなら、この方法がよさそう。

gulp から利用する

gulp のタスクから ESLint による構文チェックを実行したい場合は gulp-eslint を利用する。タスク定義は以下のようになる。

/*global
  require
 */

/*eslint
 "strict": [2, "never"],
 "eqeqeq": 2,
 "curly": 2,
 "quotes": [2, "single"],
 "no-mixed-requires": [1, true],
 "no-multi-spaces": 0
 */

var gulp = require( 'gulp' );
var $    = require( 'gulp-load-plugins' )();

gulp.task( 'lint', function() {
  return gulp.src( './src/js/*.js' )
    .pipe( $.eslint() )
    .pipe( $.eslint.format() )
    .pipe( $.eslint.failOnError() );
} );

せっかくなので gulp タスク自体を ESLint チェックすることを考慮してコメント方式の指定も加えてみた。既に .eslintrc が設定されているなら、コメントを消してそちらを利用したほうがよい。

このタスクを実行するとコマンドラインから実行したときと同様のチェックとエラー出力がおこなわれる。gulp.watch に組み込んで watchify などと一緒に実行するとよい。

Sublime Text で利用する

テキストエディタからリアルタイムに ESLint チェックを実行できると便利なので、愛用している Sublime Text から利用できるように設定してみる。

事前に ESlint を npm で入れたうえで、Sublime Text の Package Control から SublimeLinterSublimeLinter-contrib-eslint をインストールする。

インストールするだけでチェックが有効になっているはず。適当な JavaScript ファイルを作成してコメントや .eslintrc による ESLint 設定を試してみよう。

私の環境では .eslintrc を変更したとき、プロジェクトを読みなおすか Sublime Text を再起動しないと設定が反映されなかった。bash の source コマンドみたいに、このファイルだけ明示的に読み直せればよいのだけど、そういう方法があるのか不明。

はじめて ESLint を利用するなら、既存の JavaScript ファイルをいくつか開いて ESLint のエラーがなくなるまでコメントで設定を書き、最後にそれを .eslintrc 化するのがオススメ。

ESLint と Sublime Text プラグインを入れてもチェックが実行されない場合は Sublime Text のメニューから Tools → SublimeLinter → Toggle Linter… を選択し、eslint が enable になっていることを確認する。もし disable なら eslint と書かれている部分をクリックすることで enable に切り替わる。

まとめ

JSHint に比べて設定が非常に多く、値も数値でモード指定するものとフラグ形式が混在していて、初見だと分かりにくく感じる。

しかし pluggable と宣言しているとおり、特定の設定を押し付けることなく好きなようにカスタマイズさせる設計は好ましい。私は JSLint がインデントなどの細かな記法までチェックするのが嫌で JSHint に移ったクチなので、より自由な ESLint はかなり気に入った。

また、記事冒頭でも書いたとおり React JSX に対応しているのも嬉しい。これまで SublimeLinter だと jshint/jsxhint を併用していたが、ESLint に一本化できてスッキリした。

意外に慣れるので、他に強力な競合があらわれない限り、しばらくは ESLint を利用してみようと思う。