Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(eslint): ESLintのautofixに対応 #132

Merged
merged 10 commits into from
Sep 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"always"
],
"no-console": 0,
"no-var": 2
"no-var": 2,
"prefer-const": "error"
},
"env": {
"es6": true,
Expand All @@ -30,4 +31,4 @@
"sourceType": "module"
},
"extends": "eslint:recommended"
}
}
7 changes: 7 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"timeout": "100000",
"recursive": true,
"require": [
"@babel/register"
]
}
54 changes: 29 additions & 25 deletions ja/ESLint/README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
# ESLint

> この文章は[ESLint](http://eslint.org/ "ESLint") 1.3.0を元に書かれています
> この文章は[ESLint](http://eslint.org/ "ESLint") v7.8.1を元に書かれています

[ESLint](http://eslint.org/ "ESLint")はJavaScriptのコードをJavaScriptで書かれたルールによって検証するLintツールです。

大まかな動作としては、検証したいJavaScriptのコードをパースしてできたAST(抽象構文木)をルールで検証し、エラーや警告を出力します。

このルールがプラグインとして書くことができ、ESLintのすべてのルールはプラグインとして実装されています。

> The pluggable linting utility for JavaScript and JSX
> Find and fix problems in your JavaScript code

ESLintサイト上には、上記のように書かれていることからもわかりますが、プラグインに重きを置いた設計となっています。

今回はESLintのプラグインアーキテクチャがどうなっているかを見て行きましょう。

## どう書ける?

ESLintでは`.eslintrc`という設定ファイルに利用するルールの設定をして使うため、
実行方法についてはドキュメントを参照してください。
ESLintでは`.eslintrc`という設定ファイルに利用するルールを設定して利用します。
そのため、実行方法についてはドキュメントを参照してください。

- [Documentation - ESLint - Pluggable JavaScript linter](http://eslint.org/docs/user-guide/configuring "Documentation - ESLint - Pluggable JavaScript linter")

ESLintにおけるルールとは、次のような関数をexportしたモジュールです
関数には`context`オブジェクトが渡されるので、それに対して1つのオブジェクトを返すようにします。
ESLintにおけるルールとは、次のような`create`メソッドをもつオブジェクトをexportしたモジュールです
`create`メソッドには`context`オブジェクトが渡されるので、それに対して1つのオブジェクトを返すようにします。

[import, no-console.js](../../src/ESLint/no-console.js)

Expand Down Expand Up @@ -75,8 +75,8 @@ console.log("Hello!");

ESLintではこのASTを使って、[no-console.js](#no-console.js)のように`console.log`などがコードに残ってないかなどをルールを元にチェックできます。

ルールをどう書けるかという話に戻すと、`context`というオブジェクトはただのユーティリティ関数と考えて問題ありません
ルールの本体は関数が`return`してるメソッドをもったオブジェクトです。
ルールをどう書けるかという話に戻すと、`context`というオブジェクトはただのユーティリティ関数の集合と考えて問題ありません
ルールの本体は`create`メソッドが`return`してるメソッドをもったオブジェクトです。

このオブジェクトはNodeのtypeをキーとしたメソッドを持っています。
そして、ASTを探索しながら「`"MemberExpression"` typeのNodeに到達した」と登録したルールに対して通知(メソッド呼び出し)を繰り返しています。
Expand All @@ -101,7 +101,7 @@ ESLintではこのASTを使って、[no-console.js](#no-console.js)のように`
[no-console.js](#no-console.js)のルールを見ると`MemberExpression` typeのNodeが `node.object.name === "console"` となった場合に、
`console`が残ってると判断してエラーレポートすると読めてくると思います。

ASTの探索がイメージしにくい場合は次のルールで探索の動作を見てみると分かりやすいかもしれません
ASTの探索がイメージしにくい場合は、次のルールで探索の動作を見てみると分かりやすいかもしれません

- [azu.github.io/visualize_estraverse/](http://azu.github.io/visualize_estraverse/ "visualize estraverse step")

Expand All @@ -120,7 +120,7 @@ debug("Hello");

その他、ESLintのルールの書き方についてはドキュメントや次の記事を見てみるといいでしょう。

- [Documentation - ESLint - Pluggable JavaScript linter](http://eslint.org/docs/developer-guide/working-with-rules "Documentation - ESLint - Pluggable JavaScript linter")
- [Working with Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/developer-guide/working-with-rules)
- [コードのバグはコードで見つけよう!|サイバーエージェント 公式エンジニアブログ](http://ameblo.jp/principia-ca/entry-11837554210.html "コードのバグはコードで見つけよう!|サイバーエージェント 公式エンジニアブログ")

## どのような仕組み?
Expand Down Expand Up @@ -148,22 +148,22 @@ import {EventEmitter} from "events";

function lint(code){
// コードをパースしてASTにする
let ast = parse(code);
const ast = parse(code);
// イベントの登録場所
let emitter = new EventEmitter();
let results = [];
const emitter = new EventEmitter();
const results = [];
emitter.on("report", message => {
// 3. のためのreportされた内容を集める
results.push(message);
});
// 利用するルール一覧
let ruleList = getAllRules();
const ruleList = getAllRules();
// 1. ルール毎に使っている`Node.type`をイベント登録する
ruleList.forEach(rule => {
// それぞれのルールに定義されているメソッド一覧を取得
// e.g) MemberExpression(node){}
// => {"MemberExpression" : function(node){}, ... } というオブジェクト
let methodObject = getDefinedMethod(rule);
const methodObject = getDefinedMethod(rule);
Object.keys(methodObject).forEach(nodeType => {
emitter.on(nodeType, methodList[nodeType]);
});
Expand Down Expand Up @@ -245,27 +245,31 @@ add(1, 3);
このプラグインアーキテクチャはPub/Subパターンを上手く使い、
ESLintのように与えられたコードを読み取ってチェックするような使い方に向いています。

つまり、read-onlyなプラグインアーキテクチャとしてはパフォーマンスも期待できると思います。
つまり、複数のルールで同時にLintをするというread-onlyなプラグインアーキテクチャとしてはパフォーマンスも期待できると思います。

また、ルールは `context` という与えられたものだけを使うようになっているため、ルールと本体が密結合にはなりにくいです。
そのため`context`に何を与えるかを決めることで、ルールが行える範囲を制御しやすいといえます
また、ルールは `context` オブジェクトという与えられたものだけを使うようになっているため、ルールと本体が密結合にはなりにくいです。
そのため`context`に何を与えるかを決めることで、ルールができる範囲を制御しやすいといえます

## どのような用途に向いていない?

逆に与えられたコード(AST)を書き換える場合には、
ルールを同時に処理を行うためルール間で競合するような変更がある場合に破綻してしまいます
ルールを同時に処理を行うためルール間で競合するような変更がある場合に上手く整合性を保つ必要があります

そのため、この仕組みに加えてもう1つ抽象レイヤーを設けないと対応は難しいです。
たとえば、あるルールが書き換えるとコードの位置に更新がかかるため、その後のルールはコードの位置更新の影響を受けます。
そのため、コードの書き換えをするにはこの仕組みに加えて、もう1つ抽象レイヤーを設けないと対応は難しいです。

つまり、read-writeなプラグインアーキテクチャとしては単純にこのパターンだけでは難しい部分が出てくると思います
つまり、read-writeなプラグインアーキテクチャとしては、このパターンだけでは難しい部分が出てくると思います

> **NOTE** ESLint 2.0でautofixing、つまり書き換えの機能の導入が予定されています。
> これはルールからの書き換えのコマンドを`SourceCode`というオブジェクトに集約して、最後に実際の書き換えを行うという抽象レイヤーを設けています。
> **NOTE** ESLint 2.0からautofix、つまり書き換えの機能の導入が導入されています
> ESLintでは、各ルールが書き換える位置や文字列を`fixer`オブジェクトという形で報告し、ESLint本体がルールの順番を考慮して最後に実際の書き換えを行うという抽象レイヤーを設けています。
> これは[Command パターン](https://ja.wikipedia.org/wiki/Command_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)を利用してトランザクション的なふるまいを実現しています。
> - [Implement autofixing · Issue #3134 · eslint/eslint](https://github.com/eslint/eslint/issues/3134 "Implement autofixing · Issue #3134 · eslint/eslint")

## この仕組みを使っているもの

- [azu/textlint](https://github.com/azu/textlint "azu/textlint")
- [stylelint](https://github.com/stylelint/stylelint)
- CSSのLintするツール
- [textlint](https://github.com/textlint/textlint)
- テキストやMarkdownをパースしてASTにしてLintするツール

## エコシステム
Expand Down Expand Up @@ -293,7 +297,7 @@ ESLint公式の設定として`eslint:recommended`が用意されています。
設定なしで使えると一番楽ですが、設定なしだと誰でも使えるツールにするのは難しいです。
それを解消するために柔軟な設定のしくみと設定を共有しやすくしています。

これは_The pluggable linting utility_を表現している仕組みといえるかもしれません
これは_Pluggable JavaScript linter_を表現している仕組みといえるかもしれません

## まとめ

Expand Down
4 changes: 2 additions & 2 deletions ja/connect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ Connectは登録された _middleware_ を、サーバがリクエストを受
Connectの行っている処理を抽象的なコードで書くと次のような形になっています。

```js
let req = "...",
const req = "...",
res = "...";
function next(){
let middleware = app.stack.shift();
const middleware = app.stack.shift();
// nextが呼ばれれば次のmiddleware
middleware(req, res, next);
}
Expand Down
8 changes: 4 additions & 4 deletions ja/gulp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ gulp.task("sass", function() {
[gulp-prefixer.js](#gulp-prefixer.js)を見てみると、`gulpPrefixer`という[Transform Stream](https://nodejs.org/api/stream.html#stream_class_stream_transform "stream.Transform")のインスタンスを返していることが分かります。

```js
let gulpPrefixer = function (prefix) {
const gulpPrefixer = function (prefix) {
// enable `objectMode` of the stream for vinyl File objects.
return new Transform({
// Takes in vinyl File objects
Expand Down Expand Up @@ -185,8 +185,8 @@ gulp.src("./*.*")

```js
gulp.src("./*.*", { buffer: false })
.pipe(gulpPrefixer("prefix text"))
.pipe(gulp.dest("build"));
.pipe(gulpPrefixer("prefix text"))
.pipe(gulp.dest("build"));
```

### 変換処理
Expand All @@ -203,7 +203,7 @@ export function prefixStream(prefix) {
transform: function (chunk, encoding, next) {
// ObjectMode:falseのTransform Stream
// StreamのchunkにはBufferが流れてくる
let buffer = prefixBuffer(chunk, prefix);
const buffer = prefixBuffer(chunk, prefix);
this.push(buffer);
next();
}
Expand Down
Loading