textlintプラグインの作り方(例:オンドゥル語変換) プラグイン作成編
本記事はtextlintプラグインの作り方(例:オンドゥル語変換) 準備編の続きにあたります。
本記事概要
実際にプラグインのコードを書き、コマンドラインでのtextlintを実行して、正常にオンドゥル変換できるかを確認していきます。
プラグインの作成
カタカナ→オンドゥル語変換する関数の作成
オンドゥル語への変換は、今回はカナの単純な一文字ずつ変換とし、 lib/ondulish.ts
を作成して、文字変換自体の処理はそちらに任せます。
mkdir lib
touch lib/ondulish.ts
実際のロジックはこちら。サンプルなので単純に。
形態素解析の導入
変換対象の文字列を単語に分解するためのモジュール、形態素解析のkuromojiを使います。日本語の読みをカナで出力してくれるため、それを上の ondulish.ts
に渡していきます。
textlintではそのwrapper関数である kuromojin
が用意されているので、そちらをインストール。
yarn add kuromojin
メインロジック作成
index.ts
を今回は以下のようにかきました。
import { TextlintRuleModule } from '@textlint/types';
import { tokenize } from 'kuromojin';
import ondulish from './lib/ondulish';
export interface Options {
// if the Str includes `allows` word, does not report it
allows?: string[];
}
const reporter: TextlintRuleModule<Options> = (context) => {
const { Syntax, RuleError, report, getSource } = context;
// const allows = options.allows ?? [];
return {
async [Syntax.Str](node) {
const text = getSource(node); // 文字列を取得
const tokens = await tokenize(text); // kuromojiで形態素解析し、単語に分ける
tokens.forEach((token) => {
// 単語が日本語の場合pronunciationにカタカナ読みが入るので、その時のみ処理
// token.readingにもカナが入るが、発音を正しく?オンドゥルするためにpronunciation選択
if (token.pronunciation) {
const ondul = ondulish(token.pronunciation); // オンドゥル語に変換
if (token.reading !== ondul) {
const message = `${token.surface_form} => ${ondul}`; // 出力メッセージへ整形
// メッセージを出力するためのRuleErrorクラスの作成
const ruleError = new RuleError(message, {
index: token.word_position - 1,
});
// メッセージ出力
report(node, ruleError);
}
}
});
},
};
};
export default reporter;
ダミーファイル作成
変換対象のファイルを作成しておきます。
touch ondulish_target.txt
本当に裏切ったんですか
LET'S ONDULISH!
まずはコンパイルします。
$ yarn build
yarn run v1.22.19
rimraf lib
textlint-scripts build
Successfully compiled 2 files with Babel (829ms).
✨ Done in 7.91s.
そしてONDULISH!
# うまくいかない時は --debug をつけると何かわかるかもしれません
$ yarn textlint --rulesdir ./lib ondulish_target.txt
(中略)
textlint-rule-ondul-style/ondulish_target.txt
1:1 error 本当に => オンドゥール index
1:4 error 裏切っ => ルラギッ index
1:7 error た => ダ index
1:8 error ん => ン index
1:9 error です => ディス index
1:11 error か => カ index
よいかんじですね!
kuromojinでtokenizeすると、以下のような情報を持ったtokenが渡されます。適時自分の作りたいプラグインに合わせて使うと良いでしょう。
tokenize(text).then(tokens => {
console.log(tokens)
/*
[ {
word_id: 509800, // 辞書内での単語ID
word_type: 'KNOWN', // 単語タイプ(辞書に登録されている単語ならKNOWN, 未知語ならUNKNOWN)
word_position: 1, // 単語の開始位置
surface_form: '黒文字', // 表層形
pos: '名詞', // 品詞
pos_detail_1: '一般', // 品詞細分類1
pos_detail_2: '*', // 品詞細分類2
pos_detail_3: '*', // 品詞細分類3
conjugated_type: '*', // 活用型
conjugated_form: '*', // 活用形
basic_form: '黒文字', // 基本形
reading: 'クロモジ', // 読み
pronunciation: 'クロモジ' // 発音
} ]
*/
});
忘れずcommitしておきましょう。
$ git add .
$ git commit -m "feat: オンドゥル変換まで実装"
yarn run v1.22.19
$ /textlint-rule-ondul-style/node_modules/.bin/lint-staged
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
✨ Done in 8.22s.
Fix対応のための改良
これでリリースの準備はできていますが、 fix
に対応させ、自動的に修正するまでやりたいですよね。ここからはそのための処理です。
index.tsを変更し、fixerに関する処理を追加します。 RuleError
に fix
を追加し、修正対象の単語の開始位置・終了位置と、変換文字列が入るようにしています。
const ruleError = new RuleError(message, {
index: tokenFromIndex,
fix: fixer.replaceTextRange(
[tokenFromIndex, tokenToIndex],
ondul,
),
),
export部分も修正し、 fixer
を追加します。
export default {
linter: reporter,
fixer: reporter,
};
最終的な差分は以下です。
const reporter: TextlintRuleModule<Options> = (context) => {
- const { Syntax, RuleError, report, getSource } = context;
+ const { Syntax, RuleError, report, fixer, getSource } = context;
// const allows = options.allows ?? [];
return {
@@ -23,9 +23,15 @@ const reporter: TextlintRuleModule<Options> = (context) => {
const ondul = ondulish(token.pronunciation); // オンドゥル語に変換
if (token.reading !== ondul) {
const message = `${token.surface_form} => ${ondul}`; // 出力メッセージへ整形
+ const tokenFromIndex = token.word_position - 1;
+ const tokenToIndex = tokenFromIndex + token.surface_form.length;
// メッセージを出力するためのRuleErrorクラスの作成
const ruleError = new RuleError(message, {
- index: token.word_position - 1,
+ index: tokenFromIndex,
+ fix: fixer.replaceTextRange(
+ [tokenFromIndex, tokenToIndex],
+ ondul,
+ ),
});
// メッセージ出力
report(node, ruleError);
}
}
});
},
};
};
-export default reporter;
+
+export default {
+ linter: reporter,
+ fixer: reporter,
+};
おそらく tokenToIndex
はこのロジックだと悪い気はしますが、今回はよいかな。
Let's Ondulish
さてFixしてみましょう。まずは fix
なしで出力してみます。
$ yarn build
$ yarn textlint --rulesdir ./lib ondulish_target.txt
yarn run v1.22.19
/textlint-rule-ondul-style/node_modules/.bin/textlint --rulesdir ./lib ondulish_target.txt
ホントーニ
ウラギッ
タ
ン
デス
カ
/textlint-rule-ondul-style/ondulish_target.txt
1:1 ✓ error 本当に => オンドゥール index
1:4 ✓ error 裏切っ => ルラギッ index
1:7 ✓ error た => ダ index
1:8 ✓ error ん => ン index
1:9 ✓ error です => ディス index
1:11 ✓ error か => カ index
✖ 6 problems (6 errors, 0 warnings)
✓ 6 fixable problems.
Try to run: $ textlint --fix [file]
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
✓ 6 fixable problems.
と出ましたね。修正が可能になりました。 -fix
を追加して再度実行します。
$ yarn textlint --rulesdir ./lib ondulish_target.txt --fix
yarn run v1.22.19
/textlint-rule-ondul-style/node_modules/.bin/textlint --rulesdir ./lib ondulish_target.txt --fix
ホントーニ
ウラギッ
タ
ン
デス
カ
/textlint-rule-ondul-style/ondulish_target.txt
1:1 ✔ 本当に => オンドゥール index
1:4 ✔ 裏切っ => ルラギッ index
1:7 ✔ た => ダ index
1:8 ✔ ん => ン index
1:9 ✔ です => ディス index
1:11 ✔ か => カ index
✔ Fixed 6 problems
✨ Done in 2.73s.
$ cat ./ondulish_target.txt
オンドゥールルラギッダンディスカ
無事変換できました!あとはcommitしてテストに進みましょう。
$ git add .
$ git commit -m "feat: fixerの追加"
yarn run v1.22.19
$ /textlint-rule-ondul-style/node_modules/.bin/lint-staged
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
✨ Done in 8.41s.
[v1.0.0 69e2329] feat: fixerの追加
2 files changed, 13 insertions(+), 4 deletions(-)
delete mode 100644 ondulish_target.txt
テスト
では実際にテストコードを修正していきます。
と言っても、今回はすごく単純なのでひとつテストケースを作るだけにしました。
import TextLintTester from 'textlint-tester';
import rule from '../src';
const tester = new TextLintTester();
// ruleName, rule, { valid, invalid }
tester.run('rule', rule, {
invalid: [
{
text: `本当に裏切った`,
errors: [
{
message: '本当に => オンドゥルル',
line: 1,
column: 1, //文字の開始位置は1
},
{
message: '裏切っ => ルラギッ',
line: 1,
column: 4, //「本当に」の次の文字なので4
},
{
message: 'た => ダ',
line: 1,
column: 7, //たは7文字目になる
},
],
},
],
});
Fix機能があるので、 column
がただしく設定されているかをちゃんとチェックしています。
テスト実行
yarn test
を実行しましょう。
$ yarn test
yarn run v1.22.19
$ textlint-scripts test
rule
ホントウニ
ウラギッ
タ
✔ 本当に裏切った (1150ms)
1 passing (1s)
✨ Done in 7.17s.
ではコミットします。
git add .
git commit -m "feat: test 追加"
リリースに備えて
次で最後のリリースになるのですが、このままだと今のコミットログは以下のような感じで、そのままマージするにはちょっと微妙です(commitやりなしたりしたので実際のログと手順にずれがあります)。そこでコミットを1つにまとめる処理をしておきます。
$ git log --oneline
69e2329 (HEAD -> v1.0.0, origin/v1.0.0) feat: fixerの追加
82eecfd fix: dependenciesが間違ってた
ed8109f feat: オンドゥル変換まで実装
2271df6 add: カタカナ変換
084a4a6 fix: huskyが実行されなかったので修正
e66bfce feat: linterを準備
97c1a2e fix: lintエラーを回避
e0080b8 (main) feat: initial commit
# 97c1a2e から 69e2329 を一つにするので、HEADから7つをまとめます
$ git rebase -i HEAD~7
# editorでpickの部分をsquashに変更(sでもOK)
pick 97c1a2e fix: lintエラーを回避
squash e66bfce feat: linterを準備
squash 084a4a6 fix: huskyが実行されなかったので修正
squash 2271df6 add: カタカナ変換
squash ed8109f feat: オンドゥル変換まで実装
squash 82eecfd fix: dependenciesが間違ってた
squash 69e2329 feat: fixerの追加
# 変更を確定すると、新たに画面が出るのでコミットメッセージを追加
# This is a combination of 7 commits.
# This is the 1st commit message:
feat: 初期リリース
# 確定後のログ
[detached HEAD fb52f9c] feat: 初期リリース
Date: Fri Oct 14 19:11:31 2022 +0900
11 files changed, 1874 insertions(+), 93 deletions(-)
create mode 100644 .eslintignore
create mode 100644 .eslintrc.yml
create mode 100755 .husky/pre-commit
create mode 100644 .prettierignore
create mode 100644 .prettierrc
create mode 100644 src/lib/ondulish.ts
Successfully rebased and updated refs/heads/v1.0.0.
~/repo/shivase/textlint-rule-ondul-style v1.0.0 4m 22
1つになったか確認してみましょう。
$ git log
commit fb52f9c50fd3a1684b2a0ebf5c8b371858685e7c (HEAD -> v1.0.0)
Author: shivase <keitti05@gmail.com>
Date: Fri Oct 14 19:11:31 2022 +0900
feat: 初期リリース
commit e0080b86d540770ef6373103a3cbc426dc5ace66 (main)
Author: shivase <keitti05@gmail.com>
Date: Fri Oct 14 18:30:00 2022 +0900
feat: initial commit
$ git push origin v.1.0.0
Enumerating objects: 24, done.
Counting objects: 100% (24/24), done.
Delta compression using up to 2 threads
Compressing objects: 100% (12/12), done.
Writing objects: 100% (16/16), 31.61 KiB | 4.52 MiB/s, done.
Total 16 (delta 3), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/shivase/textlint-rule-ondul-style.git
+ 69e2329...fb52f9c v1.0.0 -> v1.0.0 (forced update)
これでリリース準備ができました!
次はテストを書いて、リリースまでしていきます。
プラグイン参考にしたリポジトリ
Fixerの設定
textlint-ja/textlint-rule-ja-no-abusage: よくある日本語の誤用をチェックするtextlintルール
kuromojinの使い方
textlint-ja/textlint-rule-no-dropping-i: い抜き言葉をチェックするtextlintルール
Discussion