@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,141 @@
1
+ # MLDocument
2
+
3
+ **ソース:** `src/ml-dom/node/document.ts`
4
+
5
+ MLDOM ツリーのルートノードです。`MLParentNode` を継承し、DOM `Document` インターフェースを実装します。
6
+
7
+ ## コンストラクション
8
+
9
+ コンストラクタは以下を受け取ります:
10
+
11
+ 1. `ast: MLASTDocument` -- パース済みの AST ドキュメント
12
+ 2. `ruleset: Ruleset` -- ルール設定
13
+ 3. `schemas: MLSchema` -- HTML/ARIA 仕様データ(タプル)
14
+ 4. `options?` -- オプション設定:
15
+ - `filename` -- ソースファイルパス
16
+ - `endTag` -- `'xml'` | `'omittable'` | `'never'`(デフォルト: `'omittable'`)
17
+ - `booleanish` -- 省略された boolean 風属性を `true` として扱う(デフォルト: `false`)
18
+ - `tagNameCaseSensitive` -- タグ名の大文字小文字を区別する比較(デフォルト: `false`)
19
+ - `pretenders` -- pretender 定義の配列
20
+
21
+ コンストラクション手順:
22
+
23
+ 1. AST `nodeList` を反復して各非 endtag ノードに対して `createNode()` を呼び出し、フラットな `nodeList` を構築してから配列をフリーズする
24
+ 2. すべての要素ノードに対して `_pretending(pretenders)` で pretender コンテキストを初期化する
25
+ 3. `RuleMapper` を使って `_ruleMapping(ruleset)` でルール設定をノードに配布する
26
+
27
+ ## `_pretending(pretenders?)`
28
+
29
+ `nodeList` 内のすべての要素ノードを反復し、各要素に対して `element.pretending(pretenders)` を呼び出します。詳細は [MLElement > Pretender システム](./element.ja.md#pretender-システム)を参照してください。
30
+
31
+ ## `_ruleMapping(ruleset)`
32
+
33
+ `RuleMapper` を使って `Ruleset` からのルールを個々のノードに配布します。マッピングは3つのレイヤーで行われ、詳細度が増す順に適用されます:
34
+
35
+ 1. **グローバルルール**(`ruleset.rules`):`#document` と `nodeList` 内のすべてのノードに詳細度 `[0, 0, 0]` で適用
36
+ 2. **ノードルール**(`ruleset.nodeRules`):各 `nodeRule` に対して、`matchMLSelector()` でセレクタを要素にマッチさせる。マッチした場合、グローバルルールとマージしてセレクタの詳細度で適用
37
+ 3. **子ノードルール**(`ruleset.childNodeRules`):各 `childNodeRule` に対して、親セレクタをマッチさせ、直接の子(または `inheritance: true` の場合はすべての子孫)にマージしたルールを適用
38
+
39
+ `RuleMapper.set()` は CSS 詳細度の比較を使用します -- より高い詳細度のルールがより低い詳細度のルールを上書きします。`RuleMapper.apply()` は内部マップから解決されたルールを各ノードの `rules` レコードに転送します。
40
+
41
+ ルール解決の完全な詳細は [rule-system.md](../rule-system.md) を参照してください。
42
+
43
+ ## 主要プロパティ
44
+
45
+ | プロパティ | 型 | 説明 |
46
+ | ---------------------- | -------------------------- | ----------------------------------------------------------------- |
47
+ | `nodeList` | `ReadonlyArray<MLNode>` | ドキュメント順の全ノードのフリーズされたフラットリスト |
48
+ | `specs` | `MLMLSpec` | HTML/ARIA 仕様データ |
49
+ | `isFragment` | `boolean` | ドキュメントがフラグメントかどうか(ルート要素が不要) |
50
+ | `currentRule` | `Readonly<MLRule> \| null` | 現在評価中のルール、または `null` |
51
+ | `endTag` | `EndTagType` | 終了タグの処理モード: `'xml'` \| `'omittable'` \| `'never'` |
52
+ | `booleanish` | `boolean` | boolean 風属性を boolean として扱うかどうか(デフォルト `false`) |
53
+ | `tagNameCaseSensitive` | `boolean` | タグ名の大文字小文字区別(デフォルト `false`) |
54
+ | `filename` | `string \| undefined` | ソースファイル名 |
55
+ | `doctype` | `MLDocumentType \| null` | DOCTYPE ノード(存在する場合)。`nodeList` をスキャンして検出 |
56
+
57
+ ## `walkOn(type, walker, skipWhenRuleIsDisabled?)`
58
+
59
+ `nodeList` に対して `sequentialWalker` を使い、特定の型のノードを走査します。
60
+
61
+ **パラメータ:**
62
+
63
+ - `type` -- 走査するノード型(下表参照)
64
+ - `walker` -- マッチしたノードを受け取るコールバック関数
65
+ - `skipWhenRuleIsDisabled` -- 現在のルールが無効化されているノードをスキップする(デフォルト: `true`)
66
+
67
+ | 型 | 走査対象 | walker パラメータ |
68
+ | ------------------- | ------------------------------------------------------------------- | ------------------- |
69
+ | `'Element'` | `nodeList` 内の `ELEMENT_NODE` ノード | `MLElement` |
70
+ | `'Text'` | `nodeList` 内の `TEXT_NODE` ノード | `MLText` |
71
+ | `'Comment'` | `nodeList` 内の `COMMENT_NODE` ノード | `MLComment` |
72
+ | `'Attr'` | `nodeList` 内の各 `ELEMENT_NODE` のすべての属性 | `MLAttr` |
73
+ | `'ElementCloseTag'` | `nodeList` 内の各 `ELEMENT_NODE` の `closeTag`(`null` はスキップ) | `MLElementCloseTag` |
74
+
75
+ `skipWhenRuleIsDisabled` パラメータは現在のルールに対する `node.rule.disabled` をチェックします。`true` の場合、そのノードに対して walker は呼び出されません。これがルールがノードごとの無効化ディレクティブを尊重する仕組みです。
76
+
77
+ ```typescript
78
+ // すべての要素を走査(デフォルトで無効化されたノードはスキップ)
79
+ await document.walkOn('Element', async element => {
80
+ console.log(element.localName);
81
+ });
82
+
83
+ // 無効化されたノードを含むすべての属性を走査
84
+ await document.walkOn(
85
+ 'Attr',
86
+ async attr => {
87
+ console.log(`${attr.name}="${attr.value}"`);
88
+ },
89
+ false,
90
+ );
91
+ ```
92
+
93
+ ## `getAccessibilityProp(node, ariaVersion?)`
94
+
95
+ ノードの ARIA アクセシビリティプロパティを計算します。
96
+
97
+ **フロー:**
98
+
99
+ 1. `node` が要素でない場合 → `null` を返す
100
+ 2. 要素が `<slot>` の場合 → `{ unknown: true }` を返す(コンテンツは実行時に決定される)
101
+ 3. `isExposed()` を呼び出して要素がアクセシビリティツリーに公開されているか判定する
102
+ 4. 公開されていない場合 → `{ unknown: false, exposedToTree: false }` を返す
103
+ 5. `getComputedRole()` で ARIA ロールを計算する
104
+ 6. `getAccname()` でアクセシブル名を計算する(pretender コンテキストを含む -- [ヘルパー: accname](./helpers.ja.md#accname) を参照)
105
+ 7. ロール定義から `nameRequired` と `nameProhibited` を判定する
106
+ 8. `<slot>` の子要素をチェックする -- 存在する場合、名前が不明な可能性がある
107
+ 9. `mayBeFocusable()` でフォーカス可能性を計算する
108
+ 10. `getComputedAriaProps()` で計算された ARIA プロパティを収集する(必須 + 非デフォルトのプロパティ)
109
+
110
+ `AccessibilityProperties` を返します([型ユーティリティ](./helpers.ja.md#型ユーティリティ)を参照)。
111
+
112
+ ## `toString(fixed?)`
113
+
114
+ トークンリストからソースコードを再構築します。
115
+
116
+ - `fixed=false`(デフォルト):元の `raw` 文字列を返す
117
+ - `fixed=true`:オフセット追跡による置換を適用する:
118
+ 1. `getTokenList()` を取得する(`startOffset` でソート済み)
119
+ 2. 各トークンに対して:`toString(true) !== raw` の場合、修正済みコンテンツを文字列にスプライスする
120
+ 3. 正確な位置を維持するために累積オフセット差を追跡する
121
+
122
+ ## `defaultView`
123
+
124
+ `getComputedStyle()` を提供するモックウィンドウオブジェクトを返します。これは `getPropertyValue()` スタブを持つオブジェクトを返します。これは **Accessible Name and Description Computation** アルゴリズムのインターフェース要件を満たします。すべてのスタイルプロパティ値は空のオブジェクトを返します。
125
+
126
+ ## その他のメソッド
127
+
128
+ | メソッド | シグネチャ | 説明 |
129
+ | ---------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
130
+ | `setRule` | `setRule(rule: MLRule \| null): void` | 現在評価中のルールを設定/クリアする |
131
+ | `getTokenList` | `getTokenList(): ReadonlyArray<MLToken>` | ソース再構築用のすべてのトークン。オフセットでソート済み(キャッシュ)。ノードと閉じタグの両方を含む |
132
+ | `searchNodeByLocation` | `searchNodeByLocation(line, col): MLNode \| null` | 1始まりのソース位置でノードを検索する |
133
+ | `debugMap` | `debugMap(): string[]` | ドキュメントツリー構造のデバッグ出力 |
134
+
135
+ ## endTag モード
136
+
137
+ | 値 | 動作 |
138
+ | ------------- | --------------------------------------------------- |
139
+ | `'omittable'` | HTML モード:特定の終了タグが省略可能(デフォルト) |
140
+ | `'xml'` | XML モード:すべての要素に明示的な終了タグが必要 |
141
+ | `'never'` | 終了タグなし(例: Pug, Slim) |
@@ -0,0 +1,141 @@
1
+ # MLDocument
2
+
3
+ **Source:** `src/ml-dom/node/document.ts`
4
+
5
+ Root node of the MLDOM tree. Extends `MLParentNode` and implements the DOM `Document` interface.
6
+
7
+ ## Construction
8
+
9
+ The constructor receives:
10
+
11
+ 1. `ast: MLASTDocument` -- The parsed AST document
12
+ 2. `ruleset: Ruleset` -- Rule configuration
13
+ 3. `schemas: MLSchema` -- HTML/ARIA specification data (tuple)
14
+ 4. `options?` -- Optional configuration:
15
+ - `filename` -- Source file path
16
+ - `endTag` -- `'xml'` | `'omittable'` | `'never'` (default: `'omittable'`)
17
+ - `booleanish` -- Treat omitted boolean-like attributes as `true` (default: `false`)
18
+ - `tagNameCaseSensitive` -- Case-sensitive tag name comparison (default: `false`)
19
+ - `pretenders` -- Array of pretender definitions
20
+
21
+ Construction steps:
22
+
23
+ 1. Builds a flat `nodeList` by iterating the AST `nodeList`, calling `createNode()` for each non-endtag node, then freezes the array
24
+ 2. Initializes pretender contexts via `_pretending(pretenders)` for all element nodes
25
+ 3. Distributes rule configuration to nodes via `_ruleMapping(ruleset)` using `RuleMapper`
26
+
27
+ ## `_pretending(pretenders?)`
28
+
29
+ Iterates all element nodes in `nodeList` and calls `element.pretending(pretenders)` for each. See [MLElement > Pretender System](./element.md#pretender-system) for details.
30
+
31
+ ## `_ruleMapping(ruleset)`
32
+
33
+ Distributes rules from the `Ruleset` to individual nodes using `RuleMapper`. The mapping has three layers, applied with increasing specificity:
34
+
35
+ 1. **Global rules** (`ruleset.rules`): Applied to `#document` and every node in `nodeList` with specificity `[0, 0, 0]`
36
+ 2. **Node rules** (`ruleset.nodeRules`): For each `nodeRule`, match its selector against elements using `matchMLSelector()`. If matched, merge the rule with the global rule and apply with the selector's specificity
37
+ 3. **Child node rules** (`ruleset.childNodeRules`): For each `childNodeRule`, match the parent selector, then apply merged rules to direct children (or all descendants if `inheritance: true`)
38
+
39
+ `RuleMapper.set()` uses CSS specificity comparison -- a rule with higher specificity overrides one with lower specificity. `RuleMapper.apply()` transfers the resolved rules from the internal map to each node's `rules` record.
40
+
41
+ See [rule-system.md](../rule-system.md) for full details on rule resolution.
42
+
43
+ ## Key Properties
44
+
45
+ | Property | Type | Description |
46
+ | ---------------------- | -------------------------- | --------------------------------------------------------------------- |
47
+ | `nodeList` | `ReadonlyArray<MLNode>` | Frozen flat list of all nodes in document order |
48
+ | `specs` | `MLMLSpec` | HTML/ARIA specification data |
49
+ | `isFragment` | `boolean` | Whether document is a fragment (no root element required) |
50
+ | `currentRule` | `Readonly<MLRule> \| null` | Rule currently being evaluated, or `null` |
51
+ | `endTag` | `EndTagType` | End tag handling mode: `'xml'` \| `'omittable'` \| `'never'` |
52
+ | `booleanish` | `boolean` | Whether to treat boolean-like attributes as boolean (default `false`) |
53
+ | `tagNameCaseSensitive` | `boolean` | Case sensitivity for tag names (default `false`) |
54
+ | `filename` | `string \| undefined` | Source filename |
55
+ | `doctype` | `MLDocumentType \| null` | DOCTYPE node if present, found by scanning `nodeList` |
56
+
57
+ ## `walkOn(type, walker, skipWhenRuleIsDisabled?)`
58
+
59
+ Walks nodes of a specific type using `sequentialWalker` over `nodeList`.
60
+
61
+ **Parameters:**
62
+
63
+ - `type` -- Node type to walk (see table below)
64
+ - `walker` -- Callback function receiving the matched node
65
+ - `skipWhenRuleIsDisabled` -- Skip nodes where the current rule is disabled (default: `true`)
66
+
67
+ | Type | Walked Over | Walker Parameter |
68
+ | ------------------- | -------------------------------------------------------------- | ------------------- |
69
+ | `'Element'` | `ELEMENT_NODE` nodes from `nodeList` | `MLElement` |
70
+ | `'Text'` | `TEXT_NODE` nodes from `nodeList` | `MLText` |
71
+ | `'Comment'` | `COMMENT_NODE` nodes from `nodeList` | `MLComment` |
72
+ | `'Attr'` | All attributes on each `ELEMENT_NODE` in `nodeList` | `MLAttr` |
73
+ | `'ElementCloseTag'` | `closeTag` of each `ELEMENT_NODE` in `nodeList` (skips `null`) | `MLElementCloseTag` |
74
+
75
+ The `skipWhenRuleIsDisabled` parameter checks `node.rule.disabled` for the current rule. If `true`, the walker is not called for that node. This is how rules respect per-node disable directives.
76
+
77
+ ```typescript
78
+ // Walk all elements (skipping disabled nodes by default)
79
+ await document.walkOn('Element', async element => {
80
+ console.log(element.localName);
81
+ });
82
+
83
+ // Walk all attributes including disabled nodes
84
+ await document.walkOn(
85
+ 'Attr',
86
+ async attr => {
87
+ console.log(`${attr.name}="${attr.value}"`);
88
+ },
89
+ false,
90
+ );
91
+ ```
92
+
93
+ ## `getAccessibilityProp(node, ariaVersion?)`
94
+
95
+ Computes ARIA accessibility properties for a node.
96
+
97
+ **Flow:**
98
+
99
+ 1. If `node` is not an element → return `null`
100
+ 2. If the element is a `<slot>` → return `{ unknown: true }` (content is determined at runtime)
101
+ 3. Call `isExposed()` to determine if the element is exposed to the accessibility tree
102
+ 4. If not exposed → return `{ unknown: false, exposedToTree: false }`
103
+ 5. Compute the ARIA role via `getComputedRole()`
104
+ 6. Compute the accessible name via `getAccname()` (includes pretender context -- see [Helpers: accname](./helpers.md#accname))
105
+ 7. Determine `nameRequired` and `nameProhibited` from the role definition
106
+ 8. Check for `<slot>` children -- if present, name may be unknown
107
+ 9. Compute focusability via `mayBeFocusable()`
108
+ 10. Collect computed ARIA properties via `getComputedAriaProps()` (required + non-default props)
109
+
110
+ Returns `AccessibilityProperties` (see [Type Utilities](./helpers.md#type-utilities)).
111
+
112
+ ## `toString(fixed?)`
113
+
114
+ Reconstructs the source code from the token list.
115
+
116
+ - `fixed=false` (default): Returns the original `raw` string
117
+ - `fixed=true`: Applies offset-tracked replacement:
118
+ 1. Get `getTokenList()` (sorted by `startOffset`)
119
+ 2. For each token: if `toString(true) !== raw`, splice the fixed content into the string
120
+ 3. Track cumulative offset differences to maintain correct positions
121
+
122
+ ## `defaultView`
123
+
124
+ Returns a mock window object providing `getComputedStyle()` that returns an object with a `getPropertyValue()` stub. This satisfies the **Accessible Name and Description Computation** algorithm interface requirement. All style property values return empty objects.
125
+
126
+ ## Other Methods
127
+
128
+ | Method | Signature | Description |
129
+ | ---------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
130
+ | `setRule` | `setRule(rule: MLRule \| null): void` | Set/clear the current rule being evaluated |
131
+ | `getTokenList` | `getTokenList(): ReadonlyArray<MLToken>` | All tokens for source reconstruction, sorted by offset (cached). Includes both nodes and close tags. |
132
+ | `searchNodeByLocation` | `searchNodeByLocation(line, col): MLNode \| null` | Find node at a 1-based source position |
133
+ | `debugMap` | `debugMap(): string[]` | Debug output of document tree structure |
134
+
135
+ ## endTag Modes
136
+
137
+ | Value | Behavior |
138
+ | ------------- | ---------------------------------------------------- |
139
+ | `'omittable'` | HTML mode: certain end tags may be omitted (default) |
140
+ | `'xml'` | XML mode: all elements must have explicit end tags |
141
+ | `'never'` | No end tags (e.g., Pug, Slim) |
@@ -0,0 +1,176 @@
1
+ # MLElement
2
+
3
+ **ソース:** `src/ml-dom/node/element.ts`
4
+
5
+ HTML/SVG/MathML の要素ノードです。`MLParentNode` を継承し、`Element`、`HTMLElement`、`HTMLOrSVGElement` を実装します。MLDOM 階層の中で最も多機能なクラスです。
6
+
7
+ ## 名前プロパティ
8
+
9
+ 要素には異なる目的に使用される複数の名前関連プロパティがあります:
10
+
11
+ | プロパティ | HTML `<DIV>` | SVG `<foreignObject>` | Pretender(`MyButton` → `button`) |
12
+ | --------------- | --------------------------- | --------------------- | ---------------------------------- |
13
+ | `localName` | `"div"` | `"foreignObject"` | `"button"` |
14
+ | `nodeName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
15
+ | `rawName` | `"DIV"` | `"foreignObject"` | `"MyButton"` |
16
+ | `fixedNodeName` | `"DIV"`(修正後は `"div"`) | `"foreignObject"` | `"MyButton"` |
17
+ | `tagName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
18
+
19
+ **ルール:**
20
+
21
+ - **`localName`**: HTML 要素 → 小文字化。外部要素または非 `'html'` の elementType → そのまま。pretender コンテキスト → pretender の `localName`。`tagNameCaseSensitive` が `true` の場合 → 小文字化しない。
22
+ - **`nodeName`**: HTML 要素 → 大文字化(DOM 慣例)。外部要素または非 `'html'` の elementType → AST からそのまま。pretender コンテキスト → pretender の `nodeName`。
23
+ - **`rawName`**: 常に元の AST `nodeName`。正規化なし、pretender の影響なし。
24
+ - **`fixedNodeName`**: 初期値は `rawName`。リント修正がタグ名を変更した場合に `fixNodeName(name)` で更新される。
25
+ - **`tagName`**: `nodeName` と同じ(pretender コンテキストに従う)。
26
+
27
+ ## 要素型の判定
28
+
29
+ | `elementType` | 条件 | 例 |
30
+ | ----------------- | -------------------------------------------------------------- | --------------------------- |
31
+ | `'html'` | 標準 HTML 要素(HTML 名前空間の既知のタグ名) | `<div>`、`<span>`、`<p>` |
32
+ | `'web-component'` | タグ名にハイフンを含む(Custom Element 慣例) | `<my-component>`、`<x-app>` |
33
+ | `'authored'` | ハイフンを含まない非標準タグ名(フレームワークコンポーネント) | `<MyComponent>`(JSX) |
34
+
35
+ `elementType` はパーサーが AST 作成時に決定し、`astNode.elementType` として格納されます。
36
+
37
+ ## 属性アクセス
38
+
39
+ ### `attributes`(MLNamedNodeMap)
40
+
41
+ `attributes` ゲッターは重複排除された `MLNamedNodeMap` を返します:
42
+
43
+ 1. 属性ソースを選択する:pretender コンテキスト(`type === 'pretender'`)の場合、pretender 要素の属性を使用する。それ以外の場合は元の属性を使用する
44
+ 2. 名前で重複排除する:属性を反復し、各名前の最初の出現のみを保持する([HTML パースエラー: duplicate-attribute](https://html.spec.whatwg.org/#parse-error-duplicate-attribute) 仕様に従う)
45
+ 3. `MLNamedNodeMap` でラップし、結果をキャッシュする
46
+
47
+ ### 属性メソッド
48
+
49
+ | メソッド | シグネチャ | 説明 |
50
+ | ----------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------- |
51
+ | `getAttribute` | `getAttribute(name: string): string \| null` | 大文字小文字を区別しない名前検索で、最初にマッチした属性の `value` を返す |
52
+ | `getAttributeToken` | `getAttributeToken(name: string): MLAttr[]` | 名前にマッチする**すべての** `MLAttr` トークンを返す(重複属性に対応) |
53
+ | `getAttributeTokens` | `getAttributeTokens(): ReadonlyArray<MLAttr>` | すべての属性トークン(pretender コンテキスト → pretender の属性) |
54
+ | `getAttributePretended` | `getAttributePretended(name: string): string \| null` | pretender コンテキストを**無視**して**元の**要素から属性値を取得する |
55
+ | `hasAttribute` | `hasAttribute(name: string): boolean` | 大文字小文字を区別しない存在チェック(`getAttribute` に委譲) |
56
+
57
+ ### `hasMutableAttributes()`
58
+
59
+ 属性が非決定的な場合に `true` を返します:
60
+
61
+ - `nameNode` を持たない属性がある(つまり `{...props}` のようなスプレッド属性)
62
+ - `isDynamicValue === true` の属性がある(例:テンプレート式)
63
+
64
+ ルールはこれを使って、属性セットが静的解析時に完全には分からないことを検出します。
65
+
66
+ ## セレクタマッチング
67
+
68
+ ### `matches(selector, scope?)`
69
+
70
+ `boolean` を返します。`matchMLSelector()` に委譲し、`matched` をチェックします。
71
+
72
+ ### `matchMLSelector(selector, scope?)`
73
+
74
+ CSS セレクタ文字列と `RegexSelector` オブジェクトの両方をサポートする拡張セレクタマッチングです。`SelectorMatches`(詳細度と正規表現キャプチャデータを含むマッチ結果)を返します。
75
+
76
+ **pretender 要素の2段階マッチング:**
77
+
78
+ 1. 要素が pretender コンテキスト(`type === 'pretender'`)を持つ場合:
79
+ - まず **pretender として**マッチングする(例:`<button>` として)
80
+ - pretender がマッチした場合 → 結果を返す
81
+ 2. pretender がマッチしなかった場合:
82
+ - 一時的に `pretenderContext` を `null` に設定する
83
+ - **元の要素として**マッチングする(例:`<MyButton>` として)
84
+ - `pretenderContext` を復元する
85
+ - 結果を返す
86
+
87
+ これにより、`button` をターゲットにしたルールが pretender にマッチし、`MyButton` をターゲットにしたルールが引き続き元の要素にマッチすることが保証されます。
88
+
89
+ ### `closest(selectors)`
90
+
91
+ `this` から `parentElement` チェーンを遡り、`matches(selectors)` が `true` になる最初の要素を返します。
92
+
93
+ ## Pretender システム
94
+
95
+ Pretender システムのアーキテクチャ、初期化フロー、プロパティ委譲、アクセシブル名の統合についての包括的なドキュメントは、専用の [Pretender システム](./pretender.ja.md)リファレンスを参照してください。
96
+
97
+ ## その他のメソッド
98
+
99
+ | メソッド | シグネチャ | 説明 |
100
+ | ------------------------ | ------------------------------------------------- | ---------------------------------------------------------------------------------- |
101
+ | `fixNodeName` | `fixNodeName(name: string): void` | リント自動修正用に `fixedNodeName` を更新する |
102
+ | `getAccessibleName` | `getAccessibleName(version: ARIAVersion): string` | `getAccname()` を通じてアクセシブル名を計算する |
103
+ | `toNormalizeString` | `toNormalizeString(): string` | 比較用の正規化された表現を返す(キャッシュ済み)。子要素と属性を再帰的に正規化する |
104
+ | `nextElementSibling` | `get nextElementSibling: MLElement \| null` | 次の兄弟要素 |
105
+ | `previousElementSibling` | `get previousElementSibling: MLElement \| null` | 前の兄弟要素 |
106
+
107
+ ## 可変な子要素の検出
108
+
109
+ ### `hasMutableChildren(attr?)`
110
+
111
+ 要素の子が非決定的な場合に `true` を返します。`getPureChildNodes()` を反復します:
112
+
113
+ - `blockBehavior` を**持たない** `MLBlock` の子が存在する(つまり `blockBehavior` が `null` -- `'if'` や `'each'` のような認識されたブロック動作型を持つブロックは `conditionalChildNodes()` で処理されるためスキップされる)
114
+ - `<slot>` の子要素が存在する(コンテンツは実行時に注入される)
115
+ - `attr` が `true` の場合:子要素のいずれかが `hasMutableAttributes() === true` を持つ
116
+ - 子要素に対して再帰的に `hasMutableChildren()` をチェックする
117
+
118
+ これは `permitted-contents` などのルールが、子要素が予測不可能な場合に検証をスキップするかどうかを判断するために使用されます。
119
+
120
+ ## `getChildElementsAndTextNodeWithoutWhitespaces()`
121
+
122
+ 省略された要素をフラット化した `MLElement | MLText`(非空白)の子のフラット配列を返します:
123
+
124
+ 1. `childNodes` を反復する
125
+ 2. 要素の場合:`isOmitted` なら再帰的にその子を取得し、代わりにそれらを含める(フラット化)
126
+ 3. テキストノードの場合:空白でない場合のみ含める
127
+ 4. 結果はキャッシュされる
128
+
129
+ これはコンテンツモデルの検証に使用されます -- 暗黙の `<tbody>` のような省略された要素は、コンテンツモデルの目的では透過的です。
130
+
131
+ ## 省略された(ゴースト)要素
132
+
133
+ `isOmitted === true` の要素はパーサーによって暗黙的に挿入されたものです(例:HTML パーサーが省略された `<tbody>` を挿入)。これらの要素は:
134
+
135
+ - 対応するソーストークンを持たない
136
+ - `prevToken` によってスキップされる(有効なオフセットチェーンを維持するため)
137
+ - `toString(fixed)` から `raw` を返す(ソースに修正すべきものがないため修正は適用されない)
138
+ - `getChildElementsAndTextNodeWithoutWhitespaces()` によってフラット化される
139
+
140
+ ## 閉じタグ
141
+
142
+ | プロパティ | 型 | 説明 |
143
+ | ---------- | --------------------------- | ------------------------------------------------------------------------------------ |
144
+ | `closeTag` | `MLElementCloseTag \| null` | ペアの閉じタグ。void 要素、自己閉じ要素、または `endTag === 'never'` の場合は `null` |
145
+
146
+ ## `toString(fixed?)`
147
+
148
+ 修正を適用して要素のソース文字列を再構築します。
149
+
150
+ - `fixed=false` または pretender/省略要素/`#` プレフィックスの nodeName → `raw` を返す
151
+ - `fixed=true`:
152
+ 1. 置換可能なノードのリストを構築する:`[tagOpenChar + fixedNodeName, ...overriddenCommentNodes, ...attributes]`
153
+ 2. 各ノードに対して、正しいオフセットで `node.toString(true)` をスプライスする
154
+ 3. 正確な位置決めのために累積オフセット差を追跡する
155
+
156
+ ```
157
+ Original: <DIV class="foo" >
158
+ Fixed: <div class="foo" >
159
+ ^^^^ (fixedNodeName が "DIV" から "div" に変更)
160
+ ```
161
+
162
+ ## その他のプロパティ
163
+
164
+ | プロパティ | 型 | 説明 |
165
+ | ------------------ | ---------------------------- | --------------------------------------------- |
166
+ | `namespaceURI` | `NamespaceURI` | 要素の名前空間(HTML, SVG, MathML) |
167
+ | `isForeignElement` | `boolean` | SVG/MathML 要素の場合 `true` |
168
+ | `elementType` | `ElementType` | `'html'` \| `'web-component'` \| `'authored'` |
169
+ | `isOmitted` | `boolean` | 暗黙的に挿入された要素の場合 `true` |
170
+ | `blockBehavior` | `MLASTBlockBehavior \| null` | AST からのブロック動作(存在する場合) |
171
+ | `classList` | `MLDomTokenList` | `class` 属性からの CSS クラスリスト |
172
+ | `className` | `string` | class 属性値 |
173
+ | `id` | `string` | ID 属性値(存在しない場合は空文字列) |
174
+ | `hasSpreadAttr` | `boolean` | 要素にスプレッド属性があるかどうか |
175
+ | `tagOpenChar` | `string` | 開始タグ区切り文字(例: `<` または `<%`) |
176
+ | `tagCloseChar` | `string` | 閉じタグ区切り文字(例: `>` または `%>`) |
@@ -0,0 +1,176 @@
1
+ # MLElement
2
+
3
+ **Source:** `src/ml-dom/node/element.ts`
4
+
5
+ HTML/SVG/MathML element node. Extends `MLParentNode` and implements `Element`, `HTMLElement`, and `HTMLOrSVGElement`. This is the most feature-rich class in the MLDOM hierarchy.
6
+
7
+ ## Name Properties
8
+
9
+ The element has multiple name-related properties that serve different purposes:
10
+
11
+ | Property | HTML `<DIV>` | SVG `<foreignObject>` | Pretender (`MyButton` → `button`) |
12
+ | --------------- | ------------------------------ | --------------------- | --------------------------------- |
13
+ | `localName` | `"div"` | `"foreignObject"` | `"button"` |
14
+ | `nodeName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
15
+ | `rawName` | `"DIV"` | `"foreignObject"` | `"MyButton"` |
16
+ | `fixedNodeName` | `"DIV"` (or `"div"` after fix) | `"foreignObject"` | `"MyButton"` |
17
+ | `tagName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
18
+
19
+ **Rules:**
20
+
21
+ - **`localName`**: HTML elements → lowercased. Foreign elements or non-`'html'` elementType → as-is. Pretender context → pretender's `localName`. If `tagNameCaseSensitive` is `true` → no lowercasing.
22
+ - **`nodeName`**: HTML elements → uppercased (DOM convention). Foreign elements or non-`'html'` elementType → as-is from AST. Pretender context → pretender's `nodeName`.
23
+ - **`rawName`**: Always the original AST `nodeName`, with no normalization and no pretender influence.
24
+ - **`fixedNodeName`**: Starts as `rawName`. Updated by `fixNodeName(name)` when a lint fix modifies the tag name.
25
+ - **`tagName`**: Same as `nodeName` (follows pretender context).
26
+
27
+ ## Element Type Resolution
28
+
29
+ | `elementType` | Condition | Examples |
30
+ | ----------------- | ---------------------------------------------------------- | --------------------------- |
31
+ | `'html'` | Standard HTML element (known tag in HTML namespace) | `<div>`, `<span>`, `<p>` |
32
+ | `'web-component'` | Tag name contains a hyphen (Custom Element convention) | `<my-component>`, `<x-app>` |
33
+ | `'authored'` | Non-standard tag name without hyphen (framework component) | `<MyComponent>` (JSX) |
34
+
35
+ The `elementType` is determined by the parser at AST creation time and stored as `astNode.elementType`.
36
+
37
+ ## Attribute Access
38
+
39
+ ### `attributes` (MLNamedNodeMap)
40
+
41
+ The `attributes` getter returns a deduplicated `MLNamedNodeMap`:
42
+
43
+ 1. Select the attribute source: if in pretender context (`type === 'pretender'`), use the pretender element's attributes; otherwise, use the original attributes
44
+ 2. Deduplicate by name: iterate the attributes, keeping only the first occurrence of each name (per the [HTML parse error: duplicate-attribute](https://html.spec.whatwg.org/#parse-error-duplicate-attribute) spec)
45
+ 3. Wrap in `MLNamedNodeMap` and cache the result
46
+
47
+ ### Attribute Methods
48
+
49
+ | Method | Signature | Description |
50
+ | ----------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------- |
51
+ | `getAttribute` | `getAttribute(name: string): string \| null` | Case-insensitive name lookup, returns the first matching attribute's `value` |
52
+ | `getAttributeToken` | `getAttributeToken(name: string): MLAttr[]` | Returns **all** `MLAttr` tokens matching the name (handles duplicate attributes) |
53
+ | `getAttributeTokens` | `getAttributeTokens(): ReadonlyArray<MLAttr>` | All attribute tokens (pretender context → pretender's attributes) |
54
+ | `getAttributePretended` | `getAttributePretended(name: string): string \| null` | Gets attribute value from the **original** element, **ignoring** the pretender context |
55
+ | `hasAttribute` | `hasAttribute(name: string): boolean` | Case-insensitive existence check (delegates to `getAttribute`) |
56
+
57
+ ### `hasMutableAttributes()`
58
+
59
+ Returns `true` if any attribute is non-deterministic:
60
+
61
+ - An attribute has no `nameNode` (i.e., it's a spread attribute like `{...props}`)
62
+ - An attribute has `isDynamicValue === true` (e.g., template expression)
63
+
64
+ Rules use this to detect that the attribute set is not fully known at static analysis time.
65
+
66
+ ## Selector Matching
67
+
68
+ ### `matches(selector, scope?)`
69
+
70
+ Returns `boolean`. Delegates to `matchMLSelector()` and checks `matched`.
71
+
72
+ ### `matchMLSelector(selector, scope?)`
73
+
74
+ Extended selector matching supporting both CSS selector strings and `RegexSelector` objects. Returns `SelectorMatches` (match result with specificity and regex capture data).
75
+
76
+ **Two-phase matching for pretender elements:**
77
+
78
+ 1. If the element has a pretender context (`type === 'pretender'`):
79
+ - First, match **as the pretender** (e.g., as `<button>`)
80
+ - If the pretender matches → return the result
81
+ 2. If the pretender did not match:
82
+ - Temporarily set `pretenderContext` to `null`
83
+ - Match **as the original element** (e.g., as `<MyButton>`)
84
+ - Restore the `pretenderContext`
85
+ - Return the result
86
+
87
+ This ensures that rules targeting `button` match the pretender, while rules targeting `MyButton` still match the original.
88
+
89
+ ### `closest(selectors)`
90
+
91
+ Walks up the `parentElement` chain from `this`, returning the first element where `matches(selectors)` is `true`.
92
+
93
+ ## Pretender System
94
+
95
+ For comprehensive documentation on the pretender system's architecture, initialization flow, property delegation, and accessible name integration, see the dedicated [Pretender System](./pretender.md) reference.
96
+
97
+ ## Other Methods
98
+
99
+ | Method | Signature | Description |
100
+ | ------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
101
+ | `fixNodeName` | `fixNodeName(name: string): void` | Updates `fixedNodeName` for lint auto-fix of the tag name |
102
+ | `getAccessibleName` | `getAccessibleName(version: ARIAVersion): string` | Computes the accessible name via `getAccname()` |
103
+ | `toNormalizeString` | `toNormalizeString(): string` | Returns a normalized representation for comparison (cached). Recursively normalizes children and attributes. |
104
+ | `nextElementSibling` | `get nextElementSibling: MLElement \| null` | Next sibling element |
105
+ | `previousElementSibling` | `get previousElementSibling: MLElement \| null` | Previous sibling element |
106
+
107
+ ## Mutable Children Detection
108
+
109
+ ### `hasMutableChildren(attr?)`
110
+
111
+ Returns `true` if the element's children are non-deterministic. Iterates `getPureChildNodes()`:
112
+
113
+ - An `MLBlock` child exists **without** a `blockBehavior` (i.e., `blockBehavior` is `null` -- blocks with recognized block behavior types like `'if'`, `'each'` are skipped because they are handled by `conditionalChildNodes()`)
114
+ - A `<slot>` child element exists (content is injected at runtime)
115
+ - If `attr` is `true`: any child element has `hasMutableAttributes() === true`
116
+ - Recursively checks `hasMutableChildren()` on child elements
117
+
118
+ This is used by rules like `permitted-contents` to decide whether to skip validation when the children are unpredictable.
119
+
120
+ ## `getChildElementsAndTextNodeWithoutWhitespaces()`
121
+
122
+ Returns a flat array of `MLElement | MLText` (non-whitespace) children, with omitted elements flattened:
123
+
124
+ 1. Iterate `childNodes`
125
+ 2. For elements: if `isOmitted`, recursively get their children and include them instead (flattening)
126
+ 3. For text nodes: include only if not whitespace
127
+ 4. Result is cached
128
+
129
+ This is used for content model validation -- omitted elements like an implicit `<tbody>` are transparent for content model purposes.
130
+
131
+ ## Omitted (Ghost) Elements
132
+
133
+ Elements with `isOmitted === true` were implicitly inserted by the parser (e.g., HTML parser inserting an omitted `<tbody>`). These elements:
134
+
135
+ - Have no corresponding source tokens
136
+ - Are skipped by `prevToken` (to maintain valid offset chains)
137
+ - Return `raw` from `toString(fixed)` (no fix is applied since there's nothing in the source to fix)
138
+ - Are flattened by `getChildElementsAndTextNodeWithoutWhitespaces()`
139
+
140
+ ## Close Tag
141
+
142
+ | Property | Type | Description |
143
+ | ---------- | --------------------------- | ------------------------------------------------------------------------------------------------ |
144
+ | `closeTag` | `MLElementCloseTag \| null` | Paired close tag. `null` for void elements, self-closing elements, or when `endTag === 'never'`. |
145
+
146
+ ## `toString(fixed?)`
147
+
148
+ Reconstructs the element's source string with fixes applied.
149
+
150
+ - `fixed=false` or pretender/omitted/`#`-prefixed nodeName → return `raw`
151
+ - `fixed=true`:
152
+ 1. Build a list of replaceable nodes: `[tagOpenChar + fixedNodeName, ...overriddenCommentNodes, ...attributes]`
153
+ 2. For each node, splice `node.toString(true)` at the correct offset
154
+ 3. Track cumulative offset differences for accurate positioning
155
+
156
+ ```
157
+ Original: <DIV class="foo" >
158
+ Fixed: <div class="foo" >
159
+ ^^^^ (fixedNodeName changed from "DIV" to "div")
160
+ ```
161
+
162
+ ## Other Properties
163
+
164
+ | Property | Type | Description |
165
+ | ------------------ | ---------------------------- | --------------------------------------------- |
166
+ | `namespaceURI` | `NamespaceURI` | Element namespace (HTML, SVG, MathML) |
167
+ | `isForeignElement` | `boolean` | `true` for SVG/MathML elements |
168
+ | `elementType` | `ElementType` | `'html'` \| `'web-component'` \| `'authored'` |
169
+ | `isOmitted` | `boolean` | `true` for implicitly inserted elements |
170
+ | `blockBehavior` | `MLASTBlockBehavior \| null` | Block behavior from the AST, if any |
171
+ | `classList` | `MLDomTokenList` | CSS class list from `class` attribute |
172
+ | `className` | `string` | Class attribute value |
173
+ | `id` | `string` | ID attribute value (empty string if absent) |
174
+ | `hasSpreadAttr` | `boolean` | Whether element has spread attributes |
175
+ | `tagOpenChar` | `string` | Opening tag delimiter (e.g., `<` or `<%`) |
176
+ | `tagCloseChar` | `string` | Closing tag delimiter (e.g., `>` or `%>`) |