コンテンツにスキップ

プログラマティック API

madr-lint は CLI に加えて ESM ライブラリのエントリ(madr-lint)を提供します。 エディタ連携、カスタムランナー、あるいは使い捨てのスクリプトを作るのに便利です。

import {
parseFile,
runRule,
runRulesOnFile,
runRulesOnProject,
buildProjectFile,
recommended,
rules,
defineConfig,
} from 'madr-lint';

parseFile は YAML frontmatter、v2 の本文リストメタデータ、統合された metadata ビュー、 mdast ツリー、そして本文を返します。

import { parseFile } from 'madr-lint';
const parsed = parseFile('---\nstatus: accepted\n---\n\n# ADR-0001\n');
parsed.frontmatter; // { status: 'accepted' }
parsed.metadata; // { status: 'accepted' } (frontmatter + v2 list)
parsed.ast; // mdast Root
import { runRule, rules } from 'madr-lint';
const diagnostics = runRule(
rules.statusEnum,
{ path: '0001-x.md', content: '---\nstatus: draft\n---\n\n# x\n' },
{ options: { caseSensitive: false } },
);
// → [{ ruleName: 'madr/status-enum', messageId: 'invalidStatus', ... }]

ランナーが返す各 diagnostic は自己完結しています。機械的に適用できる修正内容と ドキュメントリンクを持つため、消費側がルール名から組み立て直す必要はありません。

interface Diagnostic {
ruleName: string; // 例: 'madr/required-sections'
messageId: string; // ルールの `messages` マップのキー
severity: 'error' | 'warn';
path: string; // POSIX 相対パス
loc?: { line: number; column: number };
data?: Record<string, unknown>;
suggestion: string | null; // 具体的な修正内容。ルールが定義していなければ null
docsUrl: string; // rule.meta.docs.url(core/internal-error はリポジトリ)
fixable: boolean; // この diagnostic に自動修正があるか
fix?: (fixer: Fixer) => TextEdit | TextEdit[] | null; // 一時的。「自動修正」を参照
}

suggestiondocsUrl は、ルールの宣言的な meta.suggestions[messageId]meta.docs.url から、ランナーがレポート時に解決します。suggestion はメッセージと 同じく diagnostic の data で補間されます。ルールがこれらの文字列を手続き的に 組み立てることはありません。

fixable は永続的な真偽値です。キャッシュや json 出力にシリアライズされ、text レポーターは 🔧 fixable マーカーを表示します。fix サンクは一時的です。JSON シリアライズで失われる(そのためキャッシュから復元した diagnostic には存在しない) クロージャで、自動修正のアプライアが消費します。自動修正を参照してください。

ファイル単位のルールをまとめて実行する

Section titled “ファイル単位のルールをまとめて実行する”

複数のファイル単位ルールは単一の AST 走査を共有します。

import { runRulesOnFile, rules } from 'madr-lint';
const diagnostics = runRulesOnFile(
[rules.requiredSections, rules.statusEnum],
{ path: '0001-x.md', content: fileContents },
{ severity: 'error' },
);

プロジェクト(ファイル間)ルールを実行する

Section titled “プロジェクト(ファイル間)ルールを実行する”

ファイル間ルール(番号の一意性、supersedes グラフ、リンク切れ)は、buildProjectFile で 構築した、事前にパース済みの ProjectFile の配列を受け取ります。

import { runRulesOnProject, buildProjectFile, rules } from 'madr-lint';
const files = [
buildProjectFile({ path: 'docs/adr/0001-a.md', content: a }),
buildProjectFile({ path: 'docs/adr/0001-b.md', content: b }),
];
const diagnostics = runRulesOnProject(
[rules.noDuplicateNumbering],
files,
{ severity: 'error' },
);

バッチでのルールごとのオプション

Section titled “バッチでのルールごとのオプション”

それぞれ独自のオプションを必要とする複数のルールを実行する場合は、optionsByRule (名前 → オプション)を渡します。

runRulesOnFile([rules.filenameFormat], file, {
optionsByRule: {
'madr/filename-format': { pattern: '^ADR-[0-9]+\\.md$' },
},
});
import { recommended, defineConfig } from 'madr-lint';
recommended['madr/required-sections']; // 'error'
const config = defineConfig({
extends: ['madr-lint:recommended'],
rules: { 'madr/no-numbering-gap': 'warn' },
});

ベースラインをプログラムから構築・適用できます — CLI の --baseline / --update-baseline フラグと同じ減算処理です。

import { buildBaseline, applyBaseline, writeBaseline, baselinePath } from 'madr-lint';
const baseline = buildBaseline(diagnostics);
writeBaseline(baselinePath(process.cwd()), baseline);
// 以降の lint 実行時:
const { kept, hidden } = applyBaseline(newDiagnostics, baseline);

ルールは meta.fixable: 'code' を宣言し、context.report(...) に遅延評価の fix サンクを添えることで自動修正に対応します。サンクは本文(mdast)座標 (node.position.*.offset と同じ空間)で動作し、Fixer がファイル全体のオフセットへ 変換します。そのため frontmatter が除去されていても修正は正しく適用されます。

