|
| 1 | +--- |
| 2 | +title: "textlint v15.0.0をリリースしました。非推奨APIの削除とNode.js 20+サポート/MCPサーバの改善" |
| 3 | +author: azu |
| 4 | +layout: post |
| 5 | +date : 2025-06-25T09:00 |
| 6 | +category: textlint |
| 7 | +tags: |
| 8 | + - textlint |
| 9 | + - JavaScript |
| 10 | + - Node.js |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +textlint v15.0.0をリリースしました! |
| 15 | + |
| 16 | +- [Release v15.0.0 · textlint/textlint](https://github.com/textlint/textlint/releases/tag/v15.0.0) |
| 17 | +- [textlint v15.0.0 | textlint](https://textlint.org/blog/2025/06/22/textlint-15/) |
| 18 | + |
| 19 | +textlint v15は、v12.3.0から非推奨としてマークされていた古いAPIをすべて削除するメジャーリリースです。 |
| 20 | + |
| 21 | +## textlint v15.0.0の変更点 |
| 22 | + |
| 23 | +主要な変更点は次の通りです。 |
| 24 | + |
| 25 | +## Breaking Changes |
| 26 | + |
| 27 | +- Node.js 20+のサポート(Node.js 16,18はサポート終了 |
| 28 | +- `TextLintEngine`を削除 → `createLinter()` |
| 29 | +- `TextFixEngine`を削除 → `createLinter()` |
| 30 | +- `TextLintCore`を削除 → `createLinter()`または`@textlint/kernel` |
| 31 | +- `textlint`(シングルトンインスタンス)を削除 → `createLinter()` |
| 32 | +- `createFormatter`を削除 → `loadFormatter()`を |
| 33 | + |
| 34 | +## Node.js 20以上が必要 |
| 35 | + |
| 36 | +textlint v15では、Node.js 20.0.0以上が必要になりました。 |
| 37 | +Nodejs 18はEOL(End of Life)となっているため、Node.js 20以上を使用する必要があります。 |
| 38 | + |
| 39 | +- <https://nodejs.org/en/about/previous-releases> |
| 40 | + |
| 41 | +## 非推奨APIの削除 |
| 42 | + |
| 43 | +`textlint`パッケージをNode.jsモジュールとして利用する場合のみ影響を受ける変更です。 |
| 44 | + |
| 45 | +textlint v15では、非推奨となっていた次のAPIが完全に削除されました。 |
| 46 | + |
| 47 | +移行ガイドは次のページに用意しています。 |
| 48 | + |
| 49 | +- [Migration Guide to textlint v15 | textlint](https://textlint.org/docs/migration-to-v15/) |
| 50 | + |
| 51 | +### `TextLintEngine` → `createLinter()` |
| 52 | + |
| 53 | +**変更前(v14以前):** |
| 54 | +```javascript |
| 55 | +const { TextLintEngine } = require("textlint"); |
| 56 | + |
| 57 | +const engine = new TextLintEngine({ |
| 58 | + configFile: ".textlintrc.json" |
| 59 | +}); |
| 60 | +const results = await engine.executeOnFiles(["*.md"]); |
| 61 | +const output = engine.formatResults(results); |
| 62 | +console.log(output); |
| 63 | +``` |
| 64 | + |
| 65 | +**変更後(v15以降):** |
| 66 | +```javascript |
| 67 | +import { createLinter, loadTextlintrc, loadLinterFormatter } from "textlint"; |
| 68 | + |
| 69 | +const descriptor = await loadTextlintrc({ |
| 70 | + configFilePath: ".textlintrc.json" |
| 71 | +}); |
| 72 | +const linter = createLinter({ descriptor }); |
| 73 | +const results = await linter.lintFiles(["*.md"]); |
| 74 | +const formatter = await loadLinterFormatter({ formatterName: "stylish" }); |
| 75 | +const output = formatter.format(results); |
| 76 | +console.log(output); |
| 77 | +``` |
| 78 | + |
| 79 | +### `TextFixEngine` → `createLinter()` |
| 80 | + |
| 81 | +**変更前(v14以前):** |
| 82 | +```javascript |
| 83 | +const { TextFixEngine } = require("textlint"); |
| 84 | + |
| 85 | +const engine = new TextFixEngine(); |
| 86 | +const results = await engine.executeOnFiles(["*.md"]); |
| 87 | +``` |
| 88 | + |
| 89 | +**変更後(v15以降):** |
| 90 | +```javascript |
| 91 | +import { createLinter, loadTextlintrc } from "textlint"; |
| 92 | + |
| 93 | +const descriptor = await loadTextlintrc(); |
| 94 | +const linter = createLinter({ descriptor }); |
| 95 | +const results = await linter.fixFiles(["*.md"]); |
| 96 | +``` |
| 97 | + |
| 98 | +### `TextLintCore` → `createLinter()` |
| 99 | + |
| 100 | +**変更前(v14以前):** |
| 101 | +```javascript |
| 102 | +const { TextLintCore } = require("textlint"); |
| 103 | + |
| 104 | +const textlint = new TextLintCore(); |
| 105 | +textlint.setupRules({ |
| 106 | + "rule-name": require("./my-rule") |
| 107 | +}); |
| 108 | +const result = await textlint.lintText("Hello world", "test.md"); |
| 109 | +``` |
| 110 | + |
| 111 | +**変更後(v15以降):** |
| 112 | +```javascript |
| 113 | +import { createLinter } from "textlint"; |
| 114 | +import { TextlintKernelDescriptor } from "@textlint/kernel"; |
| 115 | +import { moduleInterop } from "@textlint/module-interop"; |
| 116 | + |
| 117 | +const descriptor = new TextlintKernelDescriptor({ |
| 118 | + rules: [ |
| 119 | + { |
| 120 | + ruleId: "rule-name", |
| 121 | + rule: moduleInterop((await import("./my-rule")).default) |
| 122 | + } |
| 123 | + ] |
| 124 | +}); |
| 125 | +const linter = createLinter({ descriptor }); |
| 126 | +const result = await linter.lintText("Hello world", "test.md"); |
| 127 | +``` |
| 128 | + |
| 129 | +## その他の改善 |
| 130 | + |
| 131 | +### Exit Statusの改善 |
| 132 | + |
| 133 | +textlint v15では、ESLintの動作に合わせてExit Statusが改善されました。 |
| 134 | + |
| 135 | +- Fatalエラー: Exit Status 2を返す(従来は1) |
| 136 | +- Lintエラー: Exit Status 1を返す(変更なし) |
| 137 | +- 成功: Exit Status 0を返す(変更なし) |
| 138 | + |
| 139 | +```bash |
| 140 | +# v15+ behavior |
| 141 | +textlint nonexistent-file.md # Exit Status: 2 (file search error) |
| 142 | +textlint file-with-errors.md # Exit Status: 1 (lint errors) |
| 143 | +textlint clean-file.md # Exit Status: 0 (success) |
| 144 | +``` |
| 145 | + |
| 146 | +### 絶対パスのファイルも`.textlintignore`のパターンを尊重 |
| 147 | + |
| 148 | +textlint v15では、絶対パスのファイルが`.textlintignore`パターンを正しく尊重しない問題が修正されました。 |
| 149 | + |
| 150 | +以前は、`.textlintignore`に記載されたパターンにマッチしていても、`textlint`コマンドに絶対パスとして渡された場合は無視されていませんでした。 |
| 151 | +textlint v15では、絶対パスのファイルも`.textlintignore`のパターンを正しく尊重するようになりました。 |
| 152 | + |
| 153 | +```bash |
| 154 | +# Before v15 (Bug) |
| 155 | +textlint /absolute/path/to/ignored-file.md # Lintされてしまっていた |
| 156 | + |
| 157 | +# v15+ (Fixed) |
| 158 | +textlint /absolute/path/to/ignored-file.md # Lintの対象外となった |
| 159 | +``` |
| 160 | + |
| 161 | +詳しくは、[GitHub Issue #1412](https://github.com/textlint/textlint/issues/1412)を参照してください。 |
| 162 | + |
| 163 | +### Model Context Protocol (MCP)の統合強化 |
| 164 | + |
| 165 | +textlint v14.8.0から、`textlint --mcp`でtextlintをMCPサーバーとして起動できます。 |
| 166 | + |
| 167 | +- <https://textlint.org/docs/mcp/> |
| 168 | + |
| 169 | +textlint v15では、MCPのサポートを改善しています。 |
| 170 | +2025-06-18の[Key Changes - Model Context Protocol](https://modelcontextprotocol.io/specification/2025-06-18/changelog)に基づいて、以下の変更が行われました。 |
| 171 | + |
| 172 | +- [Structured Content](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content): 出力結果の構造を事前に定義することで、AIツールが結果をより正確に解釈できるようになりました |
| 173 | + - [MCP 2025-06-18 で追加された structured tool output を試す](https://zenn.dev/sushichaaaan/articles/fd57bbaa25287c) |
| 174 | +- [Output Schema](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content): 出力結果のJSON Schemaを提供することで、AIツールが結果を正確に解釈できるようにしました |
| 175 | +- `isError: true`の追加: エラーが発生した場合、`isError: true`を設定することで、AIツールがエラー状態を認識できるようにしました |
| 176 | + |
| 177 | +詳しくは、次のGitHub Issueを参照してください。 |
| 178 | + |
| 179 | +- [Enhance MCP Server Support with 2025-06-18 Specification Updates · Issue #1563 · textlint/textlint](https://github.com/textlint/textlint/issues/1563) |
| 180 | + |
| 181 | +## まとめ |
| 182 | + |
| 183 | +textlint v15では、非推奨となっているものを削除したり、動作の一貫性を改善するような変更をしています。 |
| 184 | + |
| 185 | +またtextlintの内部的にも、[pnpm](https://pnpm.io/)と[Vitest](https://vitest.dev/)への移行して、 |
| 186 | +CIの合計時間が21m 5s → 7m 20sに短縮しています。(Windowsでnpmのインストールが遅かったのがボトルネック) |
| 187 | + |
| 188 | +- [use pnpm instead of npm · Issue #1537 · textlint/textlint](https://github.com/textlint/textlint/issues/1537) |
| 189 | +- [refactor: migrate test runner from Mocha to Vitest by azu · Pull Request #1544 · textlint/textlint](https://github.com/textlint/textlint/pull/1544) |
| 190 | + |
| 191 | +他にも[Merge Gatekeeper](https://github.com/upsidr/merge-gatekeeper)を入れてAuto Mergeをできるようにしたり、Netlifyのキャッシュがやたら不安定なので[Deploy PR Preview action](https://github.com/rossjrw/pr-preview-action)を使ってPRのプレビューを作成するなどの改善をしています。 |
| 192 | + |
| 193 | +- [CI: add Merge Gatekeeper workflow for pull requests by azu · Pull Request #1577 · textlint/textlint](https://github.com/textlint/textlint/pull/1577) |
| 194 | +- [feat: replace Netlify with pr-preview-action by azu · Pull Request #1580 · textlint/textlint](https://github.com/textlint/textlint/pull/1580) |
| 195 | + |
| 196 | +この辺を整理したので、textlint自体の開発体験もだいぶ良くなっています。 |
| 197 | + |
| 198 | +まだやるべきことはたくさんあるので、興味あるひとは是非Contributorとして参加してください! |
| 199 | + |
| 200 | +- [Meta: ECMAScript Modules · Issue #1307 · textlint/textlint](https://github.com/textlint/textlint/issues/1307) |
| 201 | +- [Recreate --cache flag · Issue #1327 · textlint/textlint](https://github.com/textlint/textlint/issues/1327) |
| 202 | +- [ESLint for TypeScript · Issue #685 · textlint/textlint](https://github.com/textlint/textlint/issues/685) |
| 203 | +- [feat: migrate from Mocha to Node.js built-in test runner (node:test) · Issue #1545 · textlint/textlint](https://github.com/textlint/textlint/issues/1545) |
| 204 | + - これは今回のメジャーアップデートでNode.js 18が切れたので可能になった |
| 205 | + |
| 206 | +実験的なものとして[textlint-rule-preset-ai-writing](https://github.com/textlint-ja/textlint-rule-preset-ai-writing)とか書きながら考えていましたが、Linterの役割はちょっと広がってきているのかなと思いました。 |
| 207 | +[Biome v2](https://biomejs.dev/blog/biome-v2/)で追加された[Assist](https://biomejs.dev/assist/)もそうですが、ErrorやWarningじゃなくてSuggestionに近い部分も増えてきているように感じます。SuggestはLinterの役割なのかは微妙なところですが、この辺の機能はLinterの延長として実装されることが多い感じはしています。 |
| 208 | + |
| 209 | +特に自然言語はauto fixが難しいので、Suggestionのような形でHintを提供できる仕組みがあると、人間とAIにとっても使える感じのツールになるんじゃないかなーと思っています。 |
| 210 | + |
| 211 | +textlintでは、Contributionを歓迎しています。 |
| 212 | + |
| 213 | +- [Issues · textlint/textlint](https://github.com/textlint/textlint/issues) |
| 214 | + |
| 215 | +また、GitHub Sponsorsでのサポートも歓迎しています。 |
| 216 | + |
| 217 | +- [Sponsor @azu on GitHub Sponsors](https://github.com/sponsors/azu) |
| 218 | + |
| 219 | +## 関連リンク |
| 220 | + |
| 221 | +- [textlint v15.0.0のリリースノート](https://textlint.org/blog/2025/06/22/textlint-15/) |
| 222 | +- [v15移行ガイド](https://textlint.org/docs/migration-to-v15.html) |
| 223 | +- [新API ドキュメント](https://textlint.org/docs/use-as-modules.html) - 新しいAPIのドキュメント |
| 224 | +- [実装例](https://github.com/textlint/textlint/tree/master/examples/use-as-module) - 新しいAPIを使った例 |
0 commit comments