JavaScript、HTML、CSSを簡単にLintできる開発環境を作った

## はじめに
FrontEndのフレームワークに依存せず、JavaScriptとHTML、CSSに対してLintを行い、Visual Studio CodeとJenkinsでLint結果を確認できる開発環境を作ったので、その使い方を書きます。
## コンセプト
- JavaScriptのLintには[ESLint](http://eslint.org/)を使う
- HTMLのLintには[HTMLHint](http://htmlhint.com/)を使う
- CSSのLintには[CSSLint](http://csslint.net/)を使う
- 開発環境は[Visual Studio Code](https://www.visualstudio.com/ja-jp/products/code-vs.aspx)を使う
- CIツールは[Jenkins](https://jenkins.io/)を使う
- Jenkinsからは、手間がかからないようにLint結果のレポート形式をCheckStyle形式で統一する
- Visual Studio Codeの設定を合わせるため、[EditorConfig](http://editorconfig.org/)の設定も含める

## seedプロジェクト

[frontend-lint-seed](https://github.com/acroquest/frontend-lint-seed)としてGitHub上に公開しています。

## 開発環境のセットアップ

- [Visual Studio Code](https://www.visualstudio.com/ja-jp/products/code-vs.aspx)をインストールする。バージョンは1.0以上。
- 次の[Visual Studio Code Extension](https://code.visualstudio.com/docs/editor/extension-gallery)をインストールする。
	- ext install EditorConfig
	- ext install ESLint
	- ext install HTMLHint
	- ext install CSSLint
- プロジェクトをgit cloneし、npm install を行う。

## 使い方

セットアップが完了すればVisual Studio Code上でJavaScript、HTML、CSSのLintエラーが表示される。Jenkins用にCheckStyle形式のレポートを生成するには次のコマンドを実行する。Lintレポートはreports以下に生成される。

```
$ npm run lint
```

## プロジェクト構成

app以下はサンプルです。Lintエラーが出るようにルールに違反したコードになっています。

```
.
├── .csslintrc
├── .editorconfig
├── .eslintrc
├── .htmlhintrc
├── .vscode
│   ├── settings.json
│   └── tasks.json
├── app
│   ├── index.html
│   ├── main.html
│   ├── scripts
│   │   ├── index.js
│   │   └── main.js
│   └── styles
│       ├── index.css
│       └── main.css
├── gulpfile.js
├── package.json
└── reports
    ├── csslint-checkstyle.xml
    ├── eslint-checkstyle.xml
    └── htmlhint-checkstyle.xml
```

## .vscode/settings.json
内容はほぼデフォルトのままですが、フォントをMac用には"Monaco"、Windows用には"Consolas"を指定しています。

``` javascript:.vscode/settings.json
{
    // Controls the font family.
    "editor.fontFamily": "Monaco, Consolas",
    // Controls visibility of line numbers
    "editor.lineNumbers": true,
    // Controls the rendering size of tabs in characters. Accepted values: "auto", 2, 4, 6, etc. If set to "auto", the value will be guessed when a file is opened.
    "editor.tabSize": 2,
    // Controls if the editor will insert spaces for tabs. Accepted values:  "auto", true, false. If set to "auto", the value will be guessed when a file is opened.
    "editor.insertSpaces": true,
    // Controls after how many characters the editor will wrap to the next line. Setting this to 0 turns on viewport width wrapping
    "editor.wrappingColumn": 200,
    // Controls whether the editor should render whitespace characters
    "editor.renderWhitespace": true,
    //-------- Files configuration --------
    // Configure glob patterns for excluding files and folders.
    "files.exclude": {
        "**/.git": true,
        "**/.svn": true,
        "**/.DS_Store": true
    },
    // The default character set encoding to use when reading and writing files.
    "files.encoding": "utf8",
    //-------- Search configuration --------
    // Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the file.exclude setting.
    "search.exclude": {
        "**/node_modules": true,
        "**/bower_components": true
    }
}
```

## .editorconfig
やろうと思えばJS、HTML、CSSでindent_sizeを変えるなどできるが現状は統一しています。
EditorConfigの設定については[こちら](http://editorconfig.org/)を参照。

``` .editorconfig
# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
```

## .eslintrc
ESLintルールのベースは"eslint:recommended"を使用している。必要に応じてカスタマイズすること。
ESLintのルールについては[こちら](http://eslint.org/docs/rules/)を参照。

``` javascript:.eslintrc
{
  "extends": "eslint:recommended",
  "env": {
    "node": true,
    "browser": true
  },
  "rules": {
    "semi": 2,
    "quotes": [2, "single", "avoid-escape"]
  },
  "globals": {}
}
```

## .htmlhintrc
HTMLHintルールはデフォルトから"doctype-first"をfalseに。Component用のHTMLテンプレートを書くことがあるため。
HTMLHintのルールについては[こちら](https://github.com/yaniswang/HTMLHint/wiki/Rules)を参照。

``` javascript:.htmlhintrc
{
  "tagname-lowercase": true,
  "attr-lowercase": true,
  "attr-value-double-quotes": true,
  "doctype-first": false,
  "tag-pair": true,
  "tag-self-close": true,
  "spec-char-escape": true,
  "id-unique": true,
  "src-not-empty": true,
  "attr-no-duplication": true,
  "title-require": true,
  "doctype-html5": true,
  "space-tab-mixed-disabled": "space"
}
```

## .csslintrc
CSSLintルールはひとまずの仮決めです。今後運用してカスタマイズする。
CSSLintのルールについては[こちら](https://github.com/CSSLint/csslint/wiki/rules)を参照。

``` javascript:.csslintrc
{
  "adjoining-classes": true,
  "box-model": true,
  "box-sizing": true,
  "bulletproof-font-face": false,
  "compatible-vendor-prefixes": true,
  "display-property-grouping": true,
  "duplicate-background-images": true,
  "duplicate-properties": true,
  "empty-rules": true,
  "fallback-colors": true,
  "floats": false,
  "font-faces": true,
  "font-sizes": true,
  "gradients": true,
  "ids": false,
  "import": false,
  "important": true,
  "known-properties": true,
  "non-link-hover": true,
  "outline-none": true,
  "overqualified-elements": false,
  "qualified-headings": true,
  "regex-selectors": false,
  "shorthand": false,
  "star-property-hack": true,
  "text-indent": false,
  "underscore-property-hack": true,
  "vendor-prefix": true,
  "unique-headings": false,
  "universal-selector": true,
  "unqualified-attributes": false,
  "zero-units": false
}
```

## gulpfile.js

Lintの実行には[gulp](http://gulpjs.com/)を使っています。htmlhintの部分はゴリゴリでスマートさに欠けるが、まずは期待する結果を出すことを優先した。


``` javascript:gulpfile.js
var gulp = require('gulp');
var gulpLoadPlugins = require('gulp-load-plugins');
var $ = gulpLoadPlugins();
var del = require('del');
var vinylPaths = require('vinyl-paths');

var REPORT_DIR = 'reports/';
var ESLINT_FILE_NAME = 'eslint-checkstyle.xml';
var CSSLINT_FILE_NAME = 'csslint-checkstyle.xml';
var HTMLHINT_FILE_NAME = 'htmlhint-checkstyle.xml';

gulp.task('eslint', function () {
  var fs = require('fs');
  gulp.src('app/scripts/**/*.js')
    .pipe($.eslint('.eslintrc'))
    .pipe($.eslint.format('checkstyle', fs.createWriteStream(REPORT_DIR + ESLINT_FILE_NAME)));
});

gulp.task('csslint',
  $.shell.task('csslint --format=checkstyle-xml app/styles > ' + REPORT_DIR + CSSLINT_FILE_NAME)
);

process.env.HTMLHINT_CHECKSTYLE_FILE = REPORT_DIR + HTMLHINT_FILE_NAME;
gulp.task('htmlhint', function () {
  gulp.src('app/**/*.html')
    .pipe($.htmlhint('.htmlhintrc'))
    .pipe($.htmlhint.reporter('gulp-htmlhint-checkstyle-file-reporter'))
    .pipe(gulp.dest(REPORT_DIR + '.tmp'))
    .on('end', function () {
      gulp.src(REPORT_DIR + '*.tmp.*')
        .pipe(vinylPaths(del))
        .pipe($.concat(HTMLHINT_FILE_NAME))
        .pipe($.header('<?xml version="1.0" encoding="utf-8"?>\n<checkstyle version="4.3">\n'))
        .pipe($.footer('\n</checkstyle>'))
        .pipe(gulp.dest(REPORT_DIR))
        .on('end', function () {
          del([REPORT_DIR + '.tmp']);
        });
    });
});

gulp.task('lint', ['eslint', 'csslint', 'htmlhint'], function () {
  gulp.src(REPORT_DIR + '*.xml')
    .pipe($.prettyData({ type: 'prettify' }))
    .pipe(gulp.dest(REPORT_DIR));
});

gulp.task('lint:clean', function () {
  return del([REPORT_DIR + '*.*']);
});

gulp.task('default', ['lint:clean'], function () {
  gulp.start('lint');
});
```

## 終わりに
- 各種ルールの設定は今後運用しながらカスタマイズする。
- Vidual Studio Codeで開発する際とCIツールで確認する際で同じルールでチェックできるのは便利。
- 一からこのような環境を作ると、各種Lintツール、gulp、Jenkinsとの連携などぞれぞれの要素ではまることになるので、その手間をなくして、本質的な開発に集中したい。
- よりよいやり方があったらぜひコメントやPRをもらえるとありがたいです。