@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.
- package/ARCHITECTURE.ja.md +259 -0
- package/ARCHITECTURE.md +259 -0
- package/CHANGELOG.md +13 -2
- package/README.md +6 -0
- package/SKILL.md +126 -0
- package/docs/maintenance.ja.md +211 -0
- package/docs/maintenance.md +211 -0
- package/docs/matching.ja.md +211 -0
- package/docs/matching.md +211 -0
- package/lib/compare-specificity.d.ts +9 -1
- package/lib/compare-specificity.js +8 -0
- package/lib/create-selector.d.ts +13 -0
- package/lib/create-selector.js +14 -1
- package/lib/debug.d.ts +5 -0
- package/lib/debug.js +5 -0
- package/lib/extended-selector/aria-pseudo-class.d.ts +26 -3
- package/lib/extended-selector/aria-pseudo-class.js +33 -4
- package/lib/extended-selector/aria-role-pseudo-class.d.ts +11 -2
- package/lib/extended-selector/aria-role-pseudo-class.js +9 -0
- package/lib/extended-selector/content-model-pseudo-class.d.ts +11 -2
- package/lib/extended-selector/content-model-pseudo-class.js +9 -0
- package/lib/index.d.ts +5 -2
- package/lib/index.js +1 -1
- package/lib/invalid-selector-error.d.ts +8 -0
- package/lib/invalid-selector-error.js +10 -1
- package/lib/is.d.ts +19 -5
- package/lib/is.js +19 -9
- package/lib/match-selector.d.ts +18 -2
- package/lib/match-selector.js +27 -40
- package/lib/regex-selector-matches.d.ts +12 -0
- package/lib/regex-selector-matches.js +12 -0
- package/lib/selector.d.ts +35 -5
- package/lib/selector.js +75 -96
- package/lib/types.d.ts +89 -1
- package/package.json +10 -7
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: SelectorElement) => 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.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# メンテナンスガイド
|
|
2
|
+
|
|
3
|
+
`@markuplint/selector` の実践的な運用・メンテナンスガイド。
|
|
4
|
+
|
|
5
|
+
## コマンド
|
|
6
|
+
|
|
7
|
+
| コマンド | 説明 |
|
|
8
|
+
| ----------------------------------------------- | --------------------------------- |
|
|
9
|
+
| `yarn build --scope @markuplint/selector` | TypeScript を `lib/` にコンパイル |
|
|
10
|
+
| `yarn workspace @markuplint/selector run dev` | ウォッチモードでコンパイル |
|
|
11
|
+
| `yarn workspace @markuplint/selector run clean` | コンパイル出力をクリーン |
|
|
12
|
+
| `yarn test --scope @markuplint/selector` | テストを実行 |
|
|
13
|
+
|
|
14
|
+
## テスト
|
|
15
|
+
|
|
16
|
+
テストは `vitest` と `jsdom`(DOM 環境)を使用します。4 つのテストファイルがパッケージをカバーしています:
|
|
17
|
+
|
|
18
|
+
| テストファイル | カバー範囲 |
|
|
19
|
+
| -------------------------------- | ------------------------------------------------------------------------ |
|
|
20
|
+
| `selector.spec.ts` | コア `Selector` クラス、CSS セレクタマッチング、コンビネータ、擬似クラス |
|
|
21
|
+
| `create-selector.spec.ts` | `createSelector()` ファクトリ、キャッシュ、拡張擬似クラス統合 |
|
|
22
|
+
| `match-selector.spec.ts` | `matchSelector()` 統合関数、CSS と Regex セレクタのディスパッチ |
|
|
23
|
+
| `regex-selector-matches.spec.ts` | `regexSelectorMatches()` パターンマッチング、キャプチャグループ |
|
|
24
|
+
|
|
25
|
+
### テストの実行
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# 全セレクタテストを実行
|
|
29
|
+
yarn test --scope @markuplint/selector
|
|
30
|
+
|
|
31
|
+
# 特定のテストファイルを実行
|
|
32
|
+
yarn workspace @markuplint/selector run vitest run src/selector.spec.ts
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## レシピ
|
|
36
|
+
|
|
37
|
+
### 1. 新しい拡張擬似クラスの追加
|
|
38
|
+
|
|
39
|
+
新しい markuplint 固有の擬似クラス(例: `:custom()`)を追加するには:
|
|
40
|
+
|
|
41
|
+
1. `src/extended-selector/custom-pseudo-class.ts` を作成:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import type { SelectorElement, SelectorResult } from '../types.js';
|
|
45
|
+
|
|
46
|
+
export function customPseudoClass() {
|
|
47
|
+
return (content: string) =>
|
|
48
|
+
(el: SelectorElement): SelectorResult => {
|
|
49
|
+
// content 文字列をパースして el に対してマッチング
|
|
50
|
+
// 完全な DOM API(例: @markuplint/ml-spec)が必要な場合は、
|
|
51
|
+
// その境界で `el as Element` とキャストしてください。
|
|
52
|
+
const matched = /* マッチングロジック */;
|
|
53
|
+
return {
|
|
54
|
+
specificity: [0, 1, 0],
|
|
55
|
+
matched,
|
|
56
|
+
...(matched ? { nodes: [el], has: [] } : {}),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
2. `src/create-selector.ts` で登録:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { customPseudoClass } from './extended-selector/custom-pseudo-class.js';
|
|
66
|
+
|
|
67
|
+
// createSelector() 内の extended オブジェクトに追加:
|
|
68
|
+
instance = new Selector(
|
|
69
|
+
selector,
|
|
70
|
+
specs
|
|
71
|
+
? {
|
|
72
|
+
model: contentModelPseudoClass(specs),
|
|
73
|
+
aria: ariaPseudoClass(),
|
|
74
|
+
role: ariaRolePseudoClass(specs),
|
|
75
|
+
custom: customPseudoClass(), // ここに追加
|
|
76
|
+
}
|
|
77
|
+
: undefined,
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
3. `src/create-selector.spec.ts` または新規テストファイルにテストを追加
|
|
82
|
+
4. `README.md` に新しい擬似クラスのドキュメントを追加
|
|
83
|
+
5. ビルド: `yarn build --scope @markuplint/selector`
|
|
84
|
+
|
|
85
|
+
### 2. 新しい CSS セレクタタイプのサポート追加
|
|
86
|
+
|
|
87
|
+
現在サポートされていない擬似クラス(例: `:empty`)をサポートするには:
|
|
88
|
+
|
|
89
|
+
1. `src/selector.ts` を開く
|
|
90
|
+
2. `pseudoMatch()` 関数の switch 文を見つける
|
|
91
|
+
3. 擬似クラスを「非サポート」ケースリストから新しいケースに移動して実装:
|
|
92
|
+
```typescript
|
|
93
|
+
case ':empty': {
|
|
94
|
+
const hasChildren = el.childNodes.length === 0;
|
|
95
|
+
return {
|
|
96
|
+
specificity: [0, 1, 0],
|
|
97
|
+
matched: hasChildren,
|
|
98
|
+
...(hasChildren ? { nodes: [el], has: [] } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
4. 新しいセレクタのテストを追加
|
|
103
|
+
5. `README.md` のサポート表を更新(`❌` を `✅` に変更)
|
|
104
|
+
6. ビルド: `yarn build --scope @markuplint/selector`
|
|
105
|
+
|
|
106
|
+
### 3. postcss-selector-parser メジャー更新時の対応
|
|
107
|
+
|
|
108
|
+
`postcss-selector-parser` がメジャーバージョンをリリースした場合:
|
|
109
|
+
|
|
110
|
+
1. 以下の破壊的変更をチェンジログで確認:
|
|
111
|
+
- AST ノード型(`parser.Selector`、`parser.Node` 等)
|
|
112
|
+
- パーサ API(`parser()`、`.processSync()`)
|
|
113
|
+
- ノードプロパティの名前と型
|
|
114
|
+
2. `src/selector.ts` の主要な統合ポイント:
|
|
115
|
+
- `Ruleset.parse()` -- `parser()` と `processSync()` を使用
|
|
116
|
+
- `StructuredSelector` コンストラクタ -- `parser.Node` 型を走査
|
|
117
|
+
- `SelectorTarget` -- ノードプロパティ(`value`、`attribute`、`operator`、`raws` 等)にアクセス
|
|
118
|
+
3. `package.json` の依存バージョンを更新
|
|
119
|
+
4. 型エラーや API 変更を修正
|
|
120
|
+
5. テストスイート全体を実行して互換性を確認
|
|
121
|
+
6. ビルド: `yarn build --scope @markuplint/selector`
|
|
122
|
+
|
|
123
|
+
### 4. 詳細度計算の修正
|
|
124
|
+
|
|
125
|
+
詳細度が正しく計算されない場合:
|
|
126
|
+
|
|
127
|
+
1. `src/compare-specificity.ts` の比較ロジックを確認
|
|
128
|
+
2. `src/selector.ts` の `SelectorTarget` と `pseudoMatch()` での詳細度割り当てを確認
|
|
129
|
+
3. Regex セレクタの場合、`src/match-selector.ts` の `uncombinedRegexSelect()` での詳細度追跡を確認
|
|
130
|
+
4. 主要な詳細度の値:
|
|
131
|
+
- ID: `[1, 0, 0]`
|
|
132
|
+
- クラス、属性、擬似クラス: `[0, 1, 0]`
|
|
133
|
+
- タイプ(タグ): `[0, 0, 1]`
|
|
134
|
+
- ユニバーサル: `[0, 0, 0]`
|
|
135
|
+
- `:where()`: 常に `[0, 0, 0]`
|
|
136
|
+
5. 誤った計算を再現するテストケースを追加
|
|
137
|
+
6. 修正して確認
|
|
138
|
+
|
|
139
|
+
### 5. 新しい Regex コンビネータの追加
|
|
140
|
+
|
|
141
|
+
Regex セレクタに新しいコンビネータを追加するには:
|
|
142
|
+
|
|
143
|
+
1. `src/types.ts` の `RegexSelectorCombinator` 型を更新:
|
|
144
|
+
```typescript
|
|
145
|
+
export type RegexSelectorCombinator = ' ' | '>' | '+' | '~' | ':has(+)' | ':has(~)' | ':new()';
|
|
146
|
+
```
|
|
147
|
+
2. `src/match-selector.ts` の `SelectorTarget.match()` の switch にトラバーサルロジックを追加:
|
|
148
|
+
```typescript
|
|
149
|
+
case ':new()': {
|
|
150
|
+
// DOM トラバーサルロジックを実装
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
3. 新しいコンビネータのテストを追加
|
|
154
|
+
4. ドキュメントを更新
|
|
155
|
+
5. ビルド: `yarn build --scope @markuplint/selector`
|
|
156
|
+
|
|
157
|
+
## トラブルシューティング
|
|
158
|
+
|
|
159
|
+
### セレクタパースエラー
|
|
160
|
+
|
|
161
|
+
**症状:** 有効に見えるセレクタで `InvalidSelectorError` がスローされる。
|
|
162
|
+
|
|
163
|
+
**診断:**
|
|
164
|
+
|
|
165
|
+
1. `postcss-selector-parser` がそのセレクタ構文をサポートしているか確認
|
|
166
|
+
2. セレクタを分離してテスト:
|
|
167
|
+
```typescript
|
|
168
|
+
import parser from 'postcss-selector-parser';
|
|
169
|
+
parser().processSync('your-selector');
|
|
170
|
+
```
|
|
171
|
+
3. 一部のセレクタには特定の `postcss-selector-parser` バージョンが必要な場合がある
|
|
172
|
+
|
|
173
|
+
### マッチングの不一致
|
|
174
|
+
|
|
175
|
+
**症状:** セレクタがマッチすべき時にマッチしない、またはその逆。
|
|
176
|
+
|
|
177
|
+
**診断:**
|
|
178
|
+
|
|
179
|
+
1. デバッグログを有効化: `DEBUG=selector* yarn test --scope @markuplint/selector`
|
|
180
|
+
2. `SelectorTarget` のマッチング順序を確認 -- コンポーネントは順次チェックされ、最初の不一致で失敗
|
|
181
|
+
3. HTML/SVG 要素の名前空間解決を `resolveNamespace()` で確認
|
|
182
|
+
4. 拡張擬似クラスの場合、`specs` が `createSelector()` に渡されているか確認
|
|
183
|
+
|
|
184
|
+
### 詳細度計算の問題
|
|
185
|
+
|
|
186
|
+
**症状:** 詳細度の高いルールが優先されない。
|
|
187
|
+
|
|
188
|
+
**診断:**
|
|
189
|
+
|
|
190
|
+
1. `matchSelector()` または `Selector.match()` が返す詳細度の値をログ出力
|
|
191
|
+
2. `compareSpecificity()` の比較ロジックを確認
|
|
192
|
+
3. `:where()` が正しく `[0, 0, 0]` を返しているか確認
|
|
193
|
+
4. ネストされた擬似クラス(`:not(:is(.a, .b))`)の場合、再帰的な詳細度計算をトレース
|
|
194
|
+
|
|
195
|
+
## 依存関係メモ
|
|
196
|
+
|
|
197
|
+
### postcss-selector-parser
|
|
198
|
+
|
|
199
|
+
- `selector.ts` 全体で使用される CSS セレクタ AST を提供
|
|
200
|
+
- 主要な型: `parser.Selector`、`parser.Node`、`parser.Pseudo`、`parser.Attribute`、`parser.ClassName`、`parser.Identifier`、`parser.Tag`、`parser.Universal`、`parser.Combinator`
|
|
201
|
+
- メジャーバージョンの破壊的変更は AST ノード構造に影響する可能性あり
|
|
202
|
+
|
|
203
|
+
### @markuplint/ml-spec
|
|
204
|
+
|
|
205
|
+
- `getComputedRole()`、`getAccname()`、`contentModelCategoryToTagNames()`、`resolveNamespace()` を提供
|
|
206
|
+
- `MLMLSpec` の型変更は拡張擬似クラスの実装に影響する可能性あり
|
|
207
|
+
|
|
208
|
+
### jsdom(開発)
|
|
209
|
+
|
|
210
|
+
- テストで DOM 環境を作成するための `JSDOM` を提供
|
|
211
|
+
- `jsdom` で作成された要素はセレクタマッチングロジックが使用する `SelectorElement` インターフェースを満たす
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Maintenance Guide
|
|
2
|
+
|
|
3
|
+
Practical operations and maintenance guide for `@markuplint/selector`.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
| Command | Description |
|
|
8
|
+
| ----------------------------------------------- | ---------------------------- |
|
|
9
|
+
| `yarn build --scope @markuplint/selector` | Compile TypeScript to `lib/` |
|
|
10
|
+
| `yarn workspace @markuplint/selector run dev` | Watch mode compilation |
|
|
11
|
+
| `yarn workspace @markuplint/selector run clean` | Clean compiled output |
|
|
12
|
+
| `yarn test --scope @markuplint/selector` | Run tests |
|
|
13
|
+
|
|
14
|
+
## Testing
|
|
15
|
+
|
|
16
|
+
Tests use `vitest` with `jsdom` as the DOM environment. Four test files cover the package:
|
|
17
|
+
|
|
18
|
+
| Test File | Coverage |
|
|
19
|
+
| -------------------------------- | ------------------------------------------------------------------------- |
|
|
20
|
+
| `selector.spec.ts` | Core `Selector` class, CSS selector matching, combinators, pseudo-classes |
|
|
21
|
+
| `create-selector.spec.ts` | `createSelector()` factory, caching, extended pseudo-class integration |
|
|
22
|
+
| `match-selector.spec.ts` | `matchSelector()` unified function, CSS and regex selector dispatch |
|
|
23
|
+
| `regex-selector-matches.spec.ts` | `regexSelectorMatches()` pattern matching, capture groups |
|
|
24
|
+
|
|
25
|
+
### Running Tests
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Run all selector tests
|
|
29
|
+
yarn test --scope @markuplint/selector
|
|
30
|
+
|
|
31
|
+
# Run a specific test file
|
|
32
|
+
yarn workspace @markuplint/selector run vitest run src/selector.spec.ts
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Common Recipes
|
|
36
|
+
|
|
37
|
+
### 1. Adding a New Extended Pseudo-Class
|
|
38
|
+
|
|
39
|
+
To add a new markuplint-specific pseudo-class (e.g., `:custom()`):
|
|
40
|
+
|
|
41
|
+
1. Create `src/extended-selector/custom-pseudo-class.ts`:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import type { SelectorElement, SelectorResult } from '../types.js';
|
|
45
|
+
|
|
46
|
+
export function customPseudoClass() {
|
|
47
|
+
return (content: string) =>
|
|
48
|
+
(el: SelectorElement): SelectorResult => {
|
|
49
|
+
// Parse content string and match against el
|
|
50
|
+
// If you need full DOM APIs (e.g., from @markuplint/ml-spec),
|
|
51
|
+
// cast with `el as Element` at that boundary.
|
|
52
|
+
const matched = /* your matching logic */;
|
|
53
|
+
return {
|
|
54
|
+
specificity: [0, 1, 0],
|
|
55
|
+
matched,
|
|
56
|
+
...(matched ? { nodes: [el], has: [] } : {}),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
2. Register it in `src/create-selector.ts`:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { customPseudoClass } from './extended-selector/custom-pseudo-class.js';
|
|
66
|
+
|
|
67
|
+
// In createSelector(), add to the extended object:
|
|
68
|
+
instance = new Selector(
|
|
69
|
+
selector,
|
|
70
|
+
specs
|
|
71
|
+
? {
|
|
72
|
+
model: contentModelPseudoClass(specs),
|
|
73
|
+
aria: ariaPseudoClass(),
|
|
74
|
+
role: ariaRolePseudoClass(specs),
|
|
75
|
+
custom: customPseudoClass(), // Add here
|
|
76
|
+
}
|
|
77
|
+
: undefined,
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
3. Add tests in `src/create-selector.spec.ts` or a new test file
|
|
82
|
+
4. Update `README.md` with the new pseudo-class documentation
|
|
83
|
+
5. Build: `yarn build --scope @markuplint/selector`
|
|
84
|
+
|
|
85
|
+
### 2. Adding Support for a New CSS Selector Type
|
|
86
|
+
|
|
87
|
+
To support a currently unsupported pseudo-class (e.g., `:empty`):
|
|
88
|
+
|
|
89
|
+
1. Open `src/selector.ts`
|
|
90
|
+
2. Find the `pseudoMatch()` function's switch statement
|
|
91
|
+
3. Move the pseudo-class from the "unsupported" case list to a new case with implementation:
|
|
92
|
+
```typescript
|
|
93
|
+
case ':empty': {
|
|
94
|
+
const hasChildren = el.childNodes.length === 0;
|
|
95
|
+
return {
|
|
96
|
+
specificity: [0, 1, 0],
|
|
97
|
+
matched: hasChildren,
|
|
98
|
+
...(hasChildren ? { nodes: [el], has: [] } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
4. Add tests for the new selector
|
|
103
|
+
5. Update `README.md` support table (change `❌` to `✅`)
|
|
104
|
+
6. Build: `yarn build --scope @markuplint/selector`
|
|
105
|
+
|
|
106
|
+
### 3. Handling postcss-selector-parser Major Updates
|
|
107
|
+
|
|
108
|
+
When `postcss-selector-parser` releases a major version:
|
|
109
|
+
|
|
110
|
+
1. Check the changelog for breaking changes in:
|
|
111
|
+
- AST node types (`parser.Selector`, `parser.Node`, etc.)
|
|
112
|
+
- Parser API (`parser()`, `.processSync()`)
|
|
113
|
+
- Node property names and types
|
|
114
|
+
2. Key integration points in `src/selector.ts`:
|
|
115
|
+
- `Ruleset.parse()` -- Uses `parser()` and `processSync()`
|
|
116
|
+
- `StructuredSelector` constructor -- Walks `parser.Node` types
|
|
117
|
+
- `SelectorTarget` -- Accesses node properties (`value`, `attribute`, `operator`, `raws`, etc.)
|
|
118
|
+
3. Update the dependency version in `package.json`
|
|
119
|
+
4. Fix any type errors or API changes
|
|
120
|
+
5. Run the full test suite to verify compatibility
|
|
121
|
+
6. Build: `yarn build --scope @markuplint/selector`
|
|
122
|
+
|
|
123
|
+
### 4. Fixing Specificity Calculation
|
|
124
|
+
|
|
125
|
+
If specificity is calculated incorrectly:
|
|
126
|
+
|
|
127
|
+
1. Check `src/compare-specificity.ts` for comparison logic
|
|
128
|
+
2. Check `src/selector.ts` for specificity assignment in `SelectorTarget` and `pseudoMatch()`
|
|
129
|
+
3. For regex selectors, check `src/match-selector.ts` `uncombinedRegexSelect()` for specificity tracking
|
|
130
|
+
4. Key specificity values:
|
|
131
|
+
- ID: `[1, 0, 0]`
|
|
132
|
+
- Class, attribute, pseudo-class: `[0, 1, 0]`
|
|
133
|
+
- Type (tag): `[0, 0, 1]`
|
|
134
|
+
- Universal: `[0, 0, 0]`
|
|
135
|
+
- `:where()`: Always `[0, 0, 0]`
|
|
136
|
+
5. Add a test case reproducing the incorrect calculation
|
|
137
|
+
6. Fix and verify
|
|
138
|
+
|
|
139
|
+
### 5. Adding a New Regex Combinator
|
|
140
|
+
|
|
141
|
+
To add a new combinator for regex selectors:
|
|
142
|
+
|
|
143
|
+
1. Update the `RegexSelectorCombinator` type in `src/types.ts`:
|
|
144
|
+
```typescript
|
|
145
|
+
export type RegexSelectorCombinator = ' ' | '>' | '+' | '~' | ':has(+)' | ':has(~)' | ':new()';
|
|
146
|
+
```
|
|
147
|
+
2. Add the traversal logic in `src/match-selector.ts` in the `SelectorTarget.match()` switch:
|
|
148
|
+
```typescript
|
|
149
|
+
case ':new()': {
|
|
150
|
+
// Implement DOM traversal logic
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
3. Add tests for the new combinator
|
|
154
|
+
4. Update documentation
|
|
155
|
+
5. Build: `yarn build --scope @markuplint/selector`
|
|
156
|
+
|
|
157
|
+
## Troubleshooting
|
|
158
|
+
|
|
159
|
+
### Selector Parse Errors
|
|
160
|
+
|
|
161
|
+
**Symptom:** `InvalidSelectorError` thrown for a valid-looking selector.
|
|
162
|
+
|
|
163
|
+
**Diagnosis:**
|
|
164
|
+
|
|
165
|
+
1. Check if `postcss-selector-parser` supports the selector syntax
|
|
166
|
+
2. Test the selector in isolation:
|
|
167
|
+
```typescript
|
|
168
|
+
import parser from 'postcss-selector-parser';
|
|
169
|
+
parser().processSync('your-selector');
|
|
170
|
+
```
|
|
171
|
+
3. Some selectors may require specific `postcss-selector-parser` versions
|
|
172
|
+
|
|
173
|
+
### Matching Mismatches
|
|
174
|
+
|
|
175
|
+
**Symptom:** A selector matches or doesn't match when it should/shouldn't.
|
|
176
|
+
|
|
177
|
+
**Diagnosis:**
|
|
178
|
+
|
|
179
|
+
1. Enable debug logging: `DEBUG=selector* yarn test --scope @markuplint/selector`
|
|
180
|
+
2. Check the `SelectorTarget` matching order -- components are checked sequentially and fail on first mismatch
|
|
181
|
+
3. Verify namespace resolution for HTML/SVG elements using `resolveNamespace()`
|
|
182
|
+
4. For extended pseudo-classes, verify that `specs` are being passed to `createSelector()`
|
|
183
|
+
|
|
184
|
+
### Specificity Calculation Issues
|
|
185
|
+
|
|
186
|
+
**Symptom:** Rules with higher specificity are not taking precedence.
|
|
187
|
+
|
|
188
|
+
**Diagnosis:**
|
|
189
|
+
|
|
190
|
+
1. Log the specificity values returned by `matchSelector()` or `Selector.match()`
|
|
191
|
+
2. Check `compareSpecificity()` comparison logic
|
|
192
|
+
3. Verify `:where()` is correctly returning `[0, 0, 0]`
|
|
193
|
+
4. For nested pseudo-classes (`:not(:is(.a, .b))`), trace through the recursive specificity calculation
|
|
194
|
+
|
|
195
|
+
## Dependency Notes
|
|
196
|
+
|
|
197
|
+
### postcss-selector-parser
|
|
198
|
+
|
|
199
|
+
- Provides the CSS selector AST used throughout `selector.ts`
|
|
200
|
+
- Key types: `parser.Selector`, `parser.Node`, `parser.Pseudo`, `parser.Attribute`, `parser.ClassName`, `parser.Identifier`, `parser.Tag`, `parser.Universal`, `parser.Combinator`
|
|
201
|
+
- Breaking changes in major versions may affect AST node structure
|
|
202
|
+
|
|
203
|
+
### @markuplint/ml-spec
|
|
204
|
+
|
|
205
|
+
- Provides `getComputedRole()`, `getAccname()`, `contentModelCategoryToTagNames()`, `resolveNamespace()`
|
|
206
|
+
- Type changes in `MLMLSpec` may affect extended pseudo-class implementations
|
|
207
|
+
|
|
208
|
+
### jsdom (dev)
|
|
209
|
+
|
|
210
|
+
- Provides `JSDOM` for creating DOM environments in tests
|
|
211
|
+
- Elements created via `jsdom` satisfy the `SelectorElement` interface used by the selector matching logic
|