textlint 6.0をリリースしました。

モジュールとして使っている場合に、細かな破壊的な変更があります。 ツールとして使っている人は単純にnpm install textlint@6 -Dなどでアップデートすれば動くと思います。

textlint自体については以下を見てください。

--fixによる自動修正の対応

5.5.3: –fix & –experimental support--fixによる自動修正機能を実験的にサポートしていました。

6.0では--fix--experimentalフラグなしで利用できるようになりました。

自動修正はルール側で対応が必要 かつ 原理的に自動修正が難しいルールもあると思うので、あくまで補助的な機能です。

Wikiにまとめてあるルールのうち、✔ fixableマークが付いてるものは対応しています。

対応しているルールをfixableルールと呼んでいて、以下のバッジをつけています。

textlint rule

現状では以下のようなルールが--fixに対応しています。

fixableルールはprhと辞書を使った表記揺れの統一JTF日本語標準スタイルガイドを使った表記の統一など、辞書ベースやスタイルの統一を簡単になるのが利点です。

逆に表現的なものをチェックするルールは自動修正が難しく、頑張ってもサジェストのような推敲支援になると思います。

textlintは校正支援として始めたツールなので、まだ推敲支援のような要素は入っていません。 以下のIssueでそのようなサジェストをどう扱うかについて話し合ってるので興味がある人は見てください。

また--fixはread onlyなlintと違ってwrite処理であるため、バグを作りやすいです。 自分もリリースしてからあるルールでバグがあることに気づいたので、そういうのをテストする方法については以下で話し合っています。

使用してバグを見つけたら、

  • それが特定のルールのバグであるならルールのリポジトリへ
  • そうでなくてtextlintのバグであるならtextlint

報告してください。

バグ報告の仕方については以下を参考にしてください。

Fixableルール

実際に--fixによる自動修正の例を見てみます。

このブログのリポジトリ対象にしてみます。

git clone https://github.com/efcl/efcl.github.io
cd efcl.github.io

次にtextlintとfixableに対応してる3つのルール/プリセットをインストールします。

npm i -D textlint textlint-rule-preset-jtf-style textlint-rule-spellcheck-tech-word textlint-rule-common-misspellings

インストールし終わったら、.textlintrcにインストールしたルールを使うように設定します。 (--rule--presetオプションでも指定できますが毎回やるのは面倒なので)

$(npm bin)/textlint --init

というコマンドを叩くと、空の.textlintrc設定ファイルが作成されるので、編集して次のような設定をします。

{
  "rules": {
  	"preset-jtf-style": true,
  	"spellcheck-tech-word": true,
  	"common-misspellings": true
  }
}

この状態でチェックしたいファイルを指定すればtextlintでLintすることができます。

自分の今まで書いてきた全記事のうちMarkdownをこのルールでLintしてみます。

$(npm bin)/textlint _posts/

...
✖ 6091 problems (6091 errors, 0 warnings)
✓ 5639 fixable problems.
Try to run: $ textlint --fix [file]

result of linting

絶望的な数のエラーが表示されました…

デフォルトのformatterである-f stylish または -f pretty-errorでは、 Lintの結果にそのうち自動修正できるエラーの数を表示してくれます。

この数値は実際に修正できる数とは必ずしも一致しません。ルール間は独立しているので、重複や衝突が存在しているためです。基本的には衝突しないように修正を逐次的に処理していく形になります。

このエラーを実際に--fixで修正してみたいと思います。

修正する前に、対象のファイルは必ず復元できるように、バックアップを取るかGitにコミットしておくなどしてください。 また、--dry-runオプションを使うことで、実際にファイルの上書きはしないでどのような変更が行われるかを見ることができます。

変更を比較する場合は -f diff でdiff表示をするformatterを利用すると分かりやすいです。 (--fixは本当に逐次的に処理するので、ルールやファイルが多いと掛け算的に処理時間が増えます)

$(npm bin)/textlint _posts --fix --dry-run -f diff
# 実際に変更は反映しないでdiffだけをみる

....
✔ Fixed 5713 problems
✖ Remaining 73 problems

result of fixing

✖ 6091 problemsのうち✔ Fixed 5713 problemsが自動で修正することができます。 --dry-runで問題ないのが確認できたら、--fixのみにして上書き保存できます。

$(npm bin)/textlint _posts --fix

fix error

textlint v5からv6の変更点

textlint 5.0.0までの変更点については以前書いたので、5.0から6.0までにあった変更点を列挙してみます。

5.1.0

