textlint v15.0.0
We are pleased to announce the release of textlint v15.0.0. This release includes major breaking changes by removing all deprecated legacy APIs.
Summary
textlint v15 is a major release that removes all deprecated APIs that have been marked as deprecated since v12.3.0. This significantly reduces the codebase size and maintenance burden while providing a cleaner, more modern API surface.
Breaking Changes:
- Requires Node.js 20 or later
- Removed
TextLintEngine
- UsecreateLinter()
instead - Removed
TextFixEngine
- UsecreateLinter()
instead - Removed
TextLintCore
- UsecreateLinter()
or@textlint/kernel
instead - Removed
textlint
(singleton instance) - UsecreateLinter()
instead - Removed
createFormatter
- UseloadFormatter()
instead
New Features:
- Enhanced MCP integration with structured output, JSON schema validation, and improved error handling
Breaking Changes
Node.js 20+ Required
textlint v15 requires Node.js 20.0.0 or later. This change aligns with Node.js LTS support and enables the use of modern JavaScript features.
Migration Required:
- Update your Node.js version to 20.0.0 or later
- Update your CI/CD environments to use Node.js 20+
- Update any Docker images or deployment configurations
Removed Deprecated APIs
Removed module
Field for Webpack Compatibility
In textlint v15, the module
field has been removed from all @textlint packages that had both type: "commonjs"
and a module
field, to improve compatibility with Webpack. Previously, having both fields caused resolution issues in Webpack, requiring users to add workarounds.
Details:
- Removed the
module
field from 15 @textlint packages - No longer necessary to set custom
mainFields
in Webpack - Issue #1587 / PR #1588
With this change, Webpack users can use textlint packages out-of-the-box, and module resolution issues are resolved without extra configuration.
The following deprecated APIs have been completely removed from the textlint
package:
TextLintEngine
→ createLinter()
Before (v14 and earlier):
const { TextLintEngine } = require("textlint");
const engine = new TextLintEngine({
configFile: ".textlintrc.json"
});
const results = await engine.executeOnFiles(["*.md"]);
const output = engine.formatResults(results);
console.log(output);
After (v15+):
import { createLinter, loadTextlintrc, loadLinterFormatter } from "textlint";
const descriptor = await loadTextlintrc({
configFilePath: ".textlintrc.json"
});
const linter = createLinter({ descriptor });
const results = await linter.lintFiles(["*.md"]);
const formatter = await loadLinterFormatter({ formatterName: "stylish" });
const output = formatter.format(results);
console.log(output);
TextFixEngine
→ createLinter()
Before (v14 and earlier):
const { TextFixEngine } = require("textlint");
const engine = new TextFixEngine();
const results = await engine.executeOnFiles(["*.md"]);
After (v15+):
import { createLinter, loadTextlintrc } from "textlint";
const descriptor = await loadTextlintrc();
const linter = createLinter({ descriptor });
const results = await linter.fixFiles(["*.md"]);
TextLintCore
→ createLinter()
or @textlint/kernel
Before (v14 and earlier):
const { TextLintCore } = require("textlint");
const textlint = new TextLintCore();
textlint.setupRules({
"rule-name": require("./my-rule")
});
const result = await textlint.lintText("Hello world", "test.md");
After (v15+):
import { createLinter } from "textlint";
import { TextlintKernelDescriptor } from "@textlint/kernel";
import { moduleInterop } from "@textlint/module-interop";
const descriptor = new TextlintKernelDescriptor({
rules: [
{
ruleId: "rule-name",
rule: moduleInterop((await import("./my-rule")).default)
}
]
});
const linter = createLinter({ descriptor });
const result = await linter.lintText("Hello world", "test.md");
Singleton textlint
→ createLinter()
Before (v14 and earlier):
const { textlint } = require("textlint");
textlint.setupRules({ "rule-name": ruleModule });
const result = await textlint.lintText("text", "file.md");
After (v15+):
import { createLinter } from "textlint";
import { TextlintKernelDescriptor } from "@textlint/kernel";
const descriptor = new TextlintKernelDescriptor({
rules: [{ ruleId: "rule-name", rule: ruleModule }]
});
const linter = createLinter({ descriptor });
const result = await linter.lintText("text", "file.md");
createFormatter
→ loadFormatter
Before (v14 and earlier):
const { createFormatter } = require("@textlint/linter-formatter");
const formatter = createFormatter({
formatterName: "stylish"
});
const output = formatter([results]);
After (v15+):
import { loadFormatter } from "@textlint/linter-formatter";
const formatter = await loadFormatter({
formatterName: "stylish"
});
const output = formatter.format(results);
Improved Exit Status Handling
textlint v15 improves exit status handling to align with ESLint's behavior, helping scripts distinguish between different types of errors.
Key Changes:
- File search errors now return exit status 2 (previously 1)
- Lint errors continue to return exit status 1
- Success cases return exit status 0
# v15+ behavior (now matches ESLint)
textlint nonexistent-file.md # Exit Status: 2 (file search error)
textlint file-with-errors.md # Exit Status: 1 (lint errors)
textlint clean-file.md # Exit Status: 0 (success)
This enables better CI/CD error handling and aligns with ESLint's behavior. For comprehensive documentation, see the Exit Status FAQ.
Fixed Ignore Pattern Handling for Absolute Paths
textlint v15 fixes a bug where absolute file paths did not respect .textlintignore
patterns.
What was fixed:
# Before v15 (Bug)
textlint /absolute/path/to/ignored-file.md # ❌ Would lint even if ignored
# v15+ (Fixed)
textlint /absolute/path/to/ignored-file.md # ✅ Properly respects .textlintignore
This fix is important for CI/CD systems and IDE integrations that use absolute paths. See GitHub Issue #1412 for details.
Migration Guide
📖 For detailed migration instructions with comprehensive examples, see the complete migration guide.
Quick Reference
Deprecated API | New API | Method Change |
---|---|---|
new TextLintEngine() | createLinter() | executeOnFiles() → lintFiles() |
new TextFixEngine() | createLinter() | executeOnFiles() → fixFiles() |
new TextLintCore() | createLinter() | lintText() → lintText() |
createFormatter() | loadFormatter() | Async loading, .format() method |
engine.formatResults() | loadLinterFormatter() | Separate formatter loading |
Common Migration Patterns
Pattern 1: Simple File Linting
Before:
const { TextLintEngine } = require("textlint");
const engine = new TextLintEngine();
const results = await engine.executeOnFiles(["README.md"]);
After:
import { createLinter, loadTextlintrc } from "textlint";
const descriptor = await loadTextlintrc();
const linter = createLinter({ descriptor });
const results = await linter.lintFiles(["README.md"]);
Pattern 2: Text Processing with Custom Rules
Before:
const { TextLintCore } = require("textlint");
const textlint = new TextLintCore();
textlint.setupRules({ "my-rule": myRule });
const result = await textlint.lintText("text", "file.md");
After:
import { createLinter } from "textlint";
import { TextlintKernelDescriptor } from "@textlint/kernel";
const descriptor = new TextlintKernelDescriptor({
rules: [{ ruleId: "my-rule", rule: myRule }]
});
const linter = createLinter({ descriptor });
const result = await linter.lintText("text", "file.md");
Pattern 3: Mixing Configuration and Custom Rules
Before:
const { TextLintCore } = require("textlint");
const { Config } = require("textlint/lib/config/config");
const config = new Config({ configFile: ".textlintrc.json" });
const textlint = new TextLintCore(config);
textlint.setupRules({ "custom-rule": customRule });
After:
import { createLinter, loadTextlintrc } from "textlint";
import { TextlintKernelDescriptor } from "@textlint/kernel";
const textlintrcDescriptor = await loadTextlintrc({
configFilePath: ".textlintrc.json"
});
const customDescriptor = new TextlintKernelDescriptor({
rules: [{ ruleId: "custom-rule", rule: customRule }]
});
const linter = createLinter({
descriptor: customDescriptor.concat(textlintrcDescriptor)
});
New Features
Enhanced Model Context Protocol (MCP) Support
textlint v15 introduces significant improvements to Model Context Protocol (MCP) support, making it easier to integrate textlint with AI assistants and language models.
Key Improvements:
- Structured Output: Better formatted results for AI consumption
- JSON Schema Support: Validation using output schemas
- Enhanced Error Handling: More detailed error reporting for MCP clients
- Improved Documentation: Better MCP integration examples
For more details, see the MCP documentation.
Improvements:
- Exit Status Improvement: File search errors now return exit status 2 (aligned with ESLint)
- Ignore Pattern Fix: Absolute file paths now correctly respect
.textlintignore
patterns
Improved Exit Status Handling
textlint v15 improves exit status handling to align with ESLint's behavior, helping scripts distinguish between different types of errors.
Key Changes:
- File search errors now return exit status 2 (previously 1)
- Lint errors continue to return exit status 1
- Success cases return exit status 0
# v15+ behavior (now matches ESLint)
textlint nonexistent-file.md # Exit Status: 2 (file search error)
textlint file-with-errors.md # Exit Status: 1 (lint errors)
textlint clean-file.md # Exit Status: 0 (success)
This enables better CI/CD error handling and aligns with ESLint's behavior. For comprehensive documentation, see the Exit Status FAQ.
Fixed Ignore Pattern Handling for Absolute Paths
textlint v15 fixes a bug where absolute file paths did not respect .textlintignore
patterns.
What was fixed:
# Before v15 (Bug)
textlint /absolute/path/to/ignored-file.md # ❌ Would lint even if ignored
# v15+ (Fixed)
textlint /absolute/path/to/ignored-file.md # ✅ Properly respects .textlintignore
This fix is important for CI/CD systems and IDE integrations that use absolute paths. See GitHub Issue #1412 for details.
Resources
- Migration Guide to v15 - Comprehensive migration guide with detailed examples
- New API Documentation - Complete documentation for the new APIs
- Working Example - Example project using the new APIs
- GitHub Issues - For migration support and questions
Conclusion
textlint v15 represents a significant step forward in modernizing the API surface while maintaining the powerful linting capabilities that users expect. While the migration requires some code changes, the new API provides better TypeScript support, modern ESM compatibility, and a cleaner architecture that will serve as a solid foundation for future development.
We encourage all users to migrate to the new APIs as soon as possible. The deprecated APIs have been removed, so upgrading to v15 will require code changes.
Thank you for your continued support of textlint!