@markuplint/ml-core 4.13.2 → 5.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/ARCHITECTURE.ja.md +524 -0
  2. package/ARCHITECTURE.md +524 -0
  3. package/CHANGELOG.md +52 -2
  4. package/README.md +5 -0
  5. package/SKILL.md +61 -0
  6. package/docs/linting-pipeline.ja.md +307 -0
  7. package/docs/linting-pipeline.md +307 -0
  8. package/docs/maintenance.ja.md +210 -0
  9. package/docs/maintenance.md +210 -0
  10. package/docs/ml-dom/attr.ja.md +103 -0
  11. package/docs/ml-dom/attr.md +103 -0
  12. package/docs/ml-dom/block.ja.md +272 -0
  13. package/docs/ml-dom/block.md +272 -0
  14. package/docs/ml-dom/document.ja.md +141 -0
  15. package/docs/ml-dom/document.md +141 -0
  16. package/docs/ml-dom/element.ja.md +176 -0
  17. package/docs/ml-dom/element.md +176 -0
  18. package/docs/ml-dom/helpers.ja.md +203 -0
  19. package/docs/ml-dom/helpers.md +203 -0
  20. package/docs/ml-dom/node.ja.md +199 -0
  21. package/docs/ml-dom/node.md +199 -0
  22. package/docs/ml-dom/others.ja.md +120 -0
  23. package/docs/ml-dom/others.md +120 -0
  24. package/docs/ml-dom/overview.ja.md +102 -0
  25. package/docs/ml-dom/overview.md +102 -0
  26. package/docs/ml-dom/pretender.ja.md +269 -0
  27. package/docs/ml-dom/pretender.md +269 -0
  28. package/docs/ml-dom/rule-mapping.ja.md +371 -0
  29. package/docs/ml-dom/rule-mapping.md +371 -0
  30. package/docs/ml-dom.ja.md +18 -0
  31. package/docs/ml-dom.md +18 -0
  32. package/docs/rule-system.ja.md +287 -0
  33. package/docs/rule-system.md +287 -0
  34. package/lib/convert-ruleset.d.ts +7 -0
  35. package/lib/convert-ruleset.js +7 -0
  36. package/lib/debug.d.ts +4 -0
  37. package/lib/debug.js +4 -0
  38. package/lib/index.d.ts +4 -3
  39. package/lib/index.js +1 -1
  40. package/lib/ml-core.d.ts +37 -1
  41. package/lib/ml-core.js +171 -82
  42. package/lib/ml-dom/helper/accname.d.ts +8 -0
  43. package/lib/ml-dom/helper/accname.js +71 -55
  44. package/lib/ml-dom/helper/create-node.js +1 -0
  45. package/lib/ml-dom/helper/get-indent.d.ts +4 -1
  46. package/lib/ml-dom/helper/get-indent.js +21 -30
  47. package/lib/ml-dom/node/attr.d.ts +65 -4
  48. package/lib/ml-dom/node/attr.js +151 -53
  49. package/lib/ml-dom/node/block.d.ts +23 -2
  50. package/lib/ml-dom/node/block.js +24 -1
  51. package/lib/ml-dom/node/child-node.d.ts +9 -0
  52. package/lib/ml-dom/node/child-node.js +9 -0
  53. package/lib/ml-dom/node/comment.d.ts +7 -0
  54. package/lib/ml-dom/node/comment.js +7 -0
  55. package/lib/ml-dom/node/document-fragment.d.ts +8 -0
  56. package/lib/ml-dom/node/document-fragment.js +8 -0
  57. package/lib/ml-dom/node/document-type.d.ts +22 -0
  58. package/lib/ml-dom/node/document-type.js +25 -0
  59. package/lib/ml-dom/node/document.d.ts +88 -7
  60. package/lib/ml-dom/node/document.js +128 -32
  61. package/lib/ml-dom/node/dom-token-list.js +17 -30
  62. package/lib/ml-dom/node/element-close-tag.js +1 -0
  63. package/lib/ml-dom/node/element.d.ts +151 -5
  64. package/lib/ml-dom/node/element.js +242 -50
  65. package/lib/ml-dom/node/node-store.js +6 -15
  66. package/lib/ml-dom/node/node.d.ts +19 -1
  67. package/lib/ml-dom/node/node.js +175 -166
  68. package/lib/ml-dom/node/parent-node.js +14 -30
  69. package/lib/ml-dom/node/rule-mapper.js +7 -20
  70. package/lib/ml-dom/node/text.d.ts +19 -0
  71. package/lib/ml-dom/node/text.js +21 -0
  72. package/lib/ml-dom/node/types.d.ts +68 -0
  73. package/lib/ml-dom/token/token.d.ts +42 -0
  74. package/lib/ml-dom/token/token.js +59 -39
  75. package/lib/ml-rule/create-rule.d.ts +17 -1
  76. package/lib/ml-rule/ml-rule-context.js +7 -11
  77. package/lib/ml-rule/ml-rule.d.ts +66 -1
  78. package/lib/ml-rule/ml-rule.js +95 -25
  79. package/lib/ml-rule/types.d.ts +41 -0
  80. package/lib/plugin/plugin.d.ts +8 -0
  81. package/lib/plugin/plugin.js +8 -0
  82. package/lib/plugin/types.d.ts +21 -0
  83. package/lib/ruleset/index.d.ts +10 -0
  84. package/lib/ruleset/index.js +13 -0
  85. package/lib/test/index.d.ts +42 -1
  86. package/lib/test/index.js +39 -2
  87. package/lib/types.d.ts +10 -1
  88. package/lib/violation-collector.d.ts +33 -0
  89. package/lib/violation-collector.js +48 -28
  90. package/lib/virtual-rule.d.ts +72 -0
  91. package/lib/virtual-rule.js +233 -0
  92. package/package.json +16 -13
