@markuplint/selector 4.7.6 → 4.7.8

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,243 @@
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 セレクタをパースし、DOM ノードに対して完全な詳細度追跡付きでマッチングします。
8
+ 2. **Regex セレクタマッチング** -- ノード名と属性に対する正規表現パターンでマッチングし、キャプチャグループデータを抽出します。
9
+
10
+ また、markuplint 固有の拡張擬似クラス(`:aria()`、`:role()`、`:model()`)を定義し、HTML/ARIA 仕様データをセレクタマッチングに統合します。
11
+
12
+ ## ディレクトリ構成
13
+
14
+ ```
15
+ src/
16
+ ├── index.ts — エクスポートエントリーポイント
17
+ ├── types.ts — 型定義(Specificity, SelectorResult, RegexSelector 等)
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 — DOM ノード型ガード
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` |
85
+ | `selector.ts` | CSS セレクタエンジン | `Selector` クラス(エントリーポイントからは再エクスポートされない)、`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` | DOM 型ガード | `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` オブジェクトの両方を受け付ける統合マッチング関数です。`{ 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
+ CSS マッチングシステムは 4 つのクラスの階層で構成されます:
122
+
123
+ ```
124
+ Selector
125
+ └── Ruleset(postcss-selector-parser でパース)
126
+ └── StructuredSelector[](カンマ区切りセレクタごとに 1 つ)
127
+ └── SelectorTarget[](コンビネータで連結されたチェーン)
128
+ ```
129
+
130
+ - **Selector** -- 公開クラス。マッチングと検索を `Ruleset` に委譲します。
131
+ - **Ruleset** -- `postcss-selector-parser` でセレクタ文字列をパースし、`StructuredSelector` インスタンスのグループを保持します。カンマ区切りの全選択肢に対する `SelectorResult` 配列を返します。
132
+ - **StructuredSelector** -- 単一のセレクタ(カンマなし)を表現します。コンビネータで連結された `SelectorTarget` ノードのチェーンを構築します。マッチング時は右から左へチェーンをたどります。
133
+ - **SelectorTarget** -- 単一の複合セレクタを要素に対してマッチングします。ID、タグ、クラス、属性、ユニバーサルセレクタ、擬似クラス(拡張擬似クラスを含む)を処理します。
134
+
135
+ ## 2 つのマッチングシステム
136
+
137
+ ### CSS セレクタマッチング
138
+
139
+ 標準 CSS セレクタは `postcss-selector-parser` により AST にパースされ、DOM ノードに対してマッチングされます:
140
+
141
+ 1. `createSelector()` がキャッシュされた `Selector` インスタンスを作成または取得
142
+ 2. `Selector.match()` が `Ruleset.match()` に委譲
143
+ 3. 各 `StructuredSelector` が AST から `SelectorTarget` チェーンを構築
144
+ 4. `SelectorTarget` が個別の複合セレクタ(ID、クラス、タグ、属性、擬似クラス)をマッチング
145
+ 5. コンビネータ(子孫、子、兄弟)が `SelectorTarget` ノード間の DOM をトラバース
146
+
147
+ ### Regex セレクタマッチング
148
+
149
+ Regex セレクタは `RegexSelector` 型を使用してパターンで要素をマッチングします:
150
+
151
+ 1. `matchSelector()` が `RegexSelector` オブジェクトを受け取る
152
+ 2. `combination` リンクから `SelectorTarget` チェーンが構築される
153
+ 3. 各ターゲットが `regexSelectorMatches()` を使用して `nodeName`、`attrName`、`attrValue` をマッチング
154
+ 4. マッチしたキャプチャグループが `data` レコード(`$0`、`$1`、名前付きグループ)に収集される
155
+ 5. コンビネータは標準 CSS コンビネータに加え、前方兄弟マッチング用の `:has(+)` と `:has(~)` を含む
156
+
157
+ 詳細なアルゴリズムは[セレクタマッチング](docs/matching.ja.md)を参照してください。
158
+
159
+ ## 拡張擬似クラスシステム
160
+
161
+ 拡張擬似クラスは `ExtendedPseudoClass` 型を通じて登録されます:
162
+
163
+ ```typescript
164
+ type ExtendedPseudoClass = Record<string, (content: string) => (el: Element) => SelectorResult>;
165
+ ```
166
+
167
+ 3 つの擬似クラスが組み込まれています:
168
+
169
+ | 擬似クラス | モジュール | 説明 |
170
+ | ---------------------------------------------- | ------------------------------- | ---------------------------------------------------------------------- |
171
+ | `:aria(has name)` / `:aria(has no name)` | `aria-pseudo-class.ts` | `getAccname()` を使用してアクセシブルネームの有無で要素をマッチング |
172
+ | `:role(roleName)` / `:role(roleName\|version)` | `aria-role-pseudo-class.ts` | `getComputedRole()` を使用して計算された ARIA ロールで要素をマッチング |
173
+ | `:model(category)` | `content-model-pseudo-class.ts` | HTML コンテンツモデルカテゴリに属する要素をマッチング |
174
+
175
+ すべての拡張擬似クラスの詳細度は `[0, 1, 0]` です。
176
+
177
+ ## 詳細度
178
+
179
+ 詳細度はマッチング全体を通じて `[id, class, type]` タプルとして追跡されます。主要なルール:
180
+
181
+ - `:where()` は `[0, 0, 0]` の詳細度
182
+ - `:not()`、`:is()`、`:has()` は最も詳細度の高い引数の詳細度
183
+ - 拡張擬似クラスは `[0, 1, 0]`
184
+ - `compareSpecificity()` は左から右に比較(ID > class > type)
185
+
186
+ ## 外部依存パッケージ
187
+
188
+ | パッケージ | 用途 |
189
+ | ------------------------- | --------------------------------------------------------- |
190
+ | `postcss-selector-parser` | CSS セレクタ文字列の AST へのパース |
191
+ | `@markuplint/ml-spec` | 拡張擬似クラス用の HTML/ARIA 仕様データ |
192
+ | `debug` | `DEBUG=selector*` によるデバッグログ |
193
+ | `type-fest` | TypeScript ユーティリティ型(`ReadonlyDeep`, `Writable`) |
194
+
195
+ 開発依存:
196
+
197
+ | パッケージ | 用途 |
198
+ | ---------- | ----------------- |
199
+ | `jsdom` | テスト用 DOM 環境 |
200
+
201
+ ## 他パッケージとの連携
202
+
203
+ ```mermaid
204
+ flowchart LR
205
+ subgraph upstream ["上流パッケージ"]
206
+ mlSpec["@markuplint/ml-spec\n(仕様データ + 型)"]
207
+ psp["postcss-selector-parser\n(CSS パーサ)"]
208
+ end
209
+
210
+ subgraph pkg ["@markuplint/selector"]
211
+ createSel["createSelector()"]
212
+ matchSel["matchSelector()"]
213
+ end
214
+
215
+ subgraph downstream ["下流パッケージ"]
216
+ mlCore["@markuplint/ml-core"]
217
+ rules["@markuplint/rules"]
218
+ mlConfig["@markuplint/ml-config"]
219
+ end
220
+
221
+ mlSpec -->|"仕様データ\n(ARIA, コンテンツモデル)"| pkg
222
+ psp -->|"セレクタ AST"| pkg
223
+
224
+ matchSel -->|"matchSelector()"| mlCore
225
+ createSel -->|"createSelector().search()"| rules
226
+ pkg -->|"RegexSelector 型"| mlConfig
227
+ ```
228
+
229
+ ### 上流
230
+
231
+ - **`@markuplint/ml-spec`** は拡張擬似クラスで使用される ARIA 仕様データ(`getComputedRole`、`getAccname`、`contentModelCategoryToTagNames`)と `resolveNamespace()` ユーティリティを提供します。
232
+ - **`postcss-selector-parser`** は CSS セレクタ文字列を `Ruleset` が使用する AST にパースします。
233
+
234
+ ### 下流
235
+
236
+ - **`@markuplint/ml-core`** はリント実行中にルール設定を要素にマッチングするために `matchSelector()` を使用します。
237
+ - **`@markuplint/rules`** はルール実装での要素選択に `createSelector().search()` を使用します。
238
+ - **`@markuplint/ml-config`** は設定スキーマ定義に `RegexSelector` 型を使用します。
239
+
240
+ ## ドキュメントマップ
241
+
242
+ - [セレクタマッチング](docs/matching.ja.md) -- CSS と Regex セレクタマッチングアルゴリズムの詳細
243
+ - [メンテナンスガイド](docs/maintenance.ja.md) -- コマンド、テスト、レシピ、トラブルシューティング
@@ -0,0 +1,243 @@
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 DOM nodes 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 (Specificity, SelectorResult, RegexSelector, 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 — DOM node type guards
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` |
85
+ | `selector.ts` | CSS selector engine | `Selector` class (not re-exported from entry point), `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` | DOM 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. 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
+ ## Core Internal Classes
120
+
121
+ The CSS matching system uses a hierarchy of four classes:
122
+
123
+ ```
124
+ Selector
125
+ └── Ruleset (parsed from postcss-selector-parser)
126
+ └── StructuredSelector[] (one per comma-separated selector)
127
+ └── SelectorTarget[] (chain linked by combinators)
128
+ ```
129
+
130
+ - **Selector** -- Public-facing class. Delegates to `Ruleset` for matching and searching.
131
+ - **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.
132
+ - **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.
133
+ - **SelectorTarget** -- Matches a single compound selector against an element. Handles ID, tag, class, attribute, universal selectors, and pseudo-classes (including extended pseudo-classes).
134
+
135
+ ## Two Matching Systems
136
+
137
+ ### CSS Selector Matching
138
+
139
+ Standard CSS selectors are parsed by `postcss-selector-parser` into an AST, then matched against DOM nodes. The matching process:
140
+
141
+ 1. `createSelector()` creates or retrieves a cached `Selector` instance
142
+ 2. `Selector.match()` delegates to `Ruleset.match()`
143
+ 3. Each `StructuredSelector` builds a `SelectorTarget` chain from the AST
144
+ 4. `SelectorTarget` matches individual compounds (ID, class, tag, attributes, pseudo-classes)
145
+ 5. Combinators (descendant, child, sibling) traverse the DOM between `SelectorTarget` nodes
146
+
147
+ ### Regex Selector Matching
148
+
149
+ Regex selectors use the `RegexSelector` type to match elements by patterns:
150
+
151
+ 1. `matchSelector()` receives a `RegexSelector` object
152
+ 2. A `SelectorTarget` chain is built from the `combination` links
153
+ 3. Each target matches `nodeName`, `attrName`, and/or `attrValue` using `regexSelectorMatches()`
154
+ 4. Matched capture groups are collected into a `data` record (`$0`, `$1`, named groups)
155
+ 5. Combinators include standard CSS combinators plus `:has(+)` and `:has(~)` for forward-sibling matching
156
+
157
+ See [Selector Matching](docs/matching.md) for detailed algorithm documentation.
158
+
159
+ ## Extended Pseudo-Class System
160
+
161
+ Extended pseudo-classes are registered through the `ExtendedPseudoClass` type:
162
+
163
+ ```typescript
164
+ type ExtendedPseudoClass = Record<string, (content: string) => (el: Element) => SelectorResult>;
165
+ ```
166
+
167
+ Three pseudo-classes are built in:
168
+
169
+ | Pseudo-Class | Module | Description |
170
+ | ---------------------------------------------- | ------------------------------- | ----------------------------------------------------------------- |
171
+ | `:aria(has name)` / `:aria(has no name)` | `aria-pseudo-class.ts` | Matches elements by accessible name presence using `getAccname()` |
172
+ | `:role(roleName)` / `:role(roleName\|version)` | `aria-role-pseudo-class.ts` | Matches elements by computed ARIA role using `getComputedRole()` |
173
+ | `:model(category)` | `content-model-pseudo-class.ts` | Matches elements belonging to an HTML content model category |
174
+
175
+ All extended pseudo-classes have a specificity of `[0, 1, 0]`.
176
+
177
+ ## Specificity
178
+
179
+ Specificity is tracked as a `[id, class, type]` tuple throughout matching. Key rules:
180
+
181
+ - `:where()` contributes `[0, 0, 0]` specificity
182
+ - `:not()`, `:is()`, `:has()` contribute the specificity of their most specific argument
183
+ - Extended pseudo-classes contribute `[0, 1, 0]`
184
+ - `compareSpecificity()` compares left-to-right (ID > class > type)
185
+
186
+ ## External Dependencies
187
+
188
+ | Dependency | Purpose |
189
+ | ------------------------- | -------------------------------------------------------- |
190
+ | `postcss-selector-parser` | CSS selector string parsing into AST |
191
+ | `@markuplint/ml-spec` | HTML/ARIA specification data for extended pseudo-classes |
192
+ | `debug` | Debug logging via `DEBUG=selector*` |
193
+ | `type-fest` | TypeScript utility types (`ReadonlyDeep`, `Writable`) |
194
+
195
+ Dev dependencies:
196
+
197
+ | Dependency | Purpose |
198
+ | ---------- | ------------------------- |
199
+ | `jsdom` | DOM environment for tests |
200
+
201
+ ## Integration Points
202
+
203
+ ```mermaid
204
+ flowchart LR
205
+ subgraph upstream ["Upstream"]
206
+ mlSpec["@markuplint/ml-spec\n(spec data + types)"]
207
+ psp["postcss-selector-parser\n(CSS parser)"]
208
+ end
209
+
210
+ subgraph pkg ["@markuplint/selector"]
211
+ createSel["createSelector()"]
212
+ matchSel["matchSelector()"]
213
+ end
214
+
215
+ subgraph downstream ["Downstream"]
216
+ mlCore["@markuplint/ml-core"]
217
+ rules["@markuplint/rules"]
218
+ mlConfig["@markuplint/ml-config"]
219
+ end
220
+
221
+ mlSpec -->|"spec data\n(ARIA, content models)"| pkg
222
+ psp -->|"selector AST"| pkg
223
+
224
+ matchSel -->|"matchSelector()"| mlCore
225
+ createSel -->|"createSelector().search()"| rules
226
+ pkg -->|"RegexSelector type"| mlConfig
227
+ ```
228
+
229
+ ### Upstream
230
+
231
+ - **`@markuplint/ml-spec`** provides ARIA specification data (`getComputedRole`, `getAccname`, `contentModelCategoryToTagNames`) used by extended pseudo-classes and the `resolveNamespace()` utility.
232
+ - **`postcss-selector-parser`** parses CSS selector strings into an AST consumed by `Ruleset`.
233
+
234
+ ### Downstream
235
+
236
+ - **`@markuplint/ml-core`** uses `matchSelector()` to match rule configurations against elements during linting.
237
+ - **`@markuplint/rules`** uses `createSelector().search()` for element selection in rule implementations.
238
+ - **`@markuplint/ml-config`** uses the `RegexSelector` type for configuration schema definitions.
239
+
240
+ ## Documentation Map
241
+
242
+ - [Selector Matching](docs/matching.md) -- CSS and regex selector matching algorithm details
243
+ - [Maintenance Guide](docs/maintenance.md) -- Commands, testing, recipes, and troubleshooting
package/CHANGELOG.md CHANGED
@@ -3,13 +3,17 @@
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.6](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.5...@markuplint/selector@4.7.6) (2025-08-24)
6
+ ## [4.7.8](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.7...@markuplint/selector@4.7.8) (2026-02-10)
7
7
 
