@markuplint/selector 4.7.7 → 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.
@@ -0,0 +1,259 @@
1
+ # @markuplint/selector
2
+
3
+ ## 概要
4
+
5
+ `@markuplint/selector` は markuplint のための拡張 [W3C Selectors Level 4](https://www.w3.org/TR/selectors-4/) マッチャーです。2 つの独立したマッチングシステムを提供します:
6
+
7
+ 1. **CSS セレクタマッチング** -- `postcss-selector-parser` を使用して標準 CSS セレクタをパースし、要素に対して完全な詳細度追跡付きでマッチングします。
8
+ 2. **Regex セレクタマッチング** -- ノード名と属性に対する正規表現パターンでマッチングし、キャプチャグループデータを抽出します。
9
+
10
+ また、markuplint 固有の拡張擬似クラス(`:aria()`、`:role()`、`:model()`)を定義し、HTML/ARIA 仕様データをセレクタマッチングに統合します。
11
+
12
+ ## ディレクトリ構成
13
+
14
+ ```
15
+ src/
16
+ ├── index.ts — エクスポートエントリーポイント
17
+ ├── types.ts — 型定義(SelectorElement, SelectorNode, SelectorAttr, Specificity, SelectorResult 等)
18
+ ├── selector.ts — コア Selector/Ruleset/StructuredSelector/SelectorTarget クラス
19
+ ├── create-selector.ts — インスタンスキャッシュと拡張擬似クラス登録を持つ Selector ファクトリ
20
+ ├── match-selector.ts — CSS/Regex セレクタマッチング公開関数
21
+ ├── compare-specificity.ts — 詳細度比較ユーティリティ
22
+ ├── regex-selector-matches.ts — 正規表現パターンマッチングヘルパー
23
+ ├── is.ts — ノード型ガード(SelectorNode/SelectorElement)
24
+ ├── invalid-selector-error.ts — 無効セレクタ用カスタムエラークラス
25
+ ├── debug.ts — デバッグログ設定(debug パッケージ使用)
26
+ └── extended-selector/
27
+ ├── aria-pseudo-class.ts — :aria() 擬似クラス(アクセシブルネーム判定)
28
+ ├── aria-role-pseudo-class.ts — :role() 擬似クラス(計算された ARIA ロール判定)
29
+ └── content-model-pseudo-class.ts — :model() 擬似クラス(HTML コンテンツモデルカテゴリ判定)
30
+ ```
31
+
32
+ ## アーキテクチャ図
33
+
34
+ ```mermaid
35
+ flowchart TD
36
+ subgraph publicAPI ["公開 API"]
37
+ createSelector["createSelector()"]
38
+ matchSelector["matchSelector()"]
39
+ compareSpec["compareSpecificity()"]
40
+ end
41
+
42
+ subgraph cssMatching ["CSS セレクタマッチング"]
43
+ Selector["Selector"]
44
+ Ruleset["Ruleset"]
45
+ StructuredSelector["StructuredSelector"]
46
+ SelectorTarget["SelectorTarget\n(CSS)"]
47
+ psp["postcss-selector-parser"]
48
+ end
49
+
50
+ subgraph regexMatching ["Regex セレクタマッチング"]
51
+ RegexSelectorTarget["SelectorTarget\n(Regex)"]
52
+ regexSelectorMatches["regexSelectorMatches()"]
53
+ end
54
+
55
+ subgraph extendedPseudo ["拡張擬似クラス"]
56
+ aria[":aria()"]
57
+ role[":role()"]
58
+ model[":model()"]
59
+ end
60
+
61
+ subgraph deps ["外部依存"]
62
+ mlSpec["@markuplint/ml-spec"]
63
+ end
64
+
65
+ createSelector -->|"生成&キャッシュ"| Selector
66
+ Selector -->|"パース委譲"| Ruleset
67
+ Ruleset -->|"パーサ呼出"| psp
68
+ Ruleset -->|"保持"| StructuredSelector
69
+ StructuredSelector -->|"チェーン"| SelectorTarget
70
+
71
+ matchSelector -->|"CSS 文字列"| createSelector
72
+ matchSelector -->|"RegexSelector"| RegexSelectorTarget
73
+ RegexSelectorTarget -->|"使用"| regexSelectorMatches
74
+
75
+ SelectorTarget -->|"ディスパッチ"| extendedPseudo
76
+ mlSpec -->|"仕様データ"| extendedPseudo
77
+ ```
78
+
79
+ ## モジュール概要
80
+
81
+ | モジュール | 役割 | 主要エクスポート |
82
+ | ------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
83
+ | `index.ts` | エントリーポイント | 全公開 API の再エクスポート |
84
+ | `types.ts` | 型定義 | `Specificity`, `SelectorResult`, `RegexSelector`, `RegexSelectorCombinator`, `SelectorAttr`, `SelectorNode`, `SelectorElement` |
85
+ | `selector.ts` | CSS セレクタエンジン | `Selector` クラス、`ExtendedPseudoClass` 型、`Ruleset`, `StructuredSelector`, `SelectorTarget`(内部) |
86
+ | `create-selector.ts` | キャッシュ付きファクトリ | `createSelector()` |
87
+ | `match-selector.ts` | 統合マッチング | `matchSelector()`, `SelectorMatches` |
88
+ | `compare-specificity.ts` | 詳細度比較 | `compareSpecificity()` |
89
+ | `regex-selector-matches.ts` | Regex マッチングヘルパー | `regexSelectorMatches()` |
90
+ | `is.ts` | ノード型ガード | `isElement()`, `isNonDocumentTypeChildNode()`, `isPureHTMLElement()` |
91
+ | `invalid-selector-error.ts` | エラークラス | `InvalidSelectorError` |
92
+ | `debug.ts` | デバッグログ | `log`(debug インスタンス), `enableDebug()` |
93
+ | `aria-pseudo-class.ts` | `:aria()` ハンドラ | `ariaPseudoClass()` |
94
+ | `aria-role-pseudo-class.ts` | `:role()` ハンドラ | `ariaRolePseudoClass()` |
95
+ | `content-model-pseudo-class.ts` | `:model()` ハンドラ | `contentModelPseudoClass()` |
96
+
97
+ ## 公開 API
98
+
99
+ ### `createSelector(selector, specs?)`
100
+
101
+ キャッシュされた `Selector` インスタンスを作成します。`specs` を指定すると、拡張擬似クラス(`:model()`、`:aria()`、`:role()`)が利用可能になります。インスタンスはセレクタ文字列をキーとしてキャッシュされます。
102
+
103
+ ### `matchSelector(el, selector, scope?, specs?)`
104
+
105
+ CSS セレクタ文字列または `RegexSelector` オブジェクトの両方を受け付ける統合マッチング関数です。`el` および `scope` パラメータは任意の `SelectorNode` を受け取ります。`{ matched: true, selector, specificity, data? }` または `{ matched: false }` を返します。
106
+
107
+ ### `compareSpecificity(a, b)`
108
+
109
+ 2 つの `Specificity` タプル(`[id, class, type]`)を比較します。`-1`、`0`、`1` を返します。
110
+
111
+ ### `SelectorMatches`(型)
112
+
113
+ セレクタマッチ結果のユニオン型: `{ matched: true, selector, specificity, data? } | { matched: false }`。
114
+
115
+ ### `InvalidSelectorError`
116
+
117
+ CSS セレクタ文字列がパースできない場合にスローされるカスタムエラーです。
118
+
119
+ ## 純粋データインターフェース
120
+
121
+ セレクタエンジンは DOM `Element`/`Node` を直接使用せず、純粋なデータインターフェース上で動作します。これにより、マッチングロジックが約200プロパティの DOM API から分離され、移植性(例: Rust/WASM)とプレーンオブジェクトによるテストが可能になります。
122
+
123
+ | インターフェース | 継承元 | 用途 |
124
+ | ----------------- | -------------- | ------------------------------------------------------ |
125
+ | `SelectorAttr` | — | 最小属性: `name`, `localName`, `value`, `namespaceURI` |
126
+ | `SelectorNode` | — | 最小ノード: `nodeType`, `nodeName`, `parentNode` |
127
+ | `SelectorElement` | `SelectorNode` | エンジンが実際に読み取る約12のプロパティを持つ要素 |
128
+
129
+ DOM `Element`、JSDOM 要素、`MLElement` はすべて構造的型付けにより `SelectorElement` を満たします — アダプタコードは不要です。
130
+
131
+ 拡張擬似クラス(`:aria()`、`:role()`、`:model()`)は `SelectorElement` を受け取り、完全な DOM API が必要な `@markuplint/ml-spec` 境界で `Element` にキャストします。
132
+
133
+ `@markuplint/selector` は依存グラフ上 `@markuplint/ml-core` より下位に位置するため、`MLElement` を直接参照できません。`:aria()` 擬似クラスはダックタイピング(`'getAccessibleName' in el`)により、要素がキャッシュ付きアクセシブルネームメソッドを持つかを検出します。これにより、循環依存を導入することなく `MLElement` の要素単位メモ化キャッシュをセレクタから共有できます。
134
+
135
+ ## コア内部クラス
136
+
137
+ CSS マッチングシステムは 4 つのクラスの階層で構成されます:
138
+
139
+ ```
140
+ Selector
141
+ └── Ruleset(postcss-selector-parser でパース)
142
+ └── StructuredSelector[](カンマ区切りセレクタごとに 1 つ)
143
+ └── SelectorTarget[](コンビネータで連結されたチェーン)
144
+ ```
145
+
146
+ - **Selector** -- 公開クラス。マッチングと検索を `Ruleset` に委譲します。
147
+ - **Ruleset** -- `postcss-selector-parser` でセレクタ文字列をパースし、`StructuredSelector` インスタンスのグループを保持します。カンマ区切りの全選択肢に対する `SelectorResult` 配列を返します。
148
+ - **StructuredSelector** -- 単一のセレクタ(カンマなし)を表現します。コンビネータで連結された `SelectorTarget` ノードのチェーンを構築します。マッチング時は右から左へチェーンをたどります。
149
+ - **SelectorTarget** -- 単一の複合セレクタを要素に対してマッチングします。ID、タグ、クラス、属性、ユニバーサルセレクタ、擬似クラス(拡張擬似クラスを含む)を処理します。
150
+
151
+ ## 2 つのマッチングシステム
152
+
153
+ ### CSS セレクタマッチング
154
+
155
+ 標準 CSS セレクタは `postcss-selector-parser` により AST にパースされ、`SelectorNode`/`SelectorElement` インスタンスに対してマッチングされます:
156
+
157
+ 1. `createSelector()` がキャッシュされた `Selector` インスタンスを作成または取得
158
+ 2. `Selector.match()` が `Ruleset.match()` に委譲
159
+ 3. 各 `StructuredSelector` が AST から `SelectorTarget` チェーンを構築
160
+ 4. `SelectorTarget` が個別の複合セレクタ(ID、クラス、タグ、属性、擬似クラス)をマッチング
161
+ 5. コンビネータ(子孫、子、兄弟)が `SelectorTarget` ノード間の DOM をトラバース
162
+
163
+ ### Regex セレクタマッチング
164
+
165
+ Regex セレクタは `RegexSelector` 型を使用してパターンで要素をマッチングします:
166
+
167
+ 1. `matchSelector()` が `RegexSelector` オブジェクトを受け取る
168
+ 2. `combination` リンクから `SelectorTarget` チェーンが構築される
169
+ 3. 各ターゲットが `regexSelectorMatches()` を使用して `nodeName`、`attrName`、`attrValue` をマッチング
170
+ 4. マッチしたキャプチャグループが `data` レコード(`$0`、`$1`、名前付きグループ)に収集される
171
+ 5. コンビネータは標準 CSS コンビネータに加え、前方兄弟マッチング用の `:has(+)` と `:has(~)` を含む
172
+
173
+ 詳細なアルゴリズムは[セレクタマッチング](docs/matching.ja.md)を参照してください。
174
+
175
+ ## 拡張擬似クラスシステム
176
+
177
+ 拡張擬似クラスは `ExtendedPseudoClass` 型を通じて登録されます:
178
+
179
+ ```typescript
180
+ type ExtendedPseudoClass = Record<string, (content: string) => (el: SelectorElement) => SelectorResult>;
181
+ ```
182
+
183
+ 3 つの擬似クラスが組み込まれています:
184
+
185
+ | 擬似クラス | モジュール | 説明 |
186
+ | ---------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
187
+ | `:aria(has name)` / `:aria(has no name)` | `aria-pseudo-class.ts` | アクセシブルネームの有無で要素をマッチング(`MLElement.getAccessibleName()` のキャッシュを利用、フォールバックとして `getAccname()` を使用) |
188
+ | `:role(roleName)` / `:role(roleName\|version)` | `aria-role-pseudo-class.ts` | `getComputedRole()` を使用して計算された ARIA ロールで要素をマッチング |
189
+ | `:model(category)` | `content-model-pseudo-class.ts` | HTML コンテンツモデルカテゴリに属する要素をマッチング |
190
+
191
+ すべての拡張擬似クラスの詳細度は `[0, 1, 0]` です。
192
+
193
+ ## 詳細度
194
+
195
+ 詳細度はマッチング全体を通じて `[id, class, type]` タプルとして追跡されます。主要なルール:
196
+
197
+ - `:where()` は `[0, 0, 0]` の詳細度
198
+ - `:not()`、`:is()`、`:has()` は最も詳細度の高い引数の詳細度
199
+ - 拡張擬似クラスは `[0, 1, 0]`
200
+ - `compareSpecificity()` は左から右に比較(ID > class > type)
201
+
202
+ ## 外部依存パッケージ
203
+
204
+ | パッケージ | 用途 |
205
+ | ------------------------- | --------------------------------------------------------- |
206
+ | `postcss-selector-parser` | CSS セレクタ文字列の AST へのパース |
207
+ | `@markuplint/ml-spec` | 拡張擬似クラス用の HTML/ARIA 仕様データ |
208
+ | `debug` | `DEBUG=selector*` によるデバッグログ |
209
+ | `type-fest` | TypeScript ユーティリティ型(`ReadonlyDeep`, `Writable`) |
210
+
211
+ 開発依存:
212
+
213
+ | パッケージ | 用途 |
214
+ | ---------- | ----------------- |
215
+ | `jsdom` | テスト用 DOM 環境 |
216
+
217
+ ## 他パッケージとの連携
218
+
219
+ ```mermaid
220
+ flowchart LR
221
+ subgraph upstream ["上流パッケージ"]
222
+ mlSpec["@markuplint/ml-spec\n(仕様データ + 型)"]
223
+ psp["postcss-selector-parser\n(CSS パーサ)"]
224
+ end
225
+
226
+ subgraph pkg ["@markuplint/selector"]
227
+ createSel["createSelector()"]
228
+ matchSel["matchSelector()"]
229
+ end
230
+
231
+ subgraph downstream ["下流パッケージ"]
232
+ mlCore["@markuplint/ml-core"]
233
+ rules["@markuplint/rules"]
234
+ mlConfig["@markuplint/ml-config"]
235
+ end
236
+
237
+ mlSpec -->|"仕様データ\n(ARIA, コンテンツモデル)"| pkg
238
+ psp -->|"セレクタ AST"| pkg
239
+
240
+ matchSel -->|"matchSelector()"| mlCore
241
+ createSel -->|"createSelector().search()"| rules
242
+ pkg -->|"RegexSelector 型"| mlConfig
243
+ ```
244
+
245
+ ### 上流
246
+
247
+ - **`@markuplint/ml-spec`** は拡張擬似クラスで使用される ARIA 仕様データ(`getComputedRole`、`getAccname`、`contentModelCategoryToTagNames`)と `resolveNamespace()` ユーティリティを提供します。
248
+ - **`postcss-selector-parser`** は CSS セレクタ文字列を `Ruleset` が使用する AST にパースします。
249
+
250
+ ### 下流
251
+
252
+ - **`@markuplint/ml-core`** はリント実行中にルール設定を要素にマッチングするために `matchSelector()` を使用します。
253
+ - **`@markuplint/rules`** はルール実装での要素選択に `createSelector().search()` を使用します。
254
+ - **`@markuplint/ml-config`** は設定スキーマ定義に `RegexSelector` 型を使用します。
255
+
256
+ ## ドキュメントマップ
257
+
258
+ - [セレクタマッチング](docs/matching.ja.md) -- CSS と Regex セレクタマッチングアルゴリズムの詳細
259
+ - [メンテナンスガイド](docs/maintenance.ja.md) -- コマンド、テスト、レシピ、トラブルシューティング
@@ -0,0 +1,259 @@
1
+ # @markuplint/selector
2
+
3
+ ## Overview
4
+
5
+ `@markuplint/selector` is an extended [W3C Selectors Level 4](https://www.w3.org/TR/selectors-4/) matcher for markuplint. It provides two independent matching systems:
6
+
7
+ 1. **CSS Selector Matching** -- Parses standard CSS selectors via `postcss-selector-parser` and matches them against elements with full specificity tracking.
8
+ 2. **Regex Selector Matching** -- Matches elements using regular expression patterns on node names and attributes, with captured group data extraction.
9
+
10
+ The package also defines markuplint-specific extended pseudo-classes (`:aria()`, `:role()`, `:model()`) that integrate HTML/ARIA specification data into selector matching.
11
+
12
+ ## Directory Structure
13
+
14
+ ```
15
+ src/
16
+ ├── index.ts — Export entry point
17
+ ├── types.ts — Type definitions (SelectorElement, SelectorNode, SelectorAttr, Specificity, SelectorResult, etc.)
18
+ ├── selector.ts — Core Selector/Ruleset/StructuredSelector/SelectorTarget classes
19
+ ├── create-selector.ts — Selector factory with instance caching and extended pseudo-class registration
20
+ ├── match-selector.ts — CSS/Regex selector matching public function
21
+ ├── compare-specificity.ts — Specificity comparison utility
22
+ ├── regex-selector-matches.ts — Regex pattern matching helper
23
+ ├── is.ts — Node type guards (SelectorNode/SelectorElement)
24
+ ├── invalid-selector-error.ts — Custom error class for invalid selectors
25
+ ├── debug.ts — Debug log configuration (using debug package)
26
+ └── extended-selector/
27
+ ├── aria-pseudo-class.ts — :aria() pseudo-class (accessible name matching)
28
+ ├── aria-role-pseudo-class.ts — :role() pseudo-class (computed ARIA role matching)
29
+ └── content-model-pseudo-class.ts — :model() pseudo-class (HTML content model category matching)
30
+ ```
31
+
32
+ ## Architecture Diagram
33
+
34
+ ```mermaid
35
+ flowchart TD
36
+ subgraph publicAPI ["Public API"]
37
+ createSelector["createSelector()"]
38
+ matchSelector["matchSelector()"]
39
+ compareSpec["compareSpecificity()"]
40
+ end
41
+
42
+ subgraph cssMatching ["CSS Selector Matching"]
43
+ Selector["Selector"]
44
+ Ruleset["Ruleset"]
45
+ StructuredSelector["StructuredSelector"]
46
+ SelectorTarget["SelectorTarget\n(CSS)"]
47
+ psp["postcss-selector-parser"]
48
+ end
49
+
50
+ subgraph regexMatching ["Regex Selector Matching"]
51
+ RegexSelectorTarget["SelectorTarget\n(Regex)"]
52
+ regexSelectorMatches["regexSelectorMatches()"]
53
+ end
54
+
55
+ subgraph extendedPseudo ["Extended Pseudo-Classes"]
56
+ aria[":aria()"]
57
+ role[":role()"]
58
+ model[":model()"]
59
+ end
60
+
61
+ subgraph deps ["External Dependencies"]
62
+ mlSpec["@markuplint/ml-spec"]
63
+ end
64
+
65
+ createSelector -->|"creates & caches"| Selector
66
+ Selector -->|"parses via"| Ruleset
67
+ Ruleset -->|"delegates to"| psp
68
+ Ruleset -->|"contains"| StructuredSelector
69
+ StructuredSelector -->|"chain of"| SelectorTarget
70
+
71
+ matchSelector -->|"CSS string"| createSelector
72
+ matchSelector -->|"RegexSelector"| RegexSelectorTarget
73
+ RegexSelectorTarget -->|"uses"| regexSelectorMatches
74
+
75
+ SelectorTarget -->|"dispatches"| extendedPseudo
76
+ mlSpec -->|"spec data"| extendedPseudo
77
+ ```
78
+
79
+ ## Module Overview
80
+
81
+ | Module | Role | Key Exports |
82
+ | ------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
83
+ | `index.ts` | Entry point | Re-exports all public API |
84
+ | `types.ts` | Type definitions | `Specificity`, `SelectorResult`, `RegexSelector`, `RegexSelectorCombinator`, `SelectorAttr`, `SelectorNode`, `SelectorElement` |
85
+ | `selector.ts` | CSS selector engine | `Selector` class, `ExtendedPseudoClass` type, `Ruleset`, `StructuredSelector`, `SelectorTarget` (internal) |
86
+ | `create-selector.ts` | Factory with caching | `createSelector()` |
87
+ | `match-selector.ts` | Unified matching | `matchSelector()`, `SelectorMatches` |
88
+ | `compare-specificity.ts` | Specificity comparison | `compareSpecificity()` |
89
+ | `regex-selector-matches.ts` | Regex matching helper | `regexSelectorMatches()` |
90
+ | `is.ts` | Node type guards | `isElement()`, `isNonDocumentTypeChildNode()`, `isPureHTMLElement()` |
91
+ | `invalid-selector-error.ts` | Error class | `InvalidSelectorError` |
92
+ | `debug.ts` | Debug logging | `log` (debug instance), `enableDebug()` |
93
+ | `aria-pseudo-class.ts` | `:aria()` handler | `ariaPseudoClass()` |
94
+ | `aria-role-pseudo-class.ts` | `:role()` handler | `ariaRolePseudoClass()` |
95
+ | `content-model-pseudo-class.ts` | `:model()` handler | `contentModelPseudoClass()` |
96
+
97
+ ## Public API
98
+
99
+ ### `createSelector(selector, specs?)`
100
+
101
+ Creates a cached `Selector` instance. When `specs` is provided, the extended pseudo-classes (`:model()`, `:aria()`, `:role()`) become available. Instances are cached by selector string for reuse.
102
+
103
+ ### `matchSelector(el, selector, scope?, specs?)`
104
+
105
+ Unified matching function that accepts either a CSS selector string or a `RegexSelector` object. The `el` and `scope` parameters accept any `SelectorNode`. Returns `{ matched: true, selector, specificity, data? }` or `{ matched: false }`.
106
+
107
+ ### `compareSpecificity(a, b)`
108
+
109
+ Compares two `Specificity` tuples (`[id, class, type]`). Returns `-1`, `0`, or `1`.
110
+
111
+ ### `SelectorMatches` (type)
112
+
113
+ Union type for selector match results: `{ matched: true, selector, specificity, data? } | { matched: false }`.
114
+
115
+ ### `InvalidSelectorError`
116
+
117
+ Custom error thrown when a CSS selector string cannot be parsed.
118
+
119
+ ## Pure Data Interfaces
120
+
121
+ The selector engine operates on pure data interfaces rather than DOM `Element`/`Node` directly. This decouples the matching logic from the ~200-property DOM API, making it portable (e.g., to Rust/WASM) and testable with plain objects.
122
+
123
+ | Interface | Extends | Purpose |
124
+ | ----------------- | -------------- | --------------------------------------------------------------- |
125
+ | `SelectorAttr` | — | Minimal attribute: `name`, `localName`, `value`, `namespaceURI` |
126
+ | `SelectorNode` | — | Minimal node: `nodeType`, `nodeName`, `parentNode` |
127
+ | `SelectorElement` | `SelectorNode` | Element with the ~12 properties the engine actually reads |
128
+
129
+ DOM `Element`, JSDOM elements, and `MLElement` all satisfy `SelectorElement` via structural typing — no adapter code is needed.
130
+
131
+ Extended pseudo-classes (`:aria()`, `:role()`, `:model()`) receive a `SelectorElement` and cast to `Element` at the `@markuplint/ml-spec` boundary where full DOM APIs are required.
132
+
133
+ Because `@markuplint/selector` sits below `@markuplint/ml-core` in the dependency graph, it cannot reference `MLElement` directly. The `:aria()` pseudo-class uses duck-typing (`'getAccessibleName' in el`) to detect whether the element provides a cached accessible name method. This allows the selector to share `MLElement`'s per-element memoization cache without introducing a circular dependency.
134
+
135
+ ## Core Internal Classes
136
+
137
+ The CSS matching system uses a hierarchy of four classes:
138
+
139
+ ```
140
+ Selector
141
+ └── Ruleset (parsed from postcss-selector-parser)
142
+ └── StructuredSelector[] (one per comma-separated selector)
143
+ └── SelectorTarget[] (chain linked by combinators)
144
+ ```
145
+
146
+ - **Selector** -- Public-facing class. Delegates to `Ruleset` for matching and searching.
147
+ - **Ruleset** -- Parses a selector string via `postcss-selector-parser` and holds a group of `StructuredSelector` instances. Returns an array of `SelectorResult` for all comma-separated alternatives.
148
+ - **StructuredSelector** -- Represents a single selector (without commas). Builds a chain of `SelectorTarget` nodes linked by combinators. Traverses the chain from right to left during matching.
149
+ - **SelectorTarget** -- Matches a single compound selector against an element. Handles ID, tag, class, attribute, universal selectors, and pseudo-classes (including extended pseudo-classes).
150
+
151
+ ## Two Matching Systems
152
+
153
+ ### CSS Selector Matching
154
+
155
+ Standard CSS selectors are parsed by `postcss-selector-parser` into an AST, then matched against `SelectorNode`/`SelectorElement` instances. The matching process:
156
+
157
+ 1. `createSelector()` creates or retrieves a cached `Selector` instance
158
+ 2. `Selector.match()` delegates to `Ruleset.match()`
159
+ 3. Each `StructuredSelector` builds a `SelectorTarget` chain from the AST
160
+ 4. `SelectorTarget` matches individual compounds (ID, class, tag, attributes, pseudo-classes)
161
+ 5. Combinators (descendant, child, sibling) traverse the DOM between `SelectorTarget` nodes
162
+
163
+ ### Regex Selector Matching
164
+
165
+ Regex selectors use the `RegexSelector` type to match elements by patterns:
166
+
167
+ 1. `matchSelector()` receives a `RegexSelector` object
168
+ 2. A `SelectorTarget` chain is built from the `combination` links
169
+ 3. Each target matches `nodeName`, `attrName`, and/or `attrValue` using `regexSelectorMatches()`
170
+ 4. Matched capture groups are collected into a `data` record (`$0`, `$1`, named groups)
171
+ 5. Combinators include standard CSS combinators plus `:has(+)` and `:has(~)` for forward-sibling matching
172
+
173
+ See [Selector Matching](docs/matching.md) for detailed algorithm documentation.
174
+
175
+ ## Extended Pseudo-Class System
176
+
177
+ Extended pseudo-classes are registered through the `ExtendedPseudoClass` type:
178
+
179
+ ```typescript
180
+ type ExtendedPseudoClass = Record<string, (content: string) => (el: SelectorElement) => SelectorResult>;
181
+ ```
182
+
183
+ Three pseudo-classes are built in:
184
+
185
+ | Pseudo-Class | Module | Description |
186
+ | ---------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
187
+ | `:aria(has name)` / `:aria(has no name)` | `aria-pseudo-class.ts` | Matches elements by accessible name presence (uses `MLElement.getAccessibleName()` cache when available, falls back to `getAccname()`) |
188
+ | `:role(roleName)` / `:role(roleName\|version)` | `aria-role-pseudo-class.ts` | Matches elements by computed ARIA role using `getComputedRole()` |
189
+ | `:model(category)` | `content-model-pseudo-class.ts` | Matches elements belonging to an HTML content model category |
190
+
191
+ All extended pseudo-classes have a specificity of `[0, 1, 0]`.
192
+
193
+ ## Specificity
194
+
195
+ Specificity is tracked as a `[id, class, type]` tuple throughout matching. Key rules:
196
+
197
+ - `:where()` contributes `[0, 0, 0]` specificity
198
+ - `:not()`, `:is()`, `:has()` contribute the specificity of their most specific argument
199
+ - Extended pseudo-classes contribute `[0, 1, 0]`
200
+ - `compareSpecificity()` compares left-to-right (ID > class > type)
201
+
202
+ ## External Dependencies
203
+
204
+ | Dependency | Purpose |
205
+ | ------------------------- | -------------------------------------------------------- |
206
+ | `postcss-selector-parser` | CSS selector string parsing into AST |
207
+ | `@markuplint/ml-spec` | HTML/ARIA specification data for extended pseudo-classes |
208
+ | `debug` | Debug logging via `DEBUG=selector*` |
209
+ | `type-fest` | TypeScript utility types (`ReadonlyDeep`, `Writable`) |
210
+
211
+ Dev dependencies:
212
+
213
+ | Dependency | Purpose |
214
+ | ---------- | ------------------------- |
215
+ | `jsdom` | DOM environment for tests |
216
+
217
+ ## Integration Points
218
+
219
+ ```mermaid
220
+ flowchart LR
221
+ subgraph upstream ["Upstream"]
222
+ mlSpec["@markuplint/ml-spec\n(spec data + types)"]
223
+ psp["postcss-selector-parser\n(CSS parser)"]
224
+ end
225
+
226
+ subgraph pkg ["@markuplint/selector"]
227
+ createSel["createSelector()"]
228
+ matchSel["matchSelector()"]
229
+ end
230
+
231
+ subgraph downstream ["Downstream"]
232
+ mlCore["@markuplint/ml-core"]
233
+ rules["@markuplint/rules"]
234
+ mlConfig["@markuplint/ml-config"]
235
+ end
236
+
237
+ mlSpec -->|"spec data\n(ARIA, content models)"| pkg
238
+ psp -->|"selector AST"| pkg
239
+
240
+ matchSel -->|"matchSelector()"| mlCore
241
+ createSel -->|"createSelector().search()"| rules
242
+ pkg -->|"RegexSelector type"| mlConfig
243
+ ```
244
+
245
+ ### Upstream
246
+
247
+ - **`@markuplint/ml-spec`** provides ARIA specification data (`getComputedRole`, `getAccname`, `contentModelCategoryToTagNames`) used by extended pseudo-classes and the `resolveNamespace()` utility.
248
+ - **`postcss-selector-parser`** parses CSS selector strings into an AST consumed by `Ruleset`.
249
+
250
+ ### Downstream
251
+
252
+ - **`@markuplint/ml-core`** uses `matchSelector()` to match rule configurations against elements during linting.
253
+ - **`@markuplint/rules`** uses `createSelector().search()` for element selection in rule implementations.
254
+ - **`@markuplint/ml-config`** uses the `RegexSelector` type for configuration schema definitions.
255
+
256
+ ## Documentation Map
257
+
258
+ - [Selector Matching](docs/matching.md) -- CSS and regex selector matching algorithm details
259
+ - [Maintenance Guide](docs/maintenance.md) -- Commands, testing, recipes, and troubleshooting
package/CHANGELOG.md CHANGED
@@ -3,13 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [4.7.7](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.6...@markuplint/selector@4.7.7) (2025-11-05)
6
+ # [5.0.0-alpha.0](https://github.com/markuplint/markuplint/compare/v4.14.1...v5.0.0-alpha.0) (2026-02-20)
7
7
 
8
- **Note:** Version bump only for package @markuplint/selector
8
+ ### Bug Fixes
9
9
 
10
+ - resolve additional eslint-plugin-unicorn v63 errors ([e58a72c](https://github.com/markuplint/markuplint/commit/e58a72c17c97bbec522f9513b99777fac6904d64))
11
+ - use explicit `export type` for type-only re-exports ([7c77c05](https://github.com/markuplint/markuplint/commit/7c77c05619518c8d18a183132040f5b2cd0ab6ec))
10
12
 
13
+ ### Performance Improvements
11
14
 
15
+ - **selector:** use cached getAccessibleName in :aria() pseudo-class ([42ea466](https://github.com/markuplint/markuplint/commit/42ea4665308e2b90e90856b29be939a6e8022347)), closes [#2179](https://github.com/markuplint/markuplint/issues/2179)
12
16
 
17
+ ## [4.7.8](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.7...@markuplint/selector@4.7.8) (2026-02-10)
18
+
19
+ **Note:** Version bump only for package @markuplint/selector
20
+
21
+ ## [4.7.7](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.6...@markuplint/selector@4.7.7) (2025-11-05)
22
+
23
+ **Note:** Version bump only for package @markuplint/selector
13
24
 
14
25
  ## [4.7.6](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.5...@markuplint/selector@4.7.6) (2025-08-24)
15
26
 
package/README.md CHANGED
@@ -111,6 +111,12 @@ For example, `:role(interactive)` matches `<a>`(with `href` attr), `<button>`, a
111
111
  }
112
112
  ```
113
113
 
114
+ ## Documentation
115
+
116
+ - [Architecture](ARCHITECTURE.md) ([日本語](ARCHITECTURE.ja.md)) -- Package overview, module map, and class hierarchy
117
+ - [Selector Matching](docs/matching.md) ([日本語](docs/matching.ja.md)) -- CSS and regex selector matching algorithm details
118
+ - [Maintenance Guide](docs/maintenance.md) ([日本語](docs/maintenance.ja.md)) -- Commands, testing, recipes, and troubleshooting
119
+
114
120
  ## Install
115
121
 
116
122
  [`markuplint`](https://www.npmjs.com/package/markuplint) package includes this package.