修正が対象とするオフセット範囲は、通常 context.metadataValueLoc から得ます。 context.metadataValueLoc[field] は、metadata のキーのうち、実効値が v2 の先頭 リストに由来し、かつ単一の連続したテキストトークン(インライン記法を含まない) だった場合に、本文座標での { start, end } を返します — 本文をスライスして元の値と 一致することを検証済みです。実効値が frontmatter に由来するキーは存在しません (frontmatter はパース前に除去されるため本文オフセットを持たず、YAML を踏まえた 書き換えが必要になります)。そのため、範囲が存在するときだけ fix を添付します。

const valueRange = context.metadataValueLoc?.status;
context.report({
messageId: 'invalidStatus',
data: { status, allowed },
// metadataValueLoc に対象範囲があるときだけ fix を添付する。
// 見送る場合は fix を省略する(またはサンクから null を返す)。
...(valueRange && {
fix: (fixer) =>
fixer.replaceRange([valueRange.start, valueRange.end], 'accepted'),
}),
});

アプライアのプリミティブはツールやファイル間修正のためにエクスポートされています。

import {
applyEdits,
makeFixer,
fixFileContent,
frontmatterOffset,
} from 'madr-lint';
// 本文オフセットを除去済み frontmatter の分だけずらし、差し替える。
const fixer = makeFixer(frontmatterOffset(content)); // fileOffset = body + frontmatter
const edit = fixer.replaceRange([start, end], 'accepted'); // TextEdit(ファイル全体)
const fixed = applyEdits(content, [edit]); // ソートし、重複を除去し、1 パスで適用

fixFileContent(content, lint) は 1 ファイルの不動点ループを実行します。lint コールバックが返す診断(抑制・ベースライン適用済みであるべき)から編集を収集し、適用して 再 lint し、MAX_FIX_PASSES(10)まで繰り返します。戻り値は { fixedContent, remaining, changed, passes, applied } です。

ファイル間(プロジェクトルール)の修正には、collectProjectFixes(diagnostics, contentByPath) が修正の編集を対象ファイルの path ごとにグループ化します — プロジェクトルールの修正はファイル全体の座標で動作し(YAML frontmatter を編集する 場合があるため)、1 パスにつき 1 ファイルあたり最大 1 件の修正が適用されます。 applyEditsCounted(content, edits)applyEdits のカウント付き版で、 { text, applied } を返します。applied は重複・範囲チェックのフィルタリング後に 実際に適用された編集の数です。

エクスポート説明
parseFileコンテンツをパース → frontmatter、metadata、mdast、body
extractListMetadatamdast ツリーから v2 の本文リストメタデータを抽出
frontmatterOffsetgray-matter が除去する長さ(fileOffset = bodyOffset + this
applyEditsTextEdit を文字列に適用(ソート、重複除去、1 パス)
applyEditsCountedapplyEdits のカウント付き版。{ text, applied } を返す(適用された編集数)
makeFixer本文オフセットをファイル全体の TextEdit に変換する Fixer を生成
collectFixes診断の fix サンクを呼び出し → ファイル全体の TextEdit[]
collectProjectFixesプロジェクトルール(ファイル間)の修正を対象ファイルのパスごとに収集
fixFileContentlint コールバックに対しファイル単位の自動修正不動点を実行
unifiedDiff2 つの文字列間の統合 diff を生成(--fix-dry-run が使用)
MAX_FIX_PASSES不動点の反復上限(10)
runRule単一のファイル単位ルールを実行
runRulesOnFileファイル単位ルールを 1 回の AST 走査で実行
runRulesOnProjectファイル間(プロジェクト)ルールを実行
buildProjectFileプロジェクトルール向けにファイルを事前パース
rules組み込みルールの名前空間
recommended推奨プリセットの重大度
defineConfig型安全な設定ヘルパー
RuleOptionsErrorルールオプションの検証に失敗したときにスローされる
isProjectRuleプロジェクトルールとファイル単位ルールを判別する型ガード
buildBaseline診断結果を Baseline(path → rule → messageId → count)に集約
applyBaseline診断結果リストから Baseline を減算し { kept, hidden } を返す
loadBaselineベースラインファイルを読み込んでパース。存在しない・不正な場合は null
serializeBaselineBaseline を決定論的に JSON テキストへシリアライズ
writeBaselineBaseline をシリアライズしてディスクに書き込み、親ディレクトリも作成
baselinePath指定した cwd に対する .madr-lint/baseline.json の絶対パスを解決
BASELINE_VERSION現在のベースラインのオンディスクスキーマバージョン
INTERNAL_ERROR_RULE_NAMEランナーが投げるエラー用の予約ルール名。ベースライン化されない

型(RuleProjectRuleRuleContextDiagnosticRuleSeverityBaselineBaselineApplyResult など)は、カスタムルールやツールの作成のために エクスポートされています。