TextLint

概要

textlintは自然言語用のlintの1つ。JavaScriptで書かれている。 https://github.com/textlint/textlint

文章を読みやすくするためのルールが充実している。たとえば一文が〜文字以上だとエラーが出るとか、主語の「が」が連続してはいけないとか、「、」はいくつまでにしないといけないとか。表記ゆれ検知の設定もできる。プロジェクトでドキュメントの質を保たせることはもちろん、個人の文章の練習にも使える。プログラミング言語でそうなっていくように、見やすい文章がエラーなしで書けるようになってくる。最初はイライラさせられるが。

Memo

しくみ: AST変換

https://azu.github.io/JavaScript-Plugin-Architecture/JavaScript-Plugin-Architecture.pdf

  • lintは直に文字列を比較しているわけではない。ASTを比較してる。確かに文字列比較している箇所はなかった。
const ast = parse(code);

の部分だ。ルールではなく、ASTのparseをorgに対応させる。それくらいすでにありそうだけどな。

ルールの実装の方を見てみると、直接オブジェクトをexportしないで、 contextとしてRuleContextのインスタンスを受け取っていることが分かると思 います。

module.exports = {
    meta: { /* ルールのメタ情報 */ },
    create: function (context) {
        return {
            "MemberExpression": function (node) {
                if (node.object.name === "console") {
                    context.report({
                        node,
                        message: "Unexpected console statement."
                    });
                }
            }
        };
    }
};

このようにして、ルールは context という与えられたものだけを使うので、ルー ルがMyLinter本体の実装の詳細を知らなくても良くなります。

このプラグインアーキテクチャはPub/Subパターンを上手く使い、 ESLintのよ うに与えられたコードを読み取ってチェックするような使い方に向いています。

  • read向け。writeは競合変更などに対応しにくい。
  • 走査が1回なので性能がよい

plugin-org

作成した。 単にテキストからのAST変換…構文解析してオブジェクトに変換する…の部分ができればすべてうまくいくはずだ。

ああ、すでにある、思ってcloneして動かしてみる…動かない…4年前のWIPだった。 1コミットだけのをフォークしてしまった。 実行・テスト何もできない状態。importすらできず、地獄。 それでも1からやるよりはマシだった可能性はある…ほぼサンプルのコピペはされてたので。

type: “module”にすればよいとの意見多数だったが、依存パッケージが壊れるのでできなかった。 コンパイルすることに。babel。バージョンの違いで苦しみ、なんとかすべて最新の状態に(自分のグローバルインストールのnodeなんかも超古かった)。

そして…とりあえず単体で動くように。本体のtext-lintの中では動かないが、とりあえずプラグイン認識はしてくれてる。 どこが悪いのか判別つかないのでとりあえず単体テストをやる。 テストもめちゃくちゃで、とりあえず全部消してコンパイル設定とかやって一応動きはするように。

無テストでCIだけ立ち上げる。今までの困難とは裏腹に成功。 パッケージ管理メンドいけど、こういうところがメリットだとわかる。

htmlのASTとの比較

html-to-astとorgaの出力の比較。 けっこう違うな。

{
    type: 'Document',
    children: [
        {
            type: 'Html',
            tagName: 'h2',
            properties: {},
            children: [Array],
            loc: [Object],
            range: [Array],
            raw: '<h2>hello</h2>'
        },
        type: 'UNKNOWN'
    ],
    loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 14 } },
    range: [ 0, 14 ],
    raw: '<h2>hello</h2>'
}
s<ref *1> {
  type: 'document',
  properties: {},
  children: [
    {
      type: 'paragraph',
      children: [Array],
      attributes: {},
      position: [Object],
      parent: [Circular *1]
    }
  ],
  position: { start: { line: 1, column: 1 }, end: { line: 1, column: 13 } }
}

Converting circular structure to JSON

循環参照が含まれているのがだめらしい。 ↑でいうとparent: [Circular *1]のところか。

Note how [Circular ~] shows the path to the referenced object. なるほど。

power-assertの出力がかっこいい

テストの出力がかっこいい。わかりやすいし。 power-assert https://github.com/power-assert-js/power-assert

1) Array #indexOf() should return index when the value is present:
   AssertionError: # path/to/test/mocha_node.js:10

assert(ary.indexOf(zero) === two)
       |   |       |     |   |
       |   |       |     |   2
       |   -1      0     false
       [1,2,3]