8
8
  **Note:** Version bump only for package @markuplint/selector
9
9
 
10
+ ## [4.7.7](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.6...@markuplint/selector@4.7.7) (2025-11-05)
10
11
 
12
+ **Note:** Version bump only for package @markuplint/selector
11
13
 
14
+ ## [4.7.6](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.5...@markuplint/selector@4.7.6) (2025-08-24)
12
15
 
16
+ **Note:** Version bump only for package @markuplint/selector
13
17
 
14
18
  ## [4.7.5](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.4...@markuplint/selector@4.7.5) (2025-08-13)
15
19
 
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.
package/SKILL.md ADDED
@@ -0,0 +1,126 @@
1
+ ---
2
+ description: Perform maintenance tasks for @markuplint/selector
3
+ ---
4
+
5
+ # selector-maintenance
6
+
7
+ Perform maintenance tasks for `@markuplint/selector`: add extended pseudo-classes,
8
+ support new CSS selectors, debug matching issues, and update dependencies.
9
+
10
+ ## Input
11
+
12
+ `$ARGUMENTS` specifies the task. Supported tasks:
13
+
14
+ | Task | Description |
15
+ | ---------------------------------- | -------------------------------------------- |
16
+ | `add-pseudo-class <name>` | Add a new extended pseudo-class |
17
+ | `add-selector-support <type>` | Add support for a new CSS selector type |
18
+ | `debug-matching <selector> <html>` | Debug selector matching against HTML |
19
+ | `update-deps` | Update dependencies and verify compatibility |
20
+
21
+ If omitted, defaults to `add-pseudo-class`.
22
+
23
+ ## Reference
24
+
25
+ Before executing any task, read `docs/maintenance.md` (or `docs/maintenance.ja.md`)
26
+ for the full guide. The recipes there are the source of truth for procedures.
27
+
28
+ Also read:
29
+
30
+ - `docs/matching.md` -- Selector matching algorithm details
31
+ - `ARCHITECTURE.md` -- Package overview, module map, and class hierarchy
32
+
33
+ ## Task: add-pseudo-class
34
+
35
+ Add a new markuplint-specific extended pseudo-class. Follow recipe #1 in `docs/maintenance.md`.
36
+
37
+ ### Step 1: Create the handler
38
+
39
+ 1. Read existing handlers in `src/extended-selector/` to understand the pattern
40
+ 2. Create `src/extended-selector/<name>-pseudo-class.ts`
41
+ 3. Implement the handler following the `ExtendedPseudoClass` signature:
42
+ ```typescript
43
+ (content: string) => (el: Element) => SelectorResult;
44
+ ```
45
+ 4. Return specificity `[0, 1, 0]` for consistency with other extended pseudo-classes
46
+
47
+ ### Step 2: Register the handler
48
+
49
+ 1. Open `src/create-selector.ts`
50
+ 2. Import the new handler
51
+ 3. Add it to the extended object in `createSelector()` when `specs` is provided
52
+
53
+ ### Step 3: Test and document
54
+
55
+ 1. Add tests in `src/create-selector.spec.ts` or a new test file
56
+ 2. Update `README.md` extended selector table
57
+ 3. Build: `yarn build --scope @markuplint/selector`
58
+ 4. Run tests: `yarn test --scope @markuplint/selector`
59
+
60
+ ## Task: add-selector-support
61
+
62
+ Add support for a currently unsupported CSS pseudo-class. Follow recipe #2 in `docs/maintenance.md`.
63
+
64
+ ### Step 1: Implement matching
65
+
66
+ 1. Read `src/selector.ts`, find the `pseudoMatch()` function
67
+ 2. Move the target pseudo-class from the unsupported case list
68
+ 3. Add a new case with matching implementation
69
+ 4. Ensure correct specificity assignment
70
+
71
+ ### Step 2: Test and document
72
+
73
+ 1. Add tests covering the new selector
74
+ 2. Update `README.md` support table (change `❌` to `✅`)
75
+ 3. Build: `yarn build --scope @markuplint/selector`
76
+ 4. Run tests: `yarn test --scope @markuplint/selector`
77
+
78
+ ## Task: debug-matching
79
+
80
+ Debug why a selector matches or doesn't match against given HTML.
81
+
82
+ ### Step 1: Set up the environment
83
+
84
+ 1. Read `src/selector.ts` and `src/match-selector.ts` to understand the matching flow
85
+ 2. Identify whether the selector is a CSS string or a `RegexSelector` object
86
+
87
+ ### Step 2: Trace the matching
88
+
89
+ 1. Enable debug logging: `DEBUG=selector* yarn test --scope @markuplint/selector`
90
+ 2. For CSS selectors, trace through:
91
+ - `Ruleset.parse()` -- Check that the selector parses correctly
92
+ - `StructuredSelector` -- Check the `SelectorTarget` chain
93
+ - `SelectorTarget` -- Check each compound selector component
94
+ 3. For regex selectors, trace through:
95
+ - `regexSelectorMatches()` -- Check pattern matching results
96
+ - `SelectorTarget.match()` -- Check combinator traversal
97
+
98
+ ### Step 3: Report findings
99
+
100
+ 1. Identify the exact point where matching succeeds or fails
101
+ 2. Check if the issue is in parsing, matching, or specificity calculation
102
+ 3. Suggest a fix or workaround
103
+
104
+ ## Task: update-deps
105
+
106
+ Update package dependencies and verify compatibility.
107
+
108
+ 1. Read `package.json` for current dependency versions
109
+ 2. Check for available updates
110
+ 3. For `postcss-selector-parser` updates:
111
+ - Review the changelog for breaking AST or API changes
112
+ - Refer to recipe #3 in `docs/maintenance.md` for the integration surface
113
+ 4. For `@markuplint/ml-spec` updates:
114
+ - Check for type changes affecting extended pseudo-classes
115
+ 5. Update dependencies
116
+ 6. Build: `yarn build --scope @markuplint/selector`
117
+ 7. Run tests: `yarn test --scope @markuplint/selector`
118
+ 8. Verify no regressions in selector matching
119
+
120
+ ## Rules
121
+
122
+ 1. **Always build after source changes.** Run `yarn build --scope @markuplint/selector` before testing.
123
+ 2. **All extended pseudo-classes use `[0, 1, 0]` specificity.** Maintain this consistency.
124
+ 3. **CSS matching uses postcss-selector-parser AST.** Never manually parse CSS selector strings.
125
+ 4. **Regex matching is separate from CSS matching.** They share the `matchSelector()` entry point but use different code paths.
126
+ 5. **Selector instances are cached.** After changing parsing or matching logic, the cache may return stale instances in tests. Clear the cache or restart the test runner.