@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,210 @@
1
+ # メンテナンスガイド
2
+
3
+ ## コマンド
4
+
5
+ ```bash
6
+ # ビルド
7
+ yarn build --scope @markuplint/ml-core
8
+
9
+ # ウォッチモード
10
+ yarn dev --scope @markuplint/ml-core
11
+
12
+ # ビルド出力のクリーン
13
+ yarn clean --scope @markuplint/ml-core
14
+
15
+ # テスト(リポジトリルートから)
16
+ yarn test --scope @markuplint/ml-core
17
+ ```
18
+
19
+ ## テスト
20
+
21
+ ### テストファイル
22
+
23
+ | ファイル | 用途 |
24
+ | ------------------- | ---------------------------------------------------------------------------------- |
25
+ | `src/test/index.ts` | テストユーティリティエクスポート(`createTestDocument`, `createTestElement` など) |
26
+
27
+ ### テストユーティリティの使用
28
+
29
+ パッケージは `@markuplint/rules` や他のコンシューマーで一般的に使用されるテストヘルパーを提供します:
30
+
31
+ ```typescript
32
+ import { createTestDocument, createTestElement, dummySchemas } from '@markuplint/ml-core';
33
+
34
+ // HTML をテスト用 MLDocument にパース
35
+ const doc = createTestDocument('<div class="foo"><p>Hello</p></div>');
36
+
37
+ // ノードリストにアクセス
38
+ for (const node of doc.nodeList) {
39
+ console.log(node.nodeName);
40
+ }
41
+
42
+ // 最初の要素を直接取得
43
+ const el = createTestElement('<button type="submit">Click</button>');
44
+ console.log(el.localName); // 'button'
45
+ console.log(el.getAttribute('type')); // 'submit'
46
+ ```
47
+
48
+ ### カスタム設定でのテスト
49
+
50
+ ```typescript
51
+ const doc = createTestDocument('<div></div>', {
52
+ config: {
53
+ rules: {
54
+ 'my-rule': true,
55
+ },
56
+ nodeRules: [{ selector: 'div', rules: { 'my-rule': 'custom-value' } }],
57
+ },
58
+ });
59
+ ```
60
+
61
+ ### カスタムパーサーでのテスト
62
+
63
+ ```typescript
64
+ import { parser as vueParser } from '@markuplint/vue-parser';
65
+
66
+ const doc = createTestDocument('<template><div></div></template>', {
67
+ parser: vueParser,
68
+ });
69
+ ```
70
+
71
+ ## レシピ
72
+
73
+ ### 1. MLDOM ノードクラスへのプロパティ追加
74
+
75
+ 1. `src/ml-dom/node/` 内の対象クラスを特定(例:`MLElement` の場合 `element.ts`)
76
+ 2. ゲッターまたは readonly フィールドとしてプロパティを追加
77
+ 3. プロパティが AST データから派生する場合、`this.#astNode`(プライベート AST 参照)を使用
78
+ 4. プロパティが仕様データを必要とする場合、`this.ownerMLDocument.specs` 経由でアクセス
79
+ 5. 新しい型を導入する場合、`src/ml-dom/node/types.ts` の型定義を更新
80
+ 6. ビルドを検証:`yarn build --scope @markuplint/ml-core`
81
+
82
+ **例:MLElement に `hasId` プロパティを追加**
83
+
84
+ ```typescript
85
+ // src/ml-dom/node/element.ts 内
86
+ get hasId(): boolean {
87
+ return this.hasAttribute('id');
88
+ }
89
+ ```
90
+
91
+ ### 2. DOM API 更新への対応(TypeScript DOM 型定義の変更)
92
+
93
+ TypeScript 組み込みの DOM 型定義が更新された場合(例:`Element` や `Node` に新しいプロパティが追加された場合)、それらのインターフェースを `implements` している MLDOM クラスで型エラーが発生します。これは意図的な設計です -- 気づかないうちにギャップが生じることを防ぎます。
94
+
95
+ **プロセス:**
96
+
97
+ 1. `yarn build --scope @markuplint/ml-core` を実行し、型エラーを収集する
98
+ 2. 不足しているプロパティやメソッドごとに判断する:
99
+ - **実装する**: API がリントルールに有用な場合(例:`querySelector`、`getAttribute`)→ 実際のロジックを実装する
100
+ - **非サポートにする**: API が静的解析で意味を持たない場合(例:`requestFullscreen`、`animate`)→ `UnexpectedCallError` をスローするスタブを追加する
101
+ 3. 非サポートのスタブは既存のパターンに従う:
102
+
103
+ ```typescript
104
+ /**
105
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
106
+ *
107
+ * @unsupported
108
+ * @implements DOM API: `Element`
109
+ */
110
+ someNewMethod(): void {
111
+ throw new UnexpectedCallError('Not supported "someNewMethod" method');
112
+ }
113
+ ```
114
+
115
+ 4. ビルドが通ることを確認する:`yarn build --scope @markuplint/ml-core`
116
+
117
+ ### 3. 新しいリンティングルールの作成
118
+
119
+ ルールは `@markuplint/rules` に配置されますが、このパッケージの `createRule` API を使用します:
120
+
121
+ ```typescript
122
+ import { createRule } from '@markuplint/ml-core';
123
+
124
+ export default createRule({
125
+ defaultSeverity: 'error',
126
+ defaultValue: true,
127
+ async verify({ document, report, t }) {
128
+ await document.walkOn('Element', el => {
129
+ if (/* 違反条件 */) {
130
+ report({
131
+ scope: el,
132
+ message: t('違反メッセージ'),
133
+ });
134
+ }
135
+ });
136
+ },
137
+ });
138
+ ```
139
+
140
+ ルールを単体テストする場合、`src/ml-rule/create-test-rule.ts` の `createTestRule` を使用:
141
+
142
+ ```typescript
143
+ import { createRule as createTestRule } from '@markuplint/ml-core/test';
144
+ const rule = createTestRule({ name: 'my-rule', ...seed });
145
+ ```
146
+
147
+ ### 4. Pretender 設定の変更
148
+
149
+ pretender は `MLDocument` コンストラクタ(`src/ml-dom/node/document.ts`)で処理されます:
150
+
151
+ 1. pretender 定義は `MLFabric.pretenders` から取得
152
+ 2. ドキュメント構築時に、各要素が pretender セレクタに対してチェックされる
153
+ 3. マッチする要素に `pretenderContext` が割り当てられる
154
+ 4. pretender の動作を変更するには、`MLDocument` の pretender 初期化ロジックを修正
155
+ 5. `createTestDocument` の `pretenders` オプションでテスト
156
+
157
+ ### 5. ルールマッピングロジックの変更(RuleMapper)
158
+
159
+ `RuleMapper` クラス(`src/ml-dom/node/rule-mapper.ts`)はルールのノードへの適用方法を制御します:
160
+
161
+ 1. `apply()` はグローバルルール、nodeRules、childNodeRules を順に処理
162
+ 2. マッチするセレクタごとに、詳細度付きの `MappingLayer` を作成
163
+ 3. `set()` はレイヤーを対象ノードに割り当て、詳細度で競合を解決
164
+ 4. 新しいマッピングソースを追加するには、`apply()` に新しい反復ブロックを追加し、適切な `from` 値を定義
165
+
166
+ ### 6. walkOn に新しいノードタイプを追加
167
+
168
+ `walkOn()` は `MLDocument`(`src/ml-dom/node/document.ts`)で定義されています:
169
+
170
+ 1. `type` パラメータのユニオンに新しいケースを追加:`'Element' | 'Text' | 'Comment' | 'Attr' | 'ElementCloseTag' | 'NewType'`
171
+ 2. `walkOn()` メソッドに `nodeList` からマッチするノードを選択するフィルタリングロジックを追加
172
+ 3. 必要に応じて `src/ml-dom/helper/walkers.ts` の `Walker` 型を更新
173
+ 4. `walkOn()` を使用する `@markuplint/rules` の下流コードを更新
174
+
175
+ ## 下流影響チェックリスト
176
+
177
+ `@markuplint/ml-core` の変更は以下に影響を与える可能性があります:
178
+
179
+ - [ ] **`@markuplint/rules`** — すべての組み込みルールが MLDOM クラスと `createRule` に依存
180
+ - ノードのプロパティ/メソッドの変更はそれらにアクセスするルールに影響
181
+ - `walkOn()` や `MLRuleContext` の変更はすべてのルールに影響
182
+ - `RuleMapper` の変更はルール設定の解決に影響
183
+ - [ ] **`markuplint`** — CLI と API が `MLCore`, `ViolationCollector`, `convertRuleset` に依存
184
+ - `MLCore.verify()` のシグネチャや動作の変更は `MLEngine` に影響
185
+ - `MLFabric` 型の変更はエンジンの初期化に影響
186
+
187
+ ## トラブルシューティング
188
+
189
+ ### ルールが実行されない
190
+
191
+ 1. ルールがルールセット設定(`rules` フィールド)に登録されているか確認
192
+ 2. ルール名が完全に一致しているか確認(大文字小文字を区別)
193
+ 3. `RuleMapper` の出力を確認:`document.debugMap()` でルールマッピングを確認
194
+ 4. ルールの `walkOn` タイプが期待するノードタイプと一致しているか確認
195
+ 5. nodeRules/childNodeRules のセレクタがグローバルルールを無効にオーバーライドしていないか確認
196
+
197
+ ### Pretender が効かない
198
+
199
+ 1. pretender セレクタが対象要素にマッチするか確認:`element.matches(selector)` でテスト
200
+ 2. pretender 定義が `MLFabric.pretenders` を通じて渡されているか確認
201
+ 3. pretender の `as` 値が有効な HTML 要素名であることを確認
202
+ 4. ドキュメント構築後に `element.pretenderContext` が `null` でないことを確認
203
+
204
+ ### DOM ツリー構造が不正
205
+
206
+ 1. `document.debugMap()` の出力を期待される構造と比較
207
+ 2. パーサー出力を確認:同じソースをパーサーで直接パースして AST を検査
208
+ 3. ゴースト要素(暗黙の HTML/head/body)が正しく処理されているか確認
209
+ 4. テンプレートエンジンの場合、`blockBehavior` 付きの `MLBlock` ノードが期待される子をラップしているか確認
210
+ 5. `document.nodeList` でフラットノードリストを検査し、親子関係を確認
@@ -0,0 +1,210 @@
1
+ # Maintenance Guide
2
+
3
+ ## Commands
4
+
5
+ ```bash
6
+ # Build
7
+ yarn build --scope @markuplint/ml-core
8
+
9
+ # Watch mode
10
+ yarn dev --scope @markuplint/ml-core
11
+
12
+ # Clean build output
13
+ yarn clean --scope @markuplint/ml-core
14
+
15
+ # Test (from repo root)
16
+ yarn test --scope @markuplint/ml-core
17
+ ```
18
+
19
+ ## Testing
20
+
21
+ ### Test Files
22
+
23
+ | File | Purpose |
24
+ | ------------------- | ---------------------------------------------------------------------- |
25
+ | `src/test/index.ts` | Test utility exports (`createTestDocument`, `createTestElement`, etc.) |
26
+
27
+ ### Using Test Utilities
28
+
29
+ The package provides test helpers commonly used by `@markuplint/rules` and other consumers:
30
+
31
+ ```typescript
32
+ import { createTestDocument, createTestElement, dummySchemas } from '@markuplint/ml-core';
33
+
34
+ // Parse HTML into an MLDocument for testing
35
+ const doc = createTestDocument('<div class="foo"><p>Hello</p></div>');
36
+
37
+ // Access the node list
38
+ for (const node of doc.nodeList) {
39
+ console.log(node.nodeName);
40
+ }
41
+
42
+ // Get the first element directly
43
+ const el = createTestElement('<button type="submit">Click</button>');
44
+ console.log(el.localName); // 'button'
45
+ console.log(el.getAttribute('type')); // 'submit'
46
+ ```
47
+
48
+ ### Test with Custom Config
49
+
50
+ ```typescript
51
+ const doc = createTestDocument('<div></div>', {
52
+ config: {
53
+ rules: {
54
+ 'my-rule': true,
55
+ },
56
+ nodeRules: [{ selector: 'div', rules: { 'my-rule': 'custom-value' } }],
57
+ },
58
+ });
59
+ ```
60
+
61
+ ### Test with Custom Parser
62
+
63
+ ```typescript
64
+ import { parser as vueParser } from '@markuplint/vue-parser';
65
+
66
+ const doc = createTestDocument('<template><div></div></template>', {
67
+ parser: vueParser,
68
+ });
69
+ ```
70
+
71
+ ## Recipes
72
+
73
+ ### 1. Add a Property to an MLDOM Node Class
74
+
75
+ 1. Identify the target class in `src/ml-dom/node/` (e.g., `element.ts` for `MLElement`)
76
+ 2. Add the property as a getter or readonly field
77
+ 3. If the property derives from AST data, use `this.#astNode` (private AST reference)
78
+ 4. If the property needs spec data, access it via `this.ownerMLDocument.specs`
79
+ 5. Update the type definitions in `src/ml-dom/node/types.ts` if introducing a new type
80
+ 6. Verify the build: `yarn build --scope @markuplint/ml-core`
81
+
82
+ **Example: Adding a `hasId` property to MLElement**
83
+
84
+ ```typescript
85
+ // In src/ml-dom/node/element.ts
86
+ get hasId(): boolean {
87
+ return this.hasAttribute('id');
88
+ }
89
+ ```
90
+
91
+ ### 2. Handle DOM API Updates (TypeScript DOM Type Changes)
92
+
93
+ When the TypeScript built-in DOM type definitions are updated (e.g., a new property is added to `Element` or `Node`), MLDOM classes that `implements` those interfaces will produce type errors. This is intentional -- it ensures no gaps go unnoticed.
94
+
95
+ **Process:**
96
+
97
+ 1. Run `yarn build --scope @markuplint/ml-core` and collect the type errors
98
+ 2. For each missing property or method, decide:
99
+ - **Implement**: If the API is useful for lint rules (e.g., `querySelector`, `getAttribute`) → implement the actual logic
100
+ - **Mark as unsupported**: If the API is not meaningful in static analysis (e.g., `requestFullscreen`, `animate`) → add a stub that throws `UnexpectedCallError`
101
+ 3. For unsupported stubs, follow the existing pattern:
102
+
103
+ ```typescript
104
+ /**
105
+ * **IT THROWS AN ERROR WHEN CALLING THIS.**
106
+ *
107
+ * @unsupported
108
+ * @implements DOM API: `Element`
109
+ */
110
+ someNewMethod(): void {
111
+ throw new UnexpectedCallError('Not supported "someNewMethod" method');
112
+ }
113
+ ```
114
+
115
+ 4. Verify the build passes: `yarn build --scope @markuplint/ml-core`
116
+
117
+ ### 3. Create a New Linting Rule
118
+
119
+ Rules live in `@markuplint/rules`, but use the `createRule` API from this package:
120
+
121
+ ```typescript
122
+ import { createRule } from '@markuplint/ml-core';
123
+
124
+ export default createRule({
125
+ defaultSeverity: 'error',
126
+ defaultValue: true,
127
+ async verify({ document, report, t }) {
128
+ await document.walkOn('Element', el => {
129
+ if (/* violation condition */) {
130
+ report({
131
+ scope: el,
132
+ message: t('Violation message'),
133
+ });
134
+ }
135
+ });
136
+ },
137
+ });
138
+ ```
139
+
140
+ For testing rules in isolation, use `createTestRule` from `src/ml-rule/create-test-rule.ts`:
141
+
142
+ ```typescript
143
+ import { createRule as createTestRule } from '@markuplint/ml-core/test';
144
+ const rule = createTestRule({ name: 'my-rule', ...seed });
145
+ ```
146
+
147
+ ### 4. Modify Pretender Configuration
148
+
149
+ Pretenders are processed in `MLDocument` constructor (`src/ml-dom/node/document.ts`):
150
+
151
+ 1. Pretender definitions come from `MLFabric.pretenders`
152
+ 2. During document construction, each element is checked against pretender selectors
153
+ 3. Matching elements get a `pretenderContext` assigned
154
+ 4. To change pretender behavior, modify the pretender initialization logic in `MLDocument`
155
+ 5. Test with `createTestDocument` using the `pretenders` option
156
+
157
+ ### 5. Change Rule Mapping Logic (RuleMapper)
158
+
159
+ The `RuleMapper` class (`src/ml-dom/node/rule-mapper.ts`) controls how rules are applied to nodes:
160
+
161
+ 1. `apply()` iterates through global rules, nodeRules, and childNodeRules
162
+ 2. For each matching selector, it creates a `MappingLayer` with specificity
163
+ 3. `set()` assigns the layer to the target node, resolving conflicts by specificity
164
+ 4. To add a new mapping source, add a new iteration block in `apply()` and define the appropriate `from` value
165
+
166
+ ### 6. Add a New Node Type to walkOn
167
+
168
+ `walkOn()` is defined in `MLDocument` (`src/ml-dom/node/document.ts`):
169
+
170
+ 1. Add a new case to the `type` parameter union: `'Element' | 'Text' | 'Comment' | 'Attr' | 'ElementCloseTag' | 'NewType'`
171
+ 2. Add the filtering logic in the `walkOn()` method to select matching nodes from `nodeList`
172
+ 3. Update the `Walker` type in `src/ml-dom/helper/walkers.ts` if needed
173
+ 4. Update downstream code in `@markuplint/rules` that uses `walkOn()`
174
+
175
+ ## Downstream Impact Checklist
176
+
177
+ Changes to `@markuplint/ml-core` can affect:
178
+
179
+ - [ ] **`@markuplint/rules`** — All built-in rules depend on MLDOM classes and `createRule`
180
+ - Changes to node properties/methods affect rules that access them
181
+ - Changes to `walkOn()` or `MLRuleContext` affect all rules
182
+ - Changes to `RuleMapper` affect rule configuration resolution
183
+ - [ ] **`markuplint`** — CLI and API depend on `MLCore`, `ViolationCollector`, `convertRuleset`
184
+ - Changes to `MLCore.verify()` signature or behavior affect `MLEngine`
185
+ - Changes to `MLFabric` type affect engine initialization
186
+
187
+ ## Troubleshooting
188
+
189
+ ### Rule is not being executed
190
+
191
+ 1. Check if the rule is registered in the ruleset config (`rules` field)
192
+ 2. Verify the rule name matches exactly (case-sensitive)
193
+ 3. Check `RuleMapper` output: use `document.debugMap()` to see the rule mapping
194
+ 4. Verify the rule's `walkOn` type matches the node type you expect
195
+ 5. Check if nodeRules/childNodeRules selectors are overriding the global rule to disable it
196
+
197
+ ### Pretender is not taking effect
198
+
199
+ 1. Verify the pretender selector matches the target element: use `element.matches(selector)` to test
200
+ 2. Check that pretender definitions are passed through `MLFabric.pretenders`
201
+ 3. Ensure the pretender `as` value is a valid HTML element name
202
+ 4. Check `element.pretenderContext` is not `null` after document construction
203
+
204
+ ### DOM tree structure is incorrect
205
+
206
+ 1. Compare `document.debugMap()` output with expected structure
207
+ 2. Check the parser output: parse the same source with the parser directly and inspect the AST
208
+ 3. Verify ghost elements (implicit HTML/head/body) are handled correctly
209
+ 4. For template engines, check that `MLBlock` nodes with `blockBehavior` wrap the expected children
210
+ 5. Use `document.nodeList` to inspect the flat node list and verify parent-child relationships
@@ -0,0 +1,103 @@
1
+ # MLAttr
2
+
3
+ **ソース:** `src/ml-dom/node/attr.ts`
4
+
5
+ 属性ノードです。`MLNode` を継承し、DOM `Attr` インターフェースを実装します。各属性は細粒度の検査と修正のために個々のトークンに分解されます。
6
+
7
+ ## プロパティ
8
+
9
+ | プロパティ | 型 | 説明 |
10
+ | -------------- | ---------------- | -------------------------------------------------- |
11
+ | `name` | `string` | 修飾された属性名(`#potentialName` から解決) |
12
+ | `localName` | `string` | 属性名のローカル部分(名前空間プレフィックスなし) |
13
+ | `namespaceURI` | `string \| null` | 属性の名前空間 URI |
14
+ | `value` | `string` | 属性値(`#potentialValue` から解決) |
15
+ | `nodeValue` | `string` | `value` と同じ |
16
+ | `textContent` | `string` | `value` と同じ |
17
+ | `specified` | `true` | 常に `true`(DOM `Attr` インターフェース要件) |
18
+ | `ownerElement` | `MLElement` | この属性を所有する要素 |
19
+
20
+ ## `#potentialName` / `#potentialValue`
21
+
22
+ パーサーは、ソース構文が有効なセマンティクスと異なる属性に対して、解決済みの「potential」名と値を提供する場合があります。例えば、テンプレートエンジンが属性名や値を変換することがあります。
23
+
24
+ - `astToken.potentialName` が存在する場合 → `name` として使用する。そうでなければ `nameNode.raw` を使用する
25
+ - `astToken.potentialValue` が存在する場合 → `value` として使用する。そうでなければ `valueNode.raw` を使用する
26
+
27
+ ### ディレクティブパターン解決
28
+
29
+ パーサーが `astToken.potentialName` を設定していない場合、`MLAttr` コンストラクタは spec(例: `@markuplint/vue-spec`、`@markuplint/svelte-spec`)からの `directivePatterns` をチェックします。パターンが存在する場合、生の属性名とコンパイル済みパターンで `resolveDirective()` が呼び出されます。最初にマッチしたパターンが `potentialName`、`isDynamicValue`、`isDirective`、`isDuplicatable` を決定します。
30
+
31
+ ### IDL 属性名解決
32
+
33
+ ディレクティブパターン解決の後、spec が `useIDLAttributeNames: true` を設定しており(例: `@markuplint/react-spec`、`@markuplint/svelte-spec`)、属性がディレクティブでない場合、コンストラクタは `@markuplint/parser-utils` の `searchIDLAttribute()` を呼び出して IDL プロパティ名を HTML コンテンツ属性名にマッピングします(例: `className` → `class`、`htmlFor` → `for`)。これはパーサーレベルではなくコアレベルの関心事です。
34
+
35
+ ## トークン分解
36
+
37
+ 各属性は個々の `MLToken` インスタンスに分解されます:
38
+
39
+ ```
40
+ ·class="container"
41
+ ^ ^^ ^
42
+ | || └── endQuote (raw: '"')
43
+ | |└── valueNode (raw: 'container')
44
+ | └── startQuote (raw: '"')
45
+ | equal (raw: '=')
46
+ └── spacesBeforeName (raw: ' ')
47
+ nameNode (raw: 'class')
48
+ ```
49
+
50
+ | トークンプロパティ | 型 | 説明 |
51
+ | ------------------- | ----------------- | --------------------------------------------- |
52
+ | `spacesBeforeName` | `MLToken \| null` | 属性名の前の空白 |
53
+ | `nameNode` | `MLToken \| null` | 属性名トークン(スプレッド属性の場合は null) |
54
+ | `spacesBeforeEqual` | `MLToken \| null` | 名前と `=` の間の空白 |
55
+ | `equal` | `MLToken \| null` | `=` 記号トークン |
56
+ | `spacesAfterEqual` | `MLToken \| null` | `=` と値の間の空白 |
57
+ | `startQuote` | `MLToken \| null` | 開始引用符トークン |
58
+ | `valueNode` | `MLToken \| null` | 属性値トークン |
59
+ | `endQuote` | `MLToken \| null` | 終了引用符トークン |
60
+
61
+ ## スプレッド属性
62
+
63
+ スプレッド属性(例:JSX の `{...props}`)の場合、`MLAttr` は最小限のプロパティで作成されます:
64
+
65
+ | プロパティ | 値 |
66
+ | -------------------------- | ---------------------------- |
67
+ | `localName` | `'#spread'` |
68
+ | `valueType` | `'code'` |
69
+ | `isDirective` | `true` |
70
+ | `isDynamicValue` | `true` |
71
+ | `isDuplicatable` | `true` |
72
+ | すべてのトークンプロパティ | `null` |
73
+ | `fix()` | No-op(即座に return) |
74
+ | `value` | スプレッド式のソーステキスト |
75
+
76
+ ## `tokenList`(MLDomTokenList)
77
+
78
+ スペース区切りの属性値(例:`class` 属性)に対する `MLDomTokenList` を返します:
79
+
80
+ - `isDynamicValue` の場合 → `null` を返す(動的式はトークン化できない)
81
+ - それ以外の場合 → `new MLDomTokenList(this.value, [this])`
82
+
83
+ ## `rule` ゲッター
84
+
85
+ `ownerElement.rule` に委譲します -- 属性はそれを所有する要素からルール設定を継承します。
86
+
87
+ ## メタデータプロパティ
88
+
89
+ | プロパティ | 型 | 説明 |
90
+ | ---------------- | --------------------------------------------- | -------------------------------------------- |
91
+ | `isDynamicValue` | `true \| undefined` | 値に動的式が含まれているかどうか |
92
+ | `isDirective` | `true \| undefined` | 属性がフレームワークのディレクティブかどうか |
93
+ | `isDuplicatable` | `boolean` | 属性が複数回出現可能かどうか |
94
+ | `valueType` | `'string' \| 'number' \| 'boolean' \| 'code'` | 値の型分類 |
95
+ | `candidate` | `string \| undefined` | パーサーが提案する修正候補値 |
96
+
97
+ ## メソッド
98
+
99
+ | メソッド | シグネチャ | 説明 |
100
+ | ------------------- | ----------------------------------- | ------------------------------------------------------------------------------------ |
101
+ | `fix` | `fix(raw: string): void` | `valueNode` の修正済みコンテンツを更新する。スプレッド属性の場合は No-op |
102
+ | `toNormalizeString` | `toNormalizeString(): string` | 余分な空白を除去した正規化された表現 |
103
+ | `toString` | `toString(fixed?: boolean): string` | raw または fixed の文字列。`fixed=true` の場合、各トークンの修正済み値から再構築する |
@@ -0,0 +1,103 @@
1
+ # MLAttr
2
+
3
+ **Source:** `src/ml-dom/node/attr.ts`
4
+
5
+ Attribute node. Extends `MLNode` and implements the DOM `Attr` interface. Each attribute is decomposed into individual tokens for fine-grained inspection and fixing.
6
+
7
+ ## Properties
8
+
9
+ | Property | Type | Description |
10
+ | -------------- | ---------------- | --------------------------------------------------------- |
11
+ | `name` | `string` | Qualified attribute name (resolved from `#potentialName`) |
12
+ | `localName` | `string` | Local part of attribute name (without namespace prefix) |
13
+ | `namespaceURI` | `string \| null` | Attribute namespace URI |
14
+ | `value` | `string` | Attribute value (resolved from `#potentialValue`) |
15
+ | `nodeValue` | `string` | Same as `value` |
16
+ | `textContent` | `string` | Same as `value` |
17
+ | `specified` | `true` | Always `true` (DOM `Attr` interface requirement) |
18
+ | `ownerElement` | `MLElement` | Element that owns this attribute |
19
+
20
+ ## `#potentialName` / `#potentialValue`
21
+
22
+ The parser may provide resolved "potential" names and values for attributes where the source syntax differs from the effective semantics. For example, a template engine might transform attribute names or values.
23
+
24
+ - If `astToken.potentialName` exists → use it as `name`; otherwise use `nameNode.raw`
25
+ - If `astToken.potentialValue` exists → use it as `value`; otherwise use `valueNode.raw`
26
+
27
+ ### Directive Pattern Resolution
28
+
29
+ When `astToken.potentialName` is not set by the parser, the `MLAttr` constructor checks for `directivePatterns` from the spec (e.g., `@markuplint/vue-spec`, `@markuplint/svelte-spec`). If patterns exist, `resolveDirective()` is called with the raw attribute name and the compiled patterns. The first matching pattern determines `potentialName`, `isDynamicValue`, `isDirective`, and `isDuplicatable`.
30
+
31
+ ### IDL Attribute Name Resolution
32
+
33
+ After directive pattern resolution, if the spec sets `useIDLAttributeNames: true` (e.g., `@markuplint/react-spec`, `@markuplint/svelte-spec`) and the attribute is not a directive, the constructor calls `searchIDLAttribute()` from `@markuplint/parser-utils` to map IDL property names to HTML content attribute names (e.g., `className` -> `class`, `htmlFor` -> `for`). This is a core-level concern, not a parser-level one.
34
+
35
+ ## Token Decomposition
36
+
37
+ Each attribute is decomposed into individual `MLToken` instances:
38
+
39
+ ```
40
+ ·class="container"
41
+ ^ ^^ ^
42
+ | || └── endQuote (raw: '"')
43
+ | |└── valueNode (raw: 'container')
44
+ | └── startQuote (raw: '"')
45
+ | equal (raw: '=')
46
+ └── spacesBeforeName (raw: ' ')
47
+ nameNode (raw: 'class')
48
+ ```
49
+
50
+ | Token Property | Type | Description |
51
+ | ------------------- | ----------------- | ------------------------------------------------- |
52
+ | `spacesBeforeName` | `MLToken \| null` | Whitespace before the attribute name |
53
+ | `nameNode` | `MLToken \| null` | Attribute name token (null for spread attributes) |
54
+ | `spacesBeforeEqual` | `MLToken \| null` | Whitespace between name and `=` |
55
+ | `equal` | `MLToken \| null` | The `=` sign token |
56
+ | `spacesAfterEqual` | `MLToken \| null` | Whitespace between `=` and value |
57
+ | `startQuote` | `MLToken \| null` | Opening quote token |
58
+ | `valueNode` | `MLToken \| null` | Attribute value token |
59
+ | `endQuote` | `MLToken \| null` | Closing quote token |
60
+
61
+ ## Spread Attributes
62
+
63
+ For spread attributes (e.g., `{...props}` in JSX), the `MLAttr` is created with minimal properties:
64
+
65
+ | Property | Value |
66
+ | -------------------- | -------------------------------------------- |
67
+ | `localName` | `'#spread'` |
68
+ | `valueType` | `'code'` |
69
+ | `isDirective` | `true` |
70
+ | `isDynamicValue` | `true` |
71
+ | `isDuplicatable` | `true` |
72
+ | All token properties | `null` |
73
+ | `fix()` | No-op (returns immediately) |
74
+ | `value` | The raw source text of the spread expression |
75
+
76
+ ## `tokenList` (MLDomTokenList)
77
+
78
+ Returns a `MLDomTokenList` for space-separated attribute values (e.g., `class` attribute):
79
+
80
+ - If `isDynamicValue` → returns `null` (cannot tokenize a dynamic expression)
81
+ - Otherwise → `new MLDomTokenList(this.value, [this])`
82
+
83
+ ## `rule` Getter
84
+
85
+ Delegates to `ownerElement.rule` -- an attribute inherits its rule configuration from the element that owns it.
86
+
87
+ ## Metadata Properties
88
+
89
+ | Property | Type | Description |
90
+ | ---------------- | --------------------------------------------- | ------------------------------------------- |
91
+ | `isDynamicValue` | `true \| undefined` | Whether value contains a dynamic expression |
92
+ | `isDirective` | `true \| undefined` | Whether attribute is a framework directive |
93
+ | `isDuplicatable` | `boolean` | Whether attribute may appear multiple times |
94
+ | `valueType` | `'string' \| 'number' \| 'boolean' \| 'code'` | Value type classification |
95
+ | `candidate` | `string \| undefined` | Fix candidate value suggested by the parser |
96
+
97
+ ## Methods
98
+
99
+ | Method | Signature | Description |
100
+ | ------------------- | ----------------------------------- | ---------------------------------------------------------------------------------- |
101
+ | `fix` | `fix(raw: string): void` | Updates the `valueNode` fixed content. No-op for spread attributes. |
102
+ | `toNormalizeString` | `toNormalizeString(): string` | Normalized representation stripping extraneous whitespace |
103
+ | `toString` | `toString(fixed?: boolean): string` | Raw or fixed string. For `fixed=true`, reconstructs from each token's fixed value. |