Skip to main content

textlint v15.0.0

· 8 min read
azu
textlint creator

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 - Use createLinter() instead
  • Removed TextFixEngine - Use createLinter() instead
  • Removed TextLintCore - Use createLinter() or @textlint/kernel instead
  • Removed textlint (singleton instance) - Use createLinter() instead
  • Removed createFormatter - Use loadFormatter() 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:

TextLintEnginecreateLinter()

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);

TextFixEnginecreateLinter()

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"]);

TextLintCorecreateLinter() 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 textlintcreateLinter()

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");

createFormatterloadFormatter

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 APINew APIMethod 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

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!