textlintはJavaScriptでルールやサポートする拡張子を追加できる自然言語向けのLintツールです。

以前からtextlint-scriptsという、textlintのルール作成を補助するツールを公開していました。 textlint-scriptsはES2015+のビルドやテスト周りのセットアップなど、 普段JavaScriptを書かない人でも1つコマンドを叩けばtextlintのルールを書き始められるようにするためのツールです。 (JavaScript慣れてる人にはcreate-react-appみたいなものというのがわかりやすい)

3.0.0の変更点

textlint-scripts 3.0.0では、主な変更点として3つあります。

  • TypeScriptサポート
  • サポートターゲットをES2015+に変更
  • ブラウザ互換性の向上

詳細は次のリリースノートにも書かれています。

TypeScriptサポート

textlint-scripts 3.0.0では、JavaScriptだけではなくTypeScriptのサポートもしました。

textlintはJavaScriptで書かれていましたが、途中からTypeScriptに移行して、今のコードベースは8割ぐらいはTypeScriptになっています。 (テストとはそのままにしてる部分がある)

また、コアモジュールとしてもtextlintルールなどのTypeScript型だけを提供する@textlint/typesというものを公開しています。(これは内部でも使っているし、ルールを書くのにも利用できる) このように、textlintのルールをTypeScriptを書く準備は整ってきたので、textlint-scripts 3.0.0でTypeScriptのビルドとテストをサポートしました。

内部的にはBabelでビルドしていて、テスト時にはts-nodeで型チェックをしながら回すというハイブリッド構成になっています。(Babelでビルドしているのは、後述する静的ファイルのインライン化をしてかったため)

TypeScriptでtextlintルールを書く

TypeScriptでtextlintのルールを書き始めるには、create-textlint-rule--typescriptフラグを渡してプロジェクトを作成するだけです。

$ npx create-textlint-rule example --typescript
# textlint-rule-exampleというTypeScriptプロジェクトを作成する

TypeScriptのプロジェクトテンプレートを元にしたディレクトリが作成されるので、 後は開発して、npm testでテストを通して、npm run buildでビルドして、npm publichで公開するだけです。

具体的には次のようにTypeScriptと@textlint/typesを使ったテンプレートが生成されます。 TextlintRuleModule<オプションのInterface>で大体ルールに必要な型が付くので、あとは普通に開発するだけです。

import { TextlintRuleModule } from "@textlint/types";

export interface Options {
    // if the Str includes `allows` word, does not report it
    allows?: string[];
}

const report: TextlintRuleModule<Options> = (context, options = {}) => {
    const {Syntax, RuleError, report, getSource} = context;
    const allows = options.allows || [];
    return {
        [Syntax.Str](node) { // "Str" node
            const text = getSource(node); // Get text
            const matches = /bugs/g.exec(text); // Found "bugs"
            if (!matches) {
                return;
            }
            const isIgnored = allows.some(allow => text.includes(allow));
            if (isIgnored) {
                return;
            }
            const indexOfBugs = matches.index;
            const ruleError = new RuleError("Found bugs.", {
                index: indexOfBugs // padding of index
            });
            report(node, ruleError);
        }
    }
};
export default report;

既存のtextlint-scriptsを使ったJavaScriptプロジェクトからTypeScriptに移行する方法も用意しています。

@textlint/migrate-textlint-scripts-typescriptを使うと、TypeScript周りの設定ファイルを自動で用意してくれます。

# textlint-scriptをアップデートする
npm install textlint-script@3 -D
# マイグレーションする
# ts-nodeや@textlint/types、tsconfig.jsonの作成などを行う
npx @textlint/migrate-textlint-scripts-typescript

あとは手動でJavaScriptファイルをTypeScriptファイルに書き換えていくだけです。 JavaScript to TypeScriptの基本的なやり方は次の記事も参照してください。 (textlint-testerを使ったテストは拡張子を書き換えて、importを治すだけでほぼ動きます)

サポートターゲットをES2015+に変更

先ほども書いたようにtextlint-script buildでのビルドにはBabelを利用しています。

3.0.0ではBabelのプリセットを@babel/preset-es2015から@babel/preset-envに変更しています。 また、@babel/preset-envのオプションとしてtargets.esmodulesを有効化しています。

これによりtextlint-script buildでのビルドはES2015に対応したJavaScriptエンジン向けのコードを出力します。

  • ES2015の構文で書かれたコードは、ビルド後もそのままES2015のコードとなる
    • 例) class A{}はTranspileされずにclass A{}として出力される
  • ES ModuleはCommonJSに変換される

そのため、IEでは動作しないコードを出力するようになります。 (もともとブラウザで動かしてる人は少ないため、IEでの利用はほぼないと思いますが…)

ES2015のコードを出力することで、コードサイズを抑えたり、Async Functionなど変換結果が単純化されます。 (また@babel/preset-es2015ではAsync Functionに対応してなかったので、今回のアップデートで対応しています)

ブラウザ互換性の向上

textlint本体はコアロジックをPure JavaScriptな@textlint/kernelに切り出したり、ブラウザでも動作するようにしていました。

しかし、textlintのルール自体はNode.jsに依存した機能を使っていたりと、ルール自体がブラウザでは動かないパターンもあります。

textlint-scripts 3.0.0ではこの互換性の問題を緩和するため、textlintルール内でfsモジュールを使って読み込むファイルをビルド時にインライン化する処理を追加しました。

たとえば、textlintルールで次のような静的ファイルを読み込む処理を書いたとします。

const fs = require("fs");
const path = require("path");
const text = fs.readFileSync(path.join(__dirname, "readme.md"), "utf-8");

これがtextlint-script buildによって、readme.mdファイルがソースコードにインライン化されます。

const text = "README CONTENT"

このインライン化の処理はbabel-plugin-static-fsによって行われています。 先ほど、TypeScriptのビルドもBabelでやっていたのは、このような処理はBabelの方が柔軟でやりやすいためです。

これによって、fsモジュールに依存していたtextlintルールも、fs依存が外れるのでブラウザで動かしやすくなります。

作成したtextlintルールがブラウザで動作するかは@textlint/browser-runというツールを使うと簡易なテストができます。

作成したルールと@textlint/kernelを使って、ブラウザでtextlintを実行した結果を出力します。

# electronが入ってるので大きいです
npm install @textlint/browser-run --global
# テストしたいルールのjsとテスト対象を指定する
textlint-browser-run --rule ./lib/rule.js ./README.md

Lint結果が出力されるならおそらくそのルールはブラウザでも動作します。

まとめ

textlint-scripts 3.0.0でTypeScript対応とブラウザの互換性向上の仕組みが入りました。 またビルド後の出力ターゲットがES2015+となりました。

textlintでブラウザをどう扱うかはまだ未定ですが(基本的にはユースケース駆動なので、ユースケースを書いてください)、textlint-scripts 3.0.0でビルドすればfsの依存によって動かなかったルールは動くようになるかもしれません。

内部的にprhを使ってるルールなどはこのアップデートで動かせるようになってると思うので、 ブラウザで動かしたいはtextlint-scriptsをアップデートするPRを送るのがいいかもしれません。

また、[email protected]でNode.jsのWorker Threadsで並列Lintを試験的にサポートしています。この辺もいい感じに分散させるいい感じのロジックを募集してるので、Issueとかに意見をください。

textlintはいつでもContributingを募集してるので、good first issueなどみるといいかもしれません。