🤖

zennの記事の作成ルールをtextlintでチェックしてみた

2025/01/03に公開

はじめに

例えば、emoji や type を削除してみると、以下のようにプレビューにエラーが出る。
display_zenn_error.png

次のような textlint のエラーとして表示してみた。
verify_zenn_rule.png

準備

textlint と lefthook の設定なので、不要な方は飛ばしてください。

textlint をインストール

少し本題とずれますが、日本語のチェックを含めて入れておきます。

$ npm install --save-dev textlint textlint-filter-rule-allowlist textlint-filter-rule-comments textlint-rule-preset-ja-technical-writing
# 設定ファイルを生成
$ npx textlint --init

設定は以下を参考にしました。
https://zenn.dev/yamane_yuta/articles/65886897cefa1e

.textlintrc.json
{
  "plugins": {},
  "filters": {
    "allowlist": {
     "allow": [
       "/^::::/m"
     ]
   },
    "comments": true
  },
  "rules": {
    "preset-ja-technical-writing": true
  }
}

lefthook をインストール

pre-commit のイベント時に textlint を走らせるため lefthook を使います。

$ npm install --save-dev lefthook
# 設定ファイルを生成
$ npx lefthook install
lefthook.yml
pre-commit:
  parallel: true
  commands:
    lint:
      glob: "*.md"
      run: npx textlint -c .textlintrc {staged_files}

textlint の独自ルールを定義する

https://github.com/zenn-dev/zenn-editor
いくつかのパッケージがモノリポで管理されています。この中の zenn-model は主に型定義と Validation が定義されているので、こちらを使いたいと思います。

$ npm install --save-dev zenn-model gray-matter

独自ルールを定義する

https://textlint.github.io/docs/rule.html

以下の JavaScript ファイルを用意します。

lint/validateArticle.js
import path from "node:path";
import model from "zenn-model";
import matter from "gray-matter";

/**
 * @param {RuleContext} context
 */
export default function (context) {
    return {
        [context.Syntax.Document](node) {
            // Markdownファイルの全ての文章が取れる
            const content = context.getSource(node);
            const filePath = context.getFilePath(node);
            const slug = path.basename(filePath, ".md");
            // Markdown をパースし、Metadata を取得する
            const { data } = matter(content);
            const errors = model.validateArticle({ ...data, slug });
            errors.forEach(e => {
                context.report(node, new context.RuleError(e.message));
            })

        }
    };
}

他にも、 Syntax.ParagrahSyntax.Str もあり、以下のようなテキスト単位で処理できるようです。

export default function (context) {
    // rule object
    return {
        [context.Syntax.Paragraph](node) {
            console.log(`paragraph: ${context.getSource(node)}`);
        },
        [context.Syntax.Str](node) {
            console.log(`str: ${context.getSource(node)}`);
        },
    };
}
/** 出力結果 **
 * str: はじめに
 * paragraph: 例えば、emoji や type を削除してみると、以下のようにプレビューにエラーが出る。
 * ![display_zenn_error.png](/images/db7da4f037164d/display_zenn_error.png)
 * str: 例えば、emoji や type を削除してみると、以下のようにプレビューにエラーが出る。
 */

textlint 実行時に Config ファイルの指定だけでなく、独自のルール設定を行なったディレクトリも指定します。

lefthook.yml
pre-commit:
  parallel: true
  commands:
    lint:
      glob: "*.md"
-      run: npx textlint -c .textlintrc {staged_files}
+      run: npx textlint -c .textlintrc --rulesdir ./lint {staged_files}

ルールに則っていないものがあれば、コミット時にエラーで落ちるようになります。

verify_zenn_rule.png

参考

https://github.com/evilmartians/lefthook
https://github.com/jonschlinkert/gray-matter

Discussion