[number] two
=> 2
[number] ary.indexOf(zero)
=> -1
OrgProcessor-test
  #parse
    ✓ should return AST
    ✓ begin_src should CodeBlock
    ✓ text should Paragraph
    ✓ begin_comment should block
    ✓ ~~ should text.code
  OrgPlugin
    when target file is a Org
      ✓ should report lint error
      ✓ should not comma check inside the code block.

マッピング

* header はorgaだと(section) => (star) + (headline) みたいになる。 だから1階層下ってheadlineにマッピングしてやる必要がある。

textlint-plugin-org

Emacs org-modeに対応してなかったので対応させた。 https://github.com/kijimaD/textlint-plugin-org

TODO ? を誤検出してしまう問題

リンクの ? にtextlintが反応してしまう。 mdでは反応しないのでtext-lint-orgに原因がある。 orgaが対応してない模様。コードに生URLのテストはなかった。そもそもorg的にはそういう文法の可能性。 だるいのでルールをオフにするか、PR送るかだな。

TODO 返り値の型をつけていない

Typescriptなのにanyのままにしている。 あきらかにTxtNodeなので↓指定するのだが、エラー。

export function parse(org: string): TxtNode {

src/org-to-ast.ts:52:5 - error TS2739: Type ’Document’ is missing the following properties from type ’TxtNode’: raw, range, loc

astオブジェクト。

{
  type: 'Document',
  properties: {},
  children: [
    {
      type: 'UNKNOWN',
      level: 1,
      properties: {},
      children: [Array],
      loc: [Object],
      range: [Array],
      raw: '* Max comma check\n' +
        '#+begin_src\n' +
        'aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa\n' +
        '#+end_src\n'
    },
    type: 'UNKNOWN'
  ],
  loc: { start: { line: 1, column: 0 }, end: { line: 4, column: 10 } },
  range: [ 0, 76 ],
  raw: '* Max comma check\n' +
    '#+begin_src\n' +
    'aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aaaaa\n' +
    '#+end_src\n'
}

うむむ。 Type ’Document’がプロパティを持ってないとのことだが、必要な値を持っているように見える。 ast-testでもパスするし、何よりプラグインとしてうまく動いてるのだが。

TODO ファイルが空のときエラー

ファイルが空のとき、エラーになるような。positionがないエラー。

TODO 日付指定が含まれているとエラー

日本語で挿入されたときにだめなようだ。 アップデート前はできてたが対応しなくなったと。

Cannot destructure property ’value’ of ’eat(…)’ as it is undefined.

これはorgファイルの側を英語にして解決させた。

新たなエラーが出現。todoアイテムが見出しの直後にあると発生する。

TypeError: Cannot redefine property: parent at /home/kijima/Project/textlint-plugin-org/test/fixtures/lint-error.org at Function.defineProperty (<anonymous>) at Controller.enter (/home/kijima/Project/textlint-plugin-org/node_modules/@textlint/kernel/lib/src/task/textlint-core-task.js:125:24) at Controller.__execute (/home/kijima/Project/textlint-plugin-org/node_modules/@textlint/ast-traverse/lib/src/index.js:43:31) at Controller.traverse (/home/kijima/Project/textlint-plugin-org/node_modules/@textlint/ast-traverse/lib/src/index.js:114:28) at TextLintCoreTask.startTraverser (/home/kijima/Project/textlint-plugin-org/node_modules/@textlint/kernel/lib/src/task/textlint-core-task.js:122:28) at TextLintCoreTask.start (/home/kijima/Project/textlint-plugin-org/node_modules/@textlint/kernel/lib/src/task/linter-task.js:22:14) at /home/kijima/Project/textlint-plugin-org/node_modules/@textlint/kernel/lib/src/task/task-runner.js:27:18

ASTからtimestampを消すとテスト用のtextlint-kernelではエラーが出なくなった。 前後で比較したから間違いない。 が、本番のtextlintでは以前として出たままで、解決できない。のですべてrevertした。

Reference

Web Scratch

TextLintの作者の人のブログ。

Archives

DONE アップデートしたら動かなくなった

いつのまにかbuildしても、jsが出力されない状態になっていた。コンパイラの設定ファイルの、出力先ディレクトリの指定方法が変わったぽい。それで空のままnpm publishして、textlintがライブラリ読み込めない状態になっていた。

DONE バージョンアップ

テストでエラーが出てるのを直す。

ちょっとエラーをググったら治った。インポート関連と、依存パッケージが増えたことによるものだった。

DONE Dockerイメージ

試しやすいように、依存のないDockerイメージで実行できるようにする。