@@ -0,0 +1,524 @@
1
+ # @markuplint/ml-core
2
+
3
+ ## 概要
4
+
5
+ `@markuplint/ml-core` は markuplint のコアリンティングエンジンです。パースされた AST(`MLASTDocument`)を DOM ツリー(`MLDOM`)に変換し、設定されたルールをノードに適用して違反を収集します。パッケージは 3 つのサブシステムで構成されます:**MLDOM**(DOM 抽象化レイヤー)、**MLRule**(ルール実行フレームワーク)、**MLCore**(オーケストレーションエンジン)。
6
+
7
+ ## ディレクトリ構造
8
+
9
+ ```
10
+ src/
11
+ ├── index.ts — 公開 API の再エクスポート
12
+ ├── ml-core.ts — MLCore エンジンクラス
13
+ ├── types.ts — MLFabric, MLSchema 型定義
14
+ ├── convert-ruleset.ts — Config → Ruleset 変換
15
+ ├── debug.ts — デバッグログユーティリティ
16
+ ├── violation-collector.ts — 複数ファイルの違反集約
17
+ ├── ml-dom/
18
+ │ ├── index.ts — MLDOM 公開エクスポート
19
+ │ ├── node/
20
+ │ │ ├── document.ts — MLDocument(ルートノード、ルールマッピング、pretender 初期化)
21
+ │ │ ├── element.ts — MLElement(属性、セレクタ、名前空間)
22
+ │ │ ├── node.ts — MLNode(全ノードの抽象基底クラス)
23
+ │ │ ├── parent-node.ts — MLParentNode(querySelector, children)
24
+ │ │ ├── character-data.ts — MLCharacterData(テキスト系の抽象基底)
25
+ │ │ ├── text.ts — MLText
26
+ │ │ ├── comment.ts — MLComment
27
+ │ │ ├── attr.ts — MLAttr(属性トークン)
28
+ │ │ ├── block.ts — MLBlock(プリプロセッサブロック)
29
+ │ │ ├── document-fragment.ts — MLDocumentFragment
30
+ │ │ ├── document-type.ts — MLDocumentType
31
+ │ │ ├── element-close-tag.ts — MLElementCloseTag
32
+ │ │ ├── rule-mapper.ts — RuleMapper(ルールセット → ノードマッピング)
33
+ │ │ ├── types.ts — ノード型定数、AccessibilityProperties
34
+ │ │ ├── node-list.ts — NodeList/HTMLCollection ユーティリティ
35
+ │ │ └── unexpected-call-error.ts — 未サポート DOM メソッドのエラー
36
+ │ ├── token/
37
+ │ │ └── token.ts — MLToken(位置情報付き基底トークン)
38
+ │ ├── helper/
39
+ │ │ ├── accname.ts — アクセシブル名の計算
40
+ │ │ ├── create-node.ts — AST → MLDOM ノードファクトリ
41
+ │ │ ├── walkers.ts — ツリー走査(同期/非同期ウォーカー)
42
+ │ │ ├── get-indent.ts — インデント解析
43
+ │ │ └── debug.ts — デバッグマップ生成
44
+ │ └── manipulations/
45
+ │ ├── child-node-methods.ts — ChildNode インターフェーススタブ
46
+ │ └── get-children.ts — 要素の子要素抽出
47
+ ├── virtual-rule.ts — Named nodeRule の展開(expandNamedNodeRules)
48
+ ├── virtual-rule.spec.ts — 仮想ルールのユニットテスト
49
+ ├── ml-rule/
50
+ │ ├── ml-rule.ts — MLRule クラス(ルール実行)
51
+ │ ├── ml-rule-context.ts — MLRuleContext(レポート収集)
52
+ │ ├── create-rule.ts — createRule ファクトリ
53
+ │ ├── create-test-rule.ts — テスト用ルールファクトリ
54
+ │ └── types.ts — RuleSeed, Checker 型
55
+ ├── ruleset/
56
+ │ └── index.ts — Ruleset クラス(rules + nodeRules + childNodeRules)
57
+ ├── plugin/
58
+ │ ├── plugin.ts — createPlugin ファクトリ
59
+ │ ├── types.ts — Plugin, PluginCreator 型
60
+ │ └── index.ts — Plugin エクスポート
61
+ ├── test/
62
+ │ └── index.ts — createTestDocument, createTestElement, dummySchemas
63
+ └── utils/
64
+ ├── index.ts — ユーティリティエクスポート
65
+ ├── get-location-from-chars.ts — 文字位置解決
66
+ └── string-splice.ts — 文字列スプライスヘルパー
67
+ ```
68
+
69
+ ## アーキテクチャ図
70
+
71
+ ```mermaid
72
+ flowchart TD
73
+ subgraph upstream ["上流依存パッケージ"]
74
+ mlAst["@markuplint/ml-ast\n(AST 型)"]
75
+ mlConfig["@markuplint/ml-config\n(Config, RuleConfigValue)"]
76
+ mlSpec["@markuplint/ml-spec\n(HTML/ARIA 仕様)"]
77
+ htmlSpec["@markuplint/html-spec\n(デフォルト仕様データ)"]
78
+ htmlParser["@markuplint/html-parser\n(デフォルトパーサー)"]
79
+ parserUtils["@markuplint/parser-utils\n(ParserOptions)"]
80
+ selector["@markuplint/selector\n(CSS セレクタマッチング)"]
81
+ i18n["@markuplint/i18n\n(ロケール、翻訳)"]
82
+ shared["@markuplint/shared\n(ユーティリティ)"]
83
+ configPresets["@markuplint/config-presets\n(組み込みプリセット)"]
84
+ end
85
+
86
+ subgraph pkg ["@markuplint/ml-core"]
87
+ subgraph mldom ["MLDOM"]
88
+ document["MLDocument"]
89
+ element["MLElement"]
90
+ node["MLNode / MLToken"]
91
+ ruleMapper["RuleMapper"]
92
+ end
93
+
94
+ subgraph mlRule ["MLRule"]
95
+ rule["MLRule"]
96
+ ruleContext["MLRuleContext"]
97
+ createRule["createRule()"]
98
+ end
99
+
100
+ subgraph engine ["エンジン"]
101
+ core["MLCore"]
102
+ ruleset["Ruleset"]
103
+ convertRuleset["convertRuleset()"]
104
+ end
105
+
106
+ subgraph extras ["その他"]
107
+ plugin["Plugin / createPlugin()"]
108
+ testUtils["テストユーティリティ"]
109
+ end
110
+ end
111
+
112
+ subgraph downstream ["下流"]
113
+ rules["@markuplint/rules\n(組み込みルール)"]
114
+ markuplint["markuplint\n(CLI & API)"]
115
+ end
116
+
117
+ upstream -->|"型、パース、仕様"| pkg
118
+ core --> document
119
+ core --> rule
120
+ document --> ruleMapper
121
+ rule --> ruleContext
122
+ pkg -->|"MLDOM, MLRule, MLCore"| downstream
123
+ ```
124
+
125
+ ## リンティングパイプライン
126
+
127
+ `MLCore.verify()` メソッドがリンティング全体を制御します:
128
+
129
+ ```mermaid
130
+ flowchart LR
131
+ A["MLCore\nコンストラクタ"]
132
+ B["_parse()\nParser → MLASTDocument"]
133
+ C["_createDocument()\nMLASTDocument → MLDocument"]
134
+ D["verify(fix?)\n各ルールに対して:"]
135
+ E["document.setRule(rule)\nRuleMapper で設定をノードにマッピング"]
136
+ F["rule.verify(document)\nMLRuleContext で報告を収集"]
137
+ G["Violations[]"]
138
+
139
+ A --> B --> C --> D --> E --> F --> G
140
+ ```
141
+
142
+ ### ステップごとの説明
143
+
144
+ 1. **パース**: `MLCore` は設定されたパーサー(`MLParser`)を呼び出し、`MLASTDocument` を生成
145
+ 2. **ドキュメント作成**: AST を `MLDocument` でラップし、`createNode()` ファクトリで MLDOM ツリー全体を構築。`RuleMapper` が各ノードのルール設定を解決
146
+ 3. **検証**: 各 `MLRule` に対して、`document.setRule(rule)` を呼び出した後 `rule.verify(document)` を実行。ルールは `document.walkOn()` で対象ノードを走査し、`MLRuleContext` を通じて違反を報告
147
+ 4. **修正**(オプション): `fix=true` の場合、ルールが `node.fix()` でトークン内容を変更。`document.toString(true)` で修正後のソースを生成
148
+
149
+ ## MLDOM クラス階層
150
+
151
+ ```
152
+ MLToken<A extends MLASTToken>
153
+ └── MLNode<T, O, A extends MLASTNode>
154
+ ├── MLAttr<T, O>
155
+ ├── MLCharacterData<T, O, A> (abstract)
156
+ │ ├── MLText<T, O>
157
+ │ └── MLComment<T, O>
158
+ ├── MLDocumentType<T, O>
159
+ ├── MLBlock<T, O>
160
+ ├── MLElementCloseTag<T, O>
161
+ └── MLParentNode<T, O, A> (abstract)
162
+ ├── MLElement<T, O>
163
+ ├── MLDocumentFragment<T, O>
164
+ └── MLDocument<T, O>
165
+ ```
166
+
167
+ ### クラスの責務
168
+
169
+ | クラス | DOM インターフェース | 主な責務 |
170
+ | -------------------- | -------------------- | ------------------------------------------------------------------------------------ |
171
+ | `MLToken` | — | 位置情報付き基底トークン(`startLine`, `endCol`, `raw`, `fixed`)、`fix()` メソッド |
172
+ | `MLNode` | `Node` | ツリー構造(`parentNode`, `childNodes`, `nextSibling`)、ルール格納、`is()` 型ガード |
173
+ | `MLAttr` | `Attr` | 属性名・値トークン、`isDynamicValue`, `isDirective`, `valueType`, `tokenList` |
174
+ | `MLCharacterData` | `CharacterData` | テキスト内容ノードの抽象基底(`data`, `nodeValue`) |
175
+ | `MLText` | `Text` | テキストノード、`isWhitespace()`, `isRawTextElementContent()` |
176
+ | `MLComment` | `Comment` | コメントノード(`textContent`) |
177
+ | `MLDocumentType` | `DocumentType` | `<!DOCTYPE>`(`name`, `publicId`, `systemId`) |
178
+ | `MLBlock` | — | プリプロセッサ固有ブロック(if/each/switch)、`blockBehavior`, `isTransparent` |
179
+ | `MLElementCloseTag` | — | 開始タグ要素とペアになる閉じタグ |
180
+ | `MLParentNode` | `ParentNode` | `querySelector()`, `querySelectorAll()`, `children`, `childElementCount` |
181
+ | `MLElement` | `Element` | 属性、セレクタ、名前空間、pretender コンテキスト、`elementType`, `closeTag` |
182
+ | `MLDocumentFragment` | `DocumentFragment` | フラグメントルートノード |
183
+ | `MLDocument` | `Document` | ルートノード、`nodeList`, `walkOn()`, `setRule()`, ルールマッピング、仕様アクセス |
184
+
185
+ ## MLDocument
186
+
187
+ `MLDocument` は MLDOM ツリーのルートであり、ルール実行の主要インターフェースです。
188
+
189
+ ### コンストラクション
190
+
191
+ コンストラクタは `MLASTDocument`、`Ruleset`、`MLSchema` タプルを受け取ります。処理内容:
192
+
193
+ 1. AST を走査し、各 AST ノードに対して `createNode()` を呼び出してフラットな `nodeList` を構築
194
+ 2. `RuleMapper` を初期化して各ノードにルール設定を配布
195
+ 3. pretender 定義が提供されている場合、pretender コンテキストをセットアップ
196
+
197
+ ### 主要プロパティ
198
+
199
+ | プロパティ | 型 | 説明 |
200
+ | ------------- | ----------------------- | ------------------------------------------------------- |
201
+ | `nodeList` | `ReadonlyArray<MLNode>` | ドキュメント順の全ノードのフラットリスト |
202
+ | `specs` | `MLMLSpec` | HTML/ARIA 仕様データ |
203
+ | `isFragment` | `boolean` | ドキュメントがフラグメントかどうか |
204
+ | `currentRule` | `MLRule \| null` | 現在評価中のルール |
205
+ | `endTag` | `EndTagType` | 終了タグ処理モード(`'xml'`, `'omittable'`, `'never'`) |
206
+
207
+ ### 主要メソッド
208
+
209
+ | メソッド | 説明 |
210
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
211
+ | `walkOn(type, walker)` | 指定した型(`'Element'`, `'Text'`, `'Comment'`, `'Attr'`, `'ElementCloseTag'`)のノードを走査 |
212
+ | `setRule(rule)` | 現在のルールを設定(検証時に `MLCore` が使用) |
213
+ | `getTokenList()` | ソース再構築用の全トークンを返す |
214
+ | `searchNodeByLocation(line, col)` | 指定したソース位置のノードを検索 |
215
+ | `getAccessibilityProp(node)` | ARIA アクセシビリティプロパティを計算(`MLElement.getAccessibleName()` のキャッシュ経由でアクセシブルネームを取得) |
216
+ | `toString(fixed?)` | ソースコードを再構築(オプションで修正適用) |
217
+
218
+ ## MLElement
219
+
220
+ `MLElement` は HTML/SVG/MathML 要素を表し、属性アクセスとセレクタマッチングを完全にサポートします。
221
+
222
+ ### 主要プロパティ
223
+
224
+ | プロパティ | 型 | 説明 |
225
+ | ------------------ | --------------------------- | ------------------------------------------------ |
226
+ | `localName` | `string` | 小文字のタグ名(HTML の場合) |
227
+ | `namespaceURI` | `NamespaceURI` | 要素の名前空間(HTML, SVG, MathML) |
228
+ | `attributes` | `MLNamedNodeMap` | 名前付き属性コレクション |
229
+ | `elementType` | `ElementType` | `'html'`, `'web-component'`, または `'authored'` |
230
+ | `closeTag` | `MLElementCloseTag \| null` | ペアの閉じタグ |
231
+ | `pretenderContext` | `PretenderContext \| null` | pretender マッピングコンテキスト |
232
+ | `isForeignElement` | `boolean` | SVG/MathML 要素の場合 `true` |
233
+ | `isOmitted` | `boolean` | 暗黙的に挿入された要素の場合 `true` |
234
+
235
+ ### 主要メソッド
236
+
237
+ | メソッド | 説明 |
238
+ | ---------------------------- | ------------------------------------------------------------------- |
239
+ | `getAttribute(name)` | 属性値または `null` を返す |
240
+ | `getAttributeToken(name)` | 名前付き属性の `MLAttr[]` を返す |
241
+ | `hasAttribute(name)` | 属性の存在を確認 |
242
+ | `getAccessibleName(version)` | キャッシュ付きアクセシブルネーム計算(ARIA バージョンごとにメモ化) |
243
+ | `matches(selector)` | CSS セレクタマッチング |
244
+ | `matchMLSelector(selector)` | 拡張 markuplint セレクタマッチング(`RegexSelector` サポート) |
245
+ | `querySelector(selector)` | 最初にマッチする子孫を検索 |
246
+ | `querySelectorAll(selector)` | マッチするすべての子孫を検索 |
247
+
248
+ ## ルールシステム
249
+
250
+ ### MLRule
251
+
252
+ `MLRule<T, O>` はリンティングルールを検証およびオプションの修正ロジックとともにカプセル化します。
253
+
254
+ | プロパティ/メソッド | 説明 |
255
+ | --------------------------------- | ------------------------------------------------------------------------------- |
256
+ | `name` | ルール識別子(例:`"attr-duplication"`) |
257
+ | `defaultSeverity` | デフォルトの重大度レベル |
258
+ | `defaultValue` / `defaultOptions` | デフォルト設定 |
259
+ | `baseRuleId` | 仮想ルールの場合: ベースルール名(例:`"required-attr"`) |
260
+ | `groupName` | 複数エントリ仮想ルールの場合: 一括無効化用のグループ名 |
261
+ | `specConformance` | 仮想ルールの場合: `'normative'` または `'non-normative'`(named nodeRule 由来) |
262
+ | `verify(document, locale, fix)` | ルールを実行して違反を返す |
263
+ | `createAlias(name, options?)` | このルールの verify/fix ロジックを再利用する仮想ルールを作成 |
264
+ | `optimizeOption(settings)` | 生のルール設定を `RuleInfo` に正規化 |
265
+
266
+ ### RuleSeed
267
+
268
+ `RuleSeed<T, O>` 型はルールの実装を定義します:
269
+
270
+ ```typescript
271
+ type RuleSeed<T, O> = {
272
+ meta?: {
273
+ category?: 'validation' | 'style' | 'naming-convention' | 'a11y' | 'maintainability';
274
+ };
275
+ defaultSeverity?: Severity;
276
+ defaultValue?: T;
277
+ defaultOptions?: O;
278
+ verify(context): void | Promise<void>;
279
+ fix?(context): void | Promise<void>;
280
+ };
281
+ ```
282
+
283
+ ### createRule
284
+
285
+ `createRule(seed)` は型安全なルールシード作成のためのファクトリ関数です。シードをそのまま返し、主に型ヘルパーとして機能します。
286
+
287
+ ### MLRuleContext
288
+
289
+ `MLRuleContext<T, O>` はルールの実行コンテキストを提供します:
290
+
291
+ - `document` — 現在の `MLDocument`
292
+ - `translate` / `t` — ロケール対応のメッセージ翻訳
293
+ - `report(report)` — ノード、メッセージ、オプションの修正とともに違反を報告
294
+
295
+ `provide()` メソッドは `RuleSeed.verify()` と `RuleSeed.fix()` に渡されるコンテキストオブジェクトを返します。
296
+
297
+ ### ルール設定の解決
298
+
299
+ ルールは `RuleMapper` によって 3 つのレベルで設定されます:
300
+
301
+ 1. **グローバルルール**(`rules`)— すべてのノードに適用。最低優先度
302
+ 2. **ノードルール**(`nodeRules`)— セレクタにマッチするノードに適用。中優先度
303
+ 3. **子ノードルール**(`childNodeRules`)— セレクタにマッチするノードの子に適用。最高優先度
304
+
305
+ 複数のルールがマッチする場合、`RuleMapper` は CSS セレクタの詳細度を使って競合を解決します。マッピングは `MLDocument` の構築時に一度計算され、各 `MLNode.rules` に格納されます。
306
+
307
+ ### ルール実行フロー
308
+
309
+ ```mermaid
310
+ flowchart TD
311
+ A["MLCore.verify()"] --> B["各 MLRule に対して"]
312
+ B --> C["document.setRule(rule)"]
313
+ C --> D["rule.verify(document, locale, fix)"]
314
+ D --> E["rule.getRuleInfo(ruleset)\nグローバル設定を解決"]
315
+ E --> F["document.walkOn(type, walker)\nマッチするノードを走査"]
316
+ F --> G["context.report()\nノードごとに違反を収集"]
317
+ G --> H["Violation[] を返す"]
318
+ ```
319
+
320
+ ### 仮想ルールシステム
321
+
322
+ ソース: `src/virtual-rule.ts`
323
+
324
+ > **用語ポリシー**: 「仮想ルール (virtual rule)」は**コントリビューター向けの内部実装用語**です。ユーザー向けドキュメント(ウェブサイト、移行ガイド、README)では**「named rule」**を使用すること。設定ユーザーの視点では、**ベースルール**(例: `required-attr`)と **named rule**(例: `a11y/html-lang`)の2つの概念だけで十分です。`MLRule` のエイリアス機構という内部メカニズムを公開してはなりません。
325
+
326
+ 仮想ルールは、**名前付き nodeRules** — `/` を含む `name` プロパティを持つ nodeRule エントリ(例: `"a11y/html-lang"`)— から作成される独立した `MLRule` インスタンスです。これによりチェック単位の制御が可能になります: 各仮想ルールは `rules["alias/name"]: false` で個別に有効/無効化できます。
327
+
328
+ #### Named NodeRule の展開
329
+
330
+ `expandNamedNodeRules()` は `MLCore` の構築時に named nodeRules(および childNodeRules)を仮想ルールに変換します:
331
+
332
+ ```
333
+ Named nodeRule(設定) 仮想 MLRule(ランタイム)
334
+ ┌─────────────────────────┐ ┌──────────────────────────┐
335
+ │ name: "a11y/html-lang" │ │ name: "a11y/html-lang" │
336
+ │ specConformance: "norm."│ ──────► │ baseRuleId: "required-attr" │
337
+ │ selector: ":where(html)"│ │ specConformance: メタデータ│
338
+ │ rules: │ │ verify/fix: ベースから │
339
+ │ required-attr: [lang] │ └──────────────────────────┘
340
+ └─────────────────────────┘
341
+ ```
342
+
343
+ 主要な動作:
344
+
345
+ - **false エントリの分離**: `rules` 内の `false` エントリは自動的に無名 nodeRule に分離され、ベースルールの specificity override としてのセマンティクスが維持される
346
+ - **複数エントリサポート**: 非 false エントリが 2 つ以上の named nodeRule は派生名(`name/baseRuleName`)と `groupName` で作成され、グループ一括無効化が可能
347
+ - **メタデータ**: `specConformance` は下流ツールやレポート向けのメタデータとして仮想ルールに付与される
348
+ - **ホットリロード**: 展開前の nodeRules は `#originalNodeRules` / `#originalChildNodeRules` に保持され、`update()` で再展開可能
349
+
350
+ #### なぜ `specConformance` は Named NodeRule 専用なのか
351
+
352
+ `specConformance` は意図的に **named nodeRule(プリセット内)でのみ**使用可能であり、通常の組み込みルールでは使用できません。設計根拠は以下の通りです:
353
+
354
+ 1. **組み込みルールは既に正しいデフォルト重大度を持っている。** `permitted-contents` や `required-attr` のようなルールは本質的に normative(WHATWG の MUST 要件を強制する)であり、`defaultSeverity` は既に `'error'` に設定されています。別途 `specConformance` フラグは不要です — 重大度は組み込み済みです。
355
+
356
+ 2. **Named nodeRules はプリセット作成者による仕様解釈である。** `preset.html-standard.jsonc` のようなプリセットが `"html-standard/head-charset-utf8"` という named nodeRule を作成する場合、プリセット作成者は特定の仕様要件をチェックとして表現しています。`specConformance` により、その要件の RFC 2119 キーワード強度を宣言でき、下流のツールやレポートが違反の仕様由来の分類を識別できます。
357
+
358
+ 3. **ユーザーは自分のルールに `specConformance` を設定すべきではない。** カスタムコンポーネント(例: `<MyComponent>` の props 検証)に対するユーザー定義の nodeRule は仕様準拠チェックではなく、プロジェクトの規約です。任意のユーザー設定で `specConformance` を許可すると、「HTML 仕様がこれを要求している」と「チームがこれを好む」の区別が曖昧になります。`name` プロパティ(`/` を含む必要あり)がゲートキーパーとして機能します:named nodeRule のみが `specConformance` を持てる設計であり、named nodeRule は仕様を理解するプリセット作成者向けに設計されています。
359
+
360
+ まとめ: `specConformance` は仕様由来のチェックを識別するメタデータを提供する**プリセットレベルのアノテーション**です。組み込みルールは `defaultSeverity` で独自に重大度を管理します。ユーザー定義ルールはルール設定の `severity` フィールドで直接重大度を表現します。
361
+
362
+ #### 仮想ルールの無効化
363
+
364
+ 仮想ルールは `rules` 設定で3つのレベルで無効化できます:
365
+
366
+ 1. **完全一致**: `rules["a11y/html-lang"]: false`
367
+ 2. **グループ無効化**: `rules["custom/multi"]: false`(複数エントリの named nodeRule 用)
368
+ 3. **名前空間ワイルドカード**: `rules["a11y/*"]: false`(`a11y/` で始まるすべての仮想ルールを無効化)
369
+
370
+ ## Pretender システム
371
+
372
+ pretender システムにより、コンポーネントをリンティング時にセマンティック HTML 要素として扱うことができます。これにより、ルールがカスタムコンポーネント(例:`<MyButton>`)を標準要素(例:`<button>`)として検証できます。
373
+
374
+ ### 設定
375
+
376
+ pretender は markuplint 設定で `Pretender` オブジェクトの配列として定義されます:
377
+
378
+ ```typescript
379
+ type Pretender = {
380
+ selector: string; // コンポーネントにマッチする CSS セレクタ
381
+ as: string; // 偽装する HTML 要素
382
+ aria?: PretenderARIA; // オプションの ARIA オーバーライド
383
+ };
384
+ ```
385
+
386
+ ### 動作の仕組み
387
+
388
+ 1. `MLDocument` の構築時に pretender 定義が処理される
389
+ 2. pretender セレクタにマッチする各 `MLElement` は `type: 'pretender'` の `pretenderContext` を取得
390
+ 3. 対象の HTML 要素は `type: 'origin'` の `pretenderContext` を取得
391
+ 4. ルールは `element.pretenderContext` にアクセスしてセマンティックマッピングを確認可能
392
+ 5. アクセシビリティ計算はロール/名前の解決に pretender コンテキストを使用
393
+
394
+ ## 条件付き子ノード
395
+
396
+ テンプレートエンジン(Pug, EJS, Nunjucks など)はプリプロセッサ固有のブロックを生成し、`MLBlock` ノードで表現されます。これらのブロックは子ノードを条件付きでラップできます:
397
+
398
+ | `blockBehavior.type` | テンプレート構文 | 説明 |
399
+ | -------------------- | ----------------- | ------------------ |
400
+ | `'if'` | `{% if %}` | 条件ブロックの開始 |
401
+ | `'if:else'` | `{% else %}` | 代替分岐 |
402
+ | `'end'` | `{% endif %}` | 条件ブロックの終了 |
403
+ | `'each'` | `{% for %}` | ループの開始 |
404
+ | `'end'` | `{% endfor %}` | ループの終了 |
405
+ | `'switch:case'` | `{% switch %}` | switch の開始 |
406
+ | `'switch:default'` | `{% case %}` | switch ケース |
407
+ | `'end'` | `{% endswitch %}` | switch の終了 |
408
+
409
+ `MLNode.conditionalChildNodes()` は `NodeListOf` 配列の配列を返します(条件分岐ごとに 1 つ)。これにより、ルールは各分岐を独立して分析できます。
410
+
411
+ ## プラグインシステム
412
+
413
+ プラグインはカスタムルールと共有設定で markuplint を拡張します。
414
+
415
+ ### Plugin 型
416
+
417
+ ```typescript
418
+ type Plugin = {
419
+ readonly name: string;
420
+ readonly rules?: Record<string, RuleSeed<any, any>>;
421
+ readonly configs?: Record<string, Config>;
422
+ };
423
+ ```
424
+
425
+ ### PluginCreator
426
+
427
+ 設定を受け付けるプラグイン用:
428
+
429
+ ```typescript
430
+ type PluginCreator<S> = {
431
+ readonly name: string;
432
+ create(setting: S): Omit<Plugin, 'name'>;
433
+ };
434
+ ```
435
+
436
+ `createPlugin(creator)` は型安全なプラグインクリエーター定義のためのファクトリ関数です。
437
+
438
+ ## テストユーティリティ
439
+
440
+ `test/` モジュールはルールテスト用のヘルパーを提供します:
441
+
442
+ | 関数 | 説明 |
443
+ | ------------------------------------------- | ------------------------------------------------ |
444
+ | `createTestDocument(sourceCode, options?)` | ソースをテスト用 `MLDocument` にパース |
445
+ | `createTestElement(sourceCode, options?)` | ソースをパースして最初の `MLElement` を返す |
446
+ | `createTestNodeList(sourceCode, options?)` | パースされたソースのフラットノードリストを返す |
447
+ | `createTestTokenList(sourceCode, options?)` | パースされたソースのフラットトークンリストを返す |
448
+ | `dummySchemas()` | デフォルト HTML 仕様をスキーマタプルとして返す |
449
+
450
+ `CreateTestOptions` は `config`, `parser`, `specs`, `pretenders` のオーバーライドを受け付けます。
451
+
452
+ ## 外部依存パッケージ
453
+
454
+ | 依存パッケージ | 用途 |
455
+ | ---------------------------- | ------------------------------------------------------ |
456
+ | `@markuplint/ml-ast` | AST 型定義(`MLASTDocument`, `MLASTNode` など) |
457
+ | `@markuplint/ml-config` | 設定型(`Config`, `RuleConfigValue`, `Pretender`) |
458
+ | `@markuplint/ml-spec` | HTML/ARIA 仕様アクセス(`MLMLSpec`, ロール/属性仕様) |
459
+ | `@markuplint/html-spec` | デフォルト HTML 仕様データ |
460
+ | `@markuplint/html-parser` | デフォルト HTML パーサー(テストユーティリティで使用) |
461
+ | `@markuplint/parser-utils` | パーサーオプションと型 |
462
+ | `@markuplint/selector` | CSS および拡張セレクタマッチング |
463
+ | `@markuplint/i18n` | 国際化(`LocaleSet`, `Translator`) |
464
+ | `@markuplint/shared` | 共有ユーティリティ |
465
+ | `@markuplint/config-presets` | 組み込み設定プリセット |
466
+ | `debug` | デバッグログ |
467
+ | `is-plain-object` | プレーンオブジェクト型チェック |
468
+ | `type-fest` | TypeScript ユーティリティ型 |
469
+
470
+ ## 統合ポイント
471
+
472
+ ```mermaid
473
+ flowchart TD
474
+ subgraph upstream ["上流"]
475
+ mlAst["@markuplint/ml-ast"]
476
+ mlConfig["@markuplint/ml-config"]
477
+ mlSpec["@markuplint/ml-spec"]
478
+ htmlSpec["@markuplint/html-spec"]
479
+ htmlParser["@markuplint/html-parser"]
480
+ parserUtils["@markuplint/parser-utils"]
481
+ selector["@markuplint/selector"]
482
+ i18n["@markuplint/i18n"]
483
+ shared["@markuplint/shared"]
484
+ configPresets["@markuplint/config-presets"]
485
+ end
486
+
487
+ subgraph pkg ["@markuplint/ml-core"]
488
+ core["MLCore エンジン"]
489
+ end
490
+
491
+ subgraph downstream ["下流"]
492
+ rules["@markuplint/rules\n(組み込みルール実装)"]
493
+ markuplint["markuplint\n(CLI, API, MLEngine)"]
494
+ end
495
+
496
+ upstream -->|"型、パース、仕様、i18n"| core
497
+ core -->|"MLDOM クラス, MLRule,\ncreateRule, テストユーティリティ"| rules
498
+ core -->|"MLCore, ViolationCollector,\nconvertRuleset, Plugin 型"| markuplint
499
+ ```
500
+
501
+ ### 上流
502
+
503
+ - **`@markuplint/ml-ast`** — MLDOM ツリー構築に使用される AST 型
504
+ - **`@markuplint/ml-config`** — 設定およびルール設定型
505
+ - **`@markuplint/ml-spec`** — 要素検証、ロール計算用の HTML/ARIA 仕様
506
+ - **`@markuplint/html-spec`** — デフォルト仕様データバンドル
507
+ - **`@markuplint/html-parser`** — テストユーティリティで使用されるデフォルトパーサー
508
+ - **`@markuplint/parser-utils`** — パーサーオプション型
509
+ - **`@markuplint/selector`** — `querySelector`, `matches`, `RegexSelector` 用の CSS セレクタエンジン
510
+ - **`@markuplint/i18n`** — ルールメッセージ用のロケールセットと翻訳
511
+ - **`@markuplint/shared`** — 共有ユーティリティ関数
512
+ - **`@markuplint/config-presets`** — 組み込み設定プリセット
513
+
514
+ ### 下流
515
+
516
+ - **`@markuplint/rules`** — 組み込みルール実装のために MLDOM クラス、`createRule`, `MLRuleContext`, テストユーティリティをインポート
517
+ - **`markuplint`** — CLI と API を提供するために `MLCore`, `ViolationCollector`, `convertRuleset`, プラグイン型をインポート
518
+
519
+ ## ドキュメントマップ
520
+
521
+ - [MLDOM リファレンス](docs/ml-dom.ja.md) ([English](docs/ml-dom.md)) — クラス階層、ノードプロパティ、ツリー走査
522
+ - [ルールシステム](docs/rule-system.ja.md) ([English](docs/rule-system.md)) — MLRule、RuleSeed、MLRuleContext、設定解決
523
+ - [リンティングパイプライン](docs/linting-pipeline.ja.md) ([English](docs/linting-pipeline.md)) — MLCore エンジン、verify フロー、pretender、プラグインシステム
524
+ - [メンテナンスガイド](docs/maintenance.ja.md) ([English](docs/maintenance.md)) — コマンド、レシピ、トラブルシューティング