@markuplint/selector 4.7.7 → 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.
- package/ARCHITECTURE.ja.md +243 -0
- package/ARCHITECTURE.md +243 -0
- package/CHANGELOG.md +3 -3
- package/README.md +6 -0
- package/SKILL.md +126 -0
- package/docs/maintenance.ja.md +209 -0
- package/docs/maintenance.md +209 -0
- package/docs/matching.ja.md +211 -0
- package/docs/matching.md +211 -0
- package/lib/compare-specificity.d.ts +8 -0
- package/lib/compare-specificity.js +8 -0
- package/lib/create-selector.d.ts +13 -0
- package/lib/create-selector.js +13 -0
- package/lib/debug.d.ts +5 -0
- package/lib/debug.js +5 -0
- package/lib/extended-selector/aria-pseudo-class.d.ts +7 -1
- package/lib/extended-selector/aria-pseudo-class.js +7 -1
- package/lib/extended-selector/aria-role-pseudo-class.d.ts +9 -0
- package/lib/extended-selector/aria-role-pseudo-class.js +9 -0
- package/lib/extended-selector/content-model-pseudo-class.d.ts +9 -0
- package/lib/extended-selector/content-model-pseudo-class.js +9 -0
- package/lib/invalid-selector-error.d.ts +8 -0
- package/lib/invalid-selector-error.js +7 -0
- package/lib/is.d.ts +13 -0
- package/lib/is.js +13 -0
- package/lib/match-selector.d.ts +16 -0
- package/lib/match-selector.js +12 -0
- package/lib/regex-selector-matches.d.ts +12 -0
- package/lib/regex-selector-matches.js +12 -0
- package/lib/selector.d.ts +5 -0
- package/lib/selector.js +5 -0
- package/lib/types.d.ts +42 -0
- package/package.json +5 -5
|
@@ -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) -- コマンド、テスト、レシピ、トラブルシューティング
|
package/ARCHITECTURE.md
ADDED
|
@@ -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,13 @@
|
|
|
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
|
+
## [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
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
**Note:** Version bump only for package @markuplint/selector
|
|
13
13
|
|
|
14
14
|
## [4.7.6](https://github.com/markuplint/markuplint/compare/@markuplint/selector@4.7.5...@markuplint/selector@4.7.6) (2025-08-24)
|
|
15
15
|
|
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.
|