markuplint
Version:
An HTML linter for all markup developers
420 lines (345 loc) • 26.4 kB
Markdown
# markuplint
## 概要
`markuplint` は markuplint リンティングエコシステムのメイン統合パッケージです。CLI ツール、プログラマティック API、テストユーティリティを提供します。コアの `MLEngine` クラスがリンティングパイプライン全体をオーケストレーションし、ファイル解決→設定ロード→パーサー選択→ルール実行→結果出力を統括します。`@markuplint/file-resolver`、`@markuplint/ml-core`、`@markuplint/rules` 等のパッケージをエンドユーザー、エディタ拡張、CI/CD 環境向けの統一インターフェースに統合しています。
## ディレクトリ構成
```
bin/
└── markuplint.mjs -- CLI 実行可能エントリーポイント
src/
├── index.ts -- パッケージエクスポート(MLEngine, テストツール, 型, i18n)
├── types.ts -- MLResultInfo 型定義
├── version.ts -- パッケージバージョン文字列(package.json から取得)
├── i18n.ts -- ロケール検出とメッセージ読み込み
├── debug.ts -- デバッグログ(名前空間: markuplint-cli)
├── global-settings.ts -- グローバル設定管理(ロケール)
├── get-json-module.ts -- 動的 JSON モジュールローダー(安全な require ラッパー)
├── v1.ts -- 非推奨 v1 API の再エクスポート
├── api/
│ ├── index.ts -- API エクスポート(MLEngine, lint)
│ ├── types.ts -- APIOptions, MLEngineEventMap
│ ├── ml-engine.ts -- MLEngine クラス(コアオーケストレーター)
│ ├── ml-engine.spec.ts -- MLEngine テスト
│ ├── lint.ts -- スタンドアロン lint() 関数
│ └── v1.ts -- 非推奨 v1 lint 関数
├── cli/
│ ├── index.ts -- CLI エントリ(引数解析、コマンドディスパッチ)
│ ├── bootstrap.ts -- meow CLI 定義(フラグ、ヘルプテキスト)
│ ├── command.ts -- lint コマンド実装
│ ├── output.ts -- レポーターディスパッチ(format → reporter)
│ ├── index.spec.ts -- CLI 統合テスト
│ ├── init/ -- --init サブコマンド(対話式ウィザード)
│ │ ├── index.ts -- 初期化フロー制御
│ │ ├── types.ts -- Langs, Category, RuleSettingMode 型
│ │ ├── create-config.ts -- ユーザー選択からの設定生成
│ │ ├── get-default-rules.ts -- ビルトインルールメタデータ抽出
│ │ ├── select-modules.ts -- 言語選択からの npm モジュールリスト
│ │ └── *.spec.ts -- 初期化ウィザードテスト
│ └── search/ -- --search サブコマンド(CSS セレクタ検索)
│ └── index.ts -- 一時ルールを使った要素検索
├── reporter/
│ ├── index.ts -- レポーターエクスポート
│ ├── standard-reporter.ts -- 詳細マルチライン形式(ソースコンテキスト付き)
│ ├── simple-reporter.ts -- コンパクト1行/違反形式
│ ├── github-reporter.ts -- GitHub Actions アノテーション形式(::error, ::warning)
│ └── github-reporter.spec.ts
└── testing-tool/
└── index.ts -- mlTest(), mlRuleTest(), mlTestFile()
```
## アーキテクチャ図
```mermaid
flowchart TD
subgraph cli ["CLI レイヤー"]
bin["bin/markuplint.mjs"]
bootstrap["bootstrap.ts\n(meow フラグ)"]
cliIndex["cli/index.ts\n(ディスパッチ)"]
cmd["command.ts\n(lint コマンド)"]
initWiz["init/ (ウィザード)"]
searchCmd["search/ (CSS セレクタ)"]
end
subgraph api ["API レイヤー"]
MLEngine["MLEngine\n(コアオーケストレーター)"]
lintFn["lint()\n(複数ファイル)"]
end
subgraph reporters ["レポーターレイヤー"]
standard["standardReporter"]
simple["simpleReporter"]
github["githubReporter"]
json["JSON 出力"]
end
subgraph testing ["テストレイヤー"]
mlTest["mlTest()"]
mlRuleTest["mlRuleTest()"]
mlTestFile["mlTestFile()"]
end
subgraph deps ["依存パッケージ"]
fileResolver["@markuplint/file-resolver\n(ファイル, 設定, パーサー)"]
mlCore["@markuplint/ml-core\n(MLCore, ルール, verify)"]
rules["@markuplint/rules\n(ビルトインルール)"]
mlConfig["@markuplint/ml-config\n(Config 型, マージ)"]
end
bin --> cliIndex
cliIndex --> bootstrap
cliIndex -->|"--init"| initWiz
cliIndex -->|"--search"| searchCmd
cliIndex -->|"ファイル指定"| cmd
cmd --> MLEngine
cmd -->|"output()"| reporters
searchCmd --> cmd
lintFn --> MLEngine
mlTest --> lintFn
mlRuleTest --> mlTest
mlTestFile --> lintFn
MLEngine --> fileResolver
MLEngine --> mlCore
MLEngine --> rules
MLEngine --> mlConfig
```
## MLEngine クラス
リンティングパイプラインをオーケストレーションする中核クラス。`strict-event-emitter` の `Emitter<MLEngineEventMap>` を継承し、型安全なイベント発行を実現。
### 静的メソッド
| メソッド | 説明 |
| -------------------------------- | ---------------------------------------------------------------------------- |
| `fromCode(sourceCode, options?)` | インラインソースコードから MLEngine を生成。内部で MLFile を解決 |
| `toMLFile(target)` | `Target`(ファイルパスまたはインラインソース)を `MLFile` インスタンスに変換 |
### インスタンスメソッド
| メソッド | 説明 |
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `exec()` | linting を実行: `setup()` → `core.verify(fix)` を呼び出し、`MLResultInfo` を返却。スキップ時は `null` |
| `setCode(code)` | ソースコードを更新し再パース。設定の再解決は行わない |
| `watchMode(enable)` | chokidar によるファイル監視の有効/無効。変更時: 設定再解決 → core 更新 → 再 lint |
| `close()` | すべてのイベントリスナーを除去しファイルウォッチャーを停止 |
| `resolveConfig(cache)` | 設定を解決(`--show-config` サポート用に公開) |
### パイプライン: setup -> provide -> exec
```mermaid
flowchart TD
Exec["exec()"] --> Setup["setup()"]
Setup --> CoreExists{"core が存在する?"}
CoreExists -->|Yes| ReturnCore["既存の core を返却"]
CoreExists -->|No| Provide["provide()"]
Provide --> ResolveConfig["resolveConfig()\n ConfigProvider.search() + マージ"]
ResolveConfig --> FileExists{"ファイルが存在する?"}
FileExists -->|No| ReturnNull1["null を返却"]
FileExists -->|Yes| ExcludeCheck{"除外対象?"}
ExcludeCheck -->|Yes| ReturnNull2["null を返却"]
ExcludeCheck -->|No| ResolveParser["resolveParser()\n パーサーモジュール選択"]
ResolveParser --> ExtCheck{"拡張子が一致?\n(--ignore-ext でなければ)"}
ExtCheck -->|No| ReturnNull3["null を返却"]
ExtCheck -->|Yes| ResolvePretenders["resolvePretenders()"]
ResolvePretenders --> ResolveRuleset["resolveRuleset()\n convertRuleset()"]
ResolveRuleset --> ResolveSchemas["resolveSchemas()"]
ResolveSchemas --> ResolveRules["resolveRules()\n プラグイン + カスタムルール"]
ResolveRules --> LoadI18n["i18n()\n ロケール読み込み"]
LoadI18n --> ReturnFabric["MLFabric を返却"]
ReturnFabric --> CreateCore["createCore(fabric)\n new MLCore(...)"]
CreateCore --> ReturnCore
ReturnCore --> Verify["core.verify(fix)"]
Verify --> EmitLint["'lint' イベント発行"]
EmitLint --> ReturnResult["MLResultInfo を返却"]
```
### 設定解決の優先順位
`resolveConfig()` メソッドは複数のソースから以下の優先順位(高い順)で設定を解決します:
```
1. options.config -- API 経由で渡されたインライン設定オブジェクト
2. options.configFile -- 明示的な設定ファイルパス(--config フラグ)
3. ConfigProvider.search()-- ファイル位置からの自動探索(--no-search-config でなければ)
4. options.defaultConfig -- フォールバック設定
5. markuplint:recommended -- 設定が一切見つからない場合のデフォルト
```
これらは `ConfigProvider.resolve()` で結合され、`@markuplint/ml-config` の `mergeConfig()` を使用して全レイヤーをマージします。
### イベントシステム
| イベント | ペイロード | 発行タイミング |
| --------------- | -------------------------------------------------- | ---------------------- |
| `log` | phase, message | 各処理段階 |
| `config` | filePath, configSet | 設定解決後 |
| `exclude` | filePath, setting | ファイルが除外された時 |
| `parser` | filePath, parserName | パーサー解決後 |
| `ruleset` | filePath, ruleset | ルールセット変換後 |
| `schemas` | filePath, schemas | スキーマ解決後 |
| `rules` | filePath, rules | ルール解決後 |
| `i18n` | filePath, locale | ロケール読み込み後 |
| `code` | filePath, sourceCode | ソースコード取得後 |
| `lint` | filePath, sourceCode, violations, fixedCode, debug | lint 完了後 |
| `lint-error` | filePath, sourceCode, error | lint エラー時 |
| `config-errors` | filePath, errors | 設定解決エラー時 |
### Watch モード
有効時、エンジンは `chokidar.FSWatcher` で設定ファイルを監視します(対象ファイル自体はエディタ/言語サーバーが管理するため監視しません):
1. `resolveConfig()` が `configSet.files` をウォッチャーに追加
2. ファイル変更時: `onChange()` が発火
3. `provide(false)` でキャッシュなしの設定再解決
4. `core.update(fabric)` で core を新しい設定で更新
5. `exec()` でファイルを再 lint
## CLI アーキテクチャ
### エントリーポイントフロー
```
bin/markuplint.mjs
-> import cli/index.ts
|-- -v -> cli.showVersion() (exit 0)
|-- -h -> cli.showHelp(0) (exit 0)
|-- --verbose -> verbosely()
|-- --init -> initialize() (exit 0/1)
|-- --create-rule -> エラーメッセージ (exit 1, @markuplint/create-rule を使用)
|-- files + --search -> search() (exit 0)
|-- files -> command() (exit 0/1)
|-- stdin (pipe) -> command([{sourceCode}]) (exit 0/1)
`-- (引数なし) -> cli.showHelp(1) (exit 1)
```
### command() の処理フロー
1. `resolveFiles()` でファイル glob を `MLFile` リストに展開
2. `ViolationCollector` を `maxCount` 制限付きで作成
3. 各ファイルに対して:
- オプション付きで `MLEngine` を作成
- `--show-config` の場合: 計算済み設定を JSON で出力して終了
- `engine.exec()` で lint を実行
- `--progressive-output` かつ JSON 以外: 即座に出力
- それ以外: メモリに結果を蓄積
- 違反を `ViolationCollector` に収集
- `--fix` の場合: 修正済みコードでファイルを上書き
4. 結果を出力(JSON: `collector.toArray()`、その他: ファイルごとに `output()` 経由)
5. `--max-warnings` しきい値をチェック
6. `hasError` を返却(終了コードとして使用)
### CLI オプション
| フラグ | 型 | デフォルト | 説明 |
| -------------------------- | ------- | ------------ | --------------------------------------------- |
| `--config`, `-c` | string | -- | 設定ファイルパス |
| `--fix` | boolean | `false` | 違反を自動修正 |
| `--format`, `-f` | string | `"Standard"` | 出力形式: Standard, Simple, GitHub, JSON |
| `--no-search-config` | boolean | `false` | 設定ファイルの自動探索を無効化 |
| `--ignore-ext` | boolean | `false` | 拡張子に関係なくファイルを lint |
| `--no-import-preset-rules` | boolean | `false` | ビルトインルールを読み込まない |
| `--locale` | string | OS ロケール | 違反メッセージのロケール |
| `--no-color` | boolean | `false` | ANSI エスケープコードを除去 |
| `--problem-only`, `-p` | boolean | `false` | 違反のあるファイルのみ表示 |
| `--allow-warnings` | boolean | `false` | 警告があっても終了コード 0 |
| `--allow-empty-input` | boolean | `true` | ファイルリストが空でもエラーにしない |
| `--show-config` | string | -- | 計算済み設定を出力(`""` または `"details"`) |
| `--verbose` | boolean | `false` | デバッグ出力を有効化 |
| `--include-node-modules` | boolean | `false` | node_modules 内のファイルを含める |
| `--severity-parse-error` | string | `"error"` | パースエラーの重大度: error, warning, off |
| `--max-count` | number | `0` | 表示する違反数の上限(0 = 制限なし) |
| `--max-warnings` | number | `-1` | 非ゼロ終了の警告数しきい値(-1 = 制限なし) |
| `--progressive-output` | boolean | `false` | 各ファイル処理後に即座に結果を出力 |
| `--init` | boolean | `false` | 対話式セットアップウィザードを実行 |
| `--search` | string | -- | CSS セレクタで要素を検索 |
## レポーターシステム
| 形式 | レポーター | 出力先 | 特徴 |
| -------- | ------------------ | ------------------------------ | ----------------------------------------------------------------- |
| Standard | `standardReporter` | stderr(違反)/ stdout(合格) | マルチライン: ソースコンテキスト、行番号、ハイライト領域 |
| Simple | `simpleReporter` | stderr / stdout | コンパクト: 1行/違反、重大度アイコン付き |
| GitHub | `githubReporter` | stderr / stdout | GitHub Actions: `::error`, `::warning`, `::notice` アノテーション |
| JSON | (command.ts 内) | stdout | 構造化 JSON、`ViolationCollector.toArray()` 経由 |
`cli/output.ts` の `output()` 関数が `--format` に応じて適切なレポーターにディスパッチします。違反は stderr に書き込み(`process.exitCode = 1` を設定)、問題のない結果は stdout に出力します。`--no-color` 時は `strip-ansi` で ANSI コードを除去します。
## テストツール
| 関数 | 用途 |
| ------------------------------------------------------ | --------------------------------------- |
| `mlTest(sourceCode, config, rules?, locale?, fix?)` | インラインソースコードをフル設定で lint |
| `mlRuleTest(rule, sourceCode, config?, fix?, locale?)` | 個別ルール実装のユニットテスト |
| `mlTestFile(target, config?, rules?, locale?, fix?)` | ファイルターゲットの統合テスト lint |
### mlRuleTest の内部動作
`mlRuleTest()` は `<current-rule>` という名前の一時的な `MLRule` を作成し、簡略化されたテスト設定を完全な markuplint `Config` に変換します:
- `config.rule` → `rules: { '<current-rule>': value }`
- `config.nodeRule` → `nodeRules`(`<current-rule>` 配下にルール設定)
- `config.childNodeRule` → `childNodeRules`(同様)
- lint 後、violations から `ruleId` を除去し、テストアサーションをルール名に非依存にする
## 初期化ウィザード(--init)
対話フロー:
1. テンプレートエンジンを複数選択(JSX, Vue, Svelte, Pug, PHP 等)
2. npm 依存パッケージのインストールを確認
3. 選択: カテゴリごとにルールをカスタマイズ or recommended プリセット使用
4. カスタマイズの場合: 各カテゴリを確認(validation, a11y, naming-convention, maintainability, style)
5. パーサー/スペックマッピングと選択ルール付きで `.markuplintrc` を生成
6. 確認されていれば npm パッケージを自動インストール
`createConfig()` 関数は以下のように設定を構築:
- 各言語をパーサーモジュールとファイル拡張子パターンにマッピング
- Vue(`@markuplint/vue-spec`)、React(`@markuplint/react-spec`)、Svelte(`@markuplint/svelte-spec`)、Alpine 用のスペックパッケージを追加
- 選択カテゴリまたは `markuplint:recommended` プリセットからルールを設定
## Search サブコマンド(--search)
ファイル群から CSS セレクタに一致する要素を検索:
1. `__CLI_SEARCH__` という名前の一時 `MLRule` を作成
2. ルールの `verify()` が `document.querySelectorAll(selectors)` でマッチを検索
3. マッチしたノードから `{file, line, col}` の位置情報を収集
4. `file:line:col` 形式で stdout に結果を出力
`command()` を `importPresetRules: false`、`problemOnly: true` で呼び出し、完全な lint パイプラインを再利用します。
## 主要ソースファイル
| ファイル | 目的 |
| ----------------------------------- | --------------------------------------------------------------------------- |
| `src/api/ml-engine.ts` | `MLEngine` クラス: パイプラインオーケストレーション、設定解決、watch モード |
| `src/api/lint.ts` | `lint()`: 複数ファイル lint の便利関数 |
| `src/api/types.ts` | `APIOptions`、`MLEngineEventMap` 型定義 |
| `src/cli/index.ts` | CLI エントリーポイント: 引数解析とコマンドディスパッチ |
| `src/cli/bootstrap.ts` | `meow` CLI 定義(全フラグとヘルプテキスト) |
| `src/cli/command.ts` | `command()`: ファイルイテレーション、違反収集、出力 |
| `src/cli/output.ts` | `output()`: レポーター選択と結果フォーマット |
| `src/reporter/standard-reporter.ts` | ソースコンテキスト付き詳細レポーター |
| `src/reporter/simple-reporter.ts` | コンパクト1行レポーター |
| `src/reporter/github-reporter.ts` | GitHub Actions アノテーションレポーター |
| `src/testing-tool/index.ts` | `mlTest()`、`mlRuleTest()`、`mlTestFile()` |
| `src/cli/init/index.ts` | 対話式初期化ウィザード制御 |
| `src/cli/init/create-config.ts` | ウィザード選択からの設定生成 |
| `src/cli/search/index.ts` | CSS セレクタ検索サブコマンド |
| `src/types.ts` | `MLResultInfo` 型定義 |
| `src/i18n.ts` | ロケール検出とメッセージセット読み込み |
| `src/debug.ts` | デバッグロガー(名前空間: `markuplint-cli`)と `verbosely()` |
| `src/global-settings.ts` | グローバル設定(ロケール)管理 |
## 外部依存関係
| 依存パッケージ | 用途 |
| --------------------------- | -------------------------------------------------------------- |
| `@markuplint/file-resolver` | ファイル解決、設定読み込み、パーサー/スキーマ/ルール解決 |
| `@markuplint/ml-config` | `Config` 型、`mergeConfig()` |
| `@markuplint/ml-core` | `MLCore`、`MLRule`、`ViolationCollector`、`convertRuleset()` |
| `@markuplint/rules` | ビルトイン lint ルール |
| `@markuplint/html-parser` | デフォルト HTML パーサー |
| `@markuplint/html-spec` | HTML 仕様定義 |
| `@markuplint/i18n` | ロケールセット型と翻訳メッセージ |
| `@markuplint/cli-utils` | CLI 出力ユーティリティ、対話プロンプト、モジュールインストーラ |
| `@markuplint/shared` | 共有ユーティリティ関数 |
| `chokidar` | ファイルシステム監視(watch モード) |
| `debug` | 名前空間付きデバッグログ |
| `meow` | CLI 引数パーサー |
| `os-locale` | OS ロケール検出 |
| `strict-event-emitter` | 型安全イベントエミッター基底クラス |
| `strip-ansi` | ANSI エスケープコード除去(--no-color) |
## 統合ポイント
```mermaid
flowchart LR
subgraph upstream ["上流"]
fileResolver["@markuplint/file-resolver\n(ファイル解決,\n設定読み込み,\nパーサー/スキーマ解決)"]
mlConfig["@markuplint/ml-config\n(Config 型, マージ)"]
mlCore["@markuplint/ml-core\n(MLCore, verify,\nViolationCollector)"]
builtinRules["@markuplint/rules\n(ビルトインルール)"]
end
subgraph pkg ["markuplint"]
engine["MLEngine\n(オーケストレーター)"]
cli["CLI\n(meow, command, output)"]
reporters["レポーター\n(standard, simple, github)"]
testTools["テストツール\n(mlTest, mlRuleTest,\nmlTestFile)"]
end
subgraph downstream ["下流"]
users["ユーザー(CLI)"]
editors["エディタ拡張(API)"]
ci["CI/CD\n(GitHub Actions 形式)"]
ruleTests["ルール作者\n(テストユーティリティ)"]
end
fileResolver --> engine
mlConfig --> engine
mlCore --> engine
builtinRules --> engine
cli --> engine
engine --> reporters
testTools --> engine
cli --> users
engine --> editors
reporters --> ci
testTools --> ruleTests
```
### 上流
- **`@markuplint/file-resolver`** -- ファイルターゲットの解決、設定ファイルの探索と読み込み、パーサー/スキーマモジュールの解決
- **`@markuplint/ml-config`** -- `Config` 型と設定レイヤー統合用の `mergeConfig()` を提供
- **`@markuplint/ml-core`** -- ドキュメントのパースとルール検証用の `MLCore`、結果集約用の `ViolationCollector` を提供
- **`@markuplint/rules`** -- デフォルトで読み込まれるビルトインルールセットを提供
### 下流
- **ユーザー** -- CLI 経由で呼び出し(`npx markuplint`)
- **エディタ拡張** -- リアルタイム linting 用に `MLEngine` API をプログラマティックに使用
- **CI/CD** -- インラインアノテーション用の GitHub Actions レポーター形式を使用
- **ルール作者** -- カスタムルール実装のユニットテストに `mlRuleTest()` を使用
## ドキュメントマップ
- [メンテナンスガイド](docs/maintenance.ja.md) -- コマンド、レシピ、トラブルシューティング