textlint-rule-presetをサポートしました。 ルールプリセットは個別のルールをまとめたモジュールという位置づけです。

もちろん内蔵しているルール個別に設定を.textlintrcに書くことができるので、 一つのルールに沢山のチェック機能を設けるよりも、個別のルールに分けてルールプリセットとして公開することを推奨しています。

5.2.0

textlint-formatter-<name>のようなモジュールを--formatter <name>という形で指定できるようになりました。

これを利用したカバレッジを出力するformatterを公開しています。

formatterの作り方はドキュメントを見てください。

5.3.0

--stdin-filename をサポートしました。

cat readme.md | textlint --format compact --stdin --stdin-filename readme.md

という感じで標準入力のテキストに対して擬似的な名前を付けることができます。 (拡張子に対応した種類のファイルとしてtextlintが処理するため)

この仕組はSublimeプラグインを作るのに使われています

5.4.0

.textlintrcファイルを作成する--initオプションが追加されました。

textlint --init

で空の.textlintrcファイルを作成してくれます。

まだ色々改良の余地があるので興味がある人は以下のIssueを見てください。

5.5.3

--fix--experimentalを実験的にサポートしました。 ここで実験的に追加した--fixは6.0.0で正式サポートとなります。

5.6.0

ルール開発者向けにRuleErrorindexプロパティをサポートしました。

var ruleError = new context.RuleError("Found rule error", { index: paddingIndex });

正確なエラーの位置を簡単に指定できます。 今まで通り、columnlineの組み合わせでも問題ないですが、column単独の指定はindexに書き換える事を推奨しています。

textlint-testerでテストしている場合は、column単独の場合には警告を出してくれます。

5.7.0

5.7.0では一部formatterがエラーとそのエラーが自動修正できるかを出すようになりました。

6.0

6.0では

  • --dry-runのサポート(--fixと組み合わせて使います)
  • --fixからexperimentalフラグが外れました
  • --fix向けのformatterとして-f diffを追加しました

モジュールとして使う場合に関連するものとして

  • TextLintEngine#setRulesBaseDirectory が削除されました
  • TextFixEngineが追加されました
    • --fixにおける処理を扱うEngineです
  • TextLintCore#setupProcessorsが追加されました

また内部をかなりリファクタリングして、

  • CLI
  • Engine
  • Core

の役割と関心を分離してディレクトリなどを切るようになりました。 詳しくは以下のドキュメントに書かれています。

結構長くなりましたが、5.0.0〜6.0.0の変更点は以上です。 Breaking Changeはほとんどのユーザには関係ないものだと思います(実際使ってるコードはGitHub上にはなかった)

その他

textlint on chrome

Chrome拡張として動くので、Node.jsを入れなくてもブラウザにインストールしてGUIで設定できるので簡単に使うことができます。

Atomプラグインとしてlinter-textlintを使う場合は上記のリポジトリが参考になるかもしれません。

開発メモ

--fixは5.5から使えたのですが、色々APIデザインを変更する可能性があったのでexperimentalフラグを付けてリリースしました。

浅いイテレーションをするには

  • 本体機能をテストしてもらいたいならbetaをとにかく出す
  • プラグインに関係するエコシステム的な機能を試したいならexperimentalフラグ付きで出す
  • プラグイン側は最新の本体でしか動かない変更を入れるならメジャーアップデート
    • 本体バージョンとプラグインの参照するバージョンがズレているとユーザーはハマります
    • なのでメジャーアップデートとするのが無難です
  • プラグイン側が特定バージョン以降に依存するならpeerDependenciesを付ける
  • npmのbetaとGitHubのprereleaseを使う
  • フラグつき機能は、次のメジャーアップデートで外す
  • testライブラリを作りdeprecatedはプラグイン作者に通知
    • テスト中はガンガン例外を投げて伝える
    • textlintはtextlint-tester経由の実行時のみ例外を投げるUtilを持っています。
    • throw-log.js
  • 本体とプラグインが同時にnpmのbetaになると辛い
    • βはsemverで>=のような指定ができなくて、直書きする事になって辛いです
  • experimentalフラグ付きで本体はstableリリースして、プラグインはstableな本体に依存させたbetaを出す
  • プラグインがstableリリースする際はメジャーアップデート+peerDependenciesにする
    • この辺が--experimentalフラグを作った理由です

課題としては

  • 本体のBreaking Changeに依存したプラグインの変更をうまく扱う方法がない
  • なので本体のBreaking Changeはなるべく小さく、単独でリリースする
    • プラグインがすぐに追従できるような変更の粒度を考える

という感じになっています。