@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.
- package/ARCHITECTURE.ja.md +243 -0
- package/ARCHITECTURE.md +243 -0
- package/CHANGELOG.md +5 -1
- 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 +9 -1
- 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 +6 -6
|
@@ -0,0 +1,209 @@
|
|
|
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 { SelectorResult } from '../types.js';
|
|
45
|
+
|
|
46
|
+
export function customPseudoClass() {
|
|
47
|
+
return (content: string) =>
|
|
48
|
+
(el: Element): SelectorResult => {
|
|
49
|
+
// content 文字列をパースして el に対してマッチング
|
|
50
|
+
const matched = /* マッチングロジック */;
|
|
51
|
+
return {
|
|
52
|
+
specificity: [0, 1, 0],
|
|
53
|
+
matched,
|
|
54
|
+
...(matched ? { nodes: [el], has: [] } : {}),
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. `src/create-selector.ts` で登録:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { customPseudoClass } from './extended-selector/custom-pseudo-class.js';
|
|
64
|
+
|
|
65
|
+
// createSelector() 内の extended オブジェクトに追加:
|
|
66
|
+
instance = new Selector(
|
|
67
|
+
selector,
|
|
68
|
+
specs
|
|
69
|
+
? {
|
|
70
|
+
model: contentModelPseudoClass(specs),
|
|
71
|
+
aria: ariaPseudoClass(),
|
|
72
|
+
role: ariaRolePseudoClass(specs),
|
|
73
|
+
custom: customPseudoClass(), // ここに追加
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
3. `src/create-selector.spec.ts` または新規テストファイルにテストを追加
|
|
80
|
+
4. `README.md` に新しい擬似クラスのドキュメントを追加
|
|
81
|
+
5. ビルド: `yarn build --scope @markuplint/selector`
|
|
82
|
+
|
|
83
|
+
### 2. 新しい CSS セレクタタイプのサポート追加
|
|
84
|
+
|
|
85
|
+
現在サポートされていない擬似クラス(例: `:empty`)をサポートするには:
|
|
86
|
+
|
|
87
|
+
1. `src/selector.ts` を開く
|
|
88
|
+
2. `pseudoMatch()` 関数の switch 文を見つける
|
|
89
|
+
3. 擬似クラスを「非サポート」ケースリストから新しいケースに移動して実装:
|
|
90
|
+
```typescript
|
|
91
|
+
case ':empty': {
|
|
92
|
+
const hasChildren = el.childNodes.length === 0;
|
|
93
|
+
return {
|
|
94
|
+
specificity: [0, 1, 0],
|
|
95
|
+
matched: hasChildren,
|
|
96
|
+
...(hasChildren ? { nodes: [el], has: [] } : {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
4. 新しいセレクタのテストを追加
|
|
101
|
+
5. `README.md` のサポート表を更新(`❌` を `✅` に変更)
|
|
102
|
+
6. ビルド: `yarn build --scope @markuplint/selector`
|
|
103
|
+
|
|
104
|
+
### 3. postcss-selector-parser メジャー更新時の対応
|
|
105
|
+
|
|
106
|
+
`postcss-selector-parser` がメジャーバージョンをリリースした場合:
|
|
107
|
+
|
|
108
|
+
1. 以下の破壊的変更をチェンジログで確認:
|
|
109
|
+
- AST ノード型(`parser.Selector`、`parser.Node` 等)
|
|
110
|
+
- パーサ API(`parser()`、`.processSync()`)
|
|
111
|
+
- ノードプロパティの名前と型
|
|
112
|
+
2. `src/selector.ts` の主要な統合ポイント:
|
|
113
|
+
- `Ruleset.parse()` -- `parser()` と `processSync()` を使用
|
|
114
|
+
- `StructuredSelector` コンストラクタ -- `parser.Node` 型を走査
|
|
115
|
+
- `SelectorTarget` -- ノードプロパティ(`value`、`attribute`、`operator`、`raws` 等)にアクセス
|
|
116
|
+
3. `package.json` の依存バージョンを更新
|
|
117
|
+
4. 型エラーや API 変更を修正
|
|
118
|
+
5. テストスイート全体を実行して互換性を確認
|
|
119
|
+
6. ビルド: `yarn build --scope @markuplint/selector`
|
|
120
|
+
|
|
121
|
+
### 4. 詳細度計算の修正
|
|
122
|
+
|
|
123
|
+
詳細度が正しく計算されない場合:
|
|
124
|
+
|
|
125
|
+
1. `src/compare-specificity.ts` の比較ロジックを確認
|
|
126
|
+
2. `src/selector.ts` の `SelectorTarget` と `pseudoMatch()` での詳細度割り当てを確認
|
|
127
|
+
3. Regex セレクタの場合、`src/match-selector.ts` の `uncombinedRegexSelect()` での詳細度追跡を確認
|
|
128
|
+
4. 主要な詳細度の値:
|
|
129
|
+
- ID: `[1, 0, 0]`
|
|
130
|
+
- クラス、属性、擬似クラス: `[0, 1, 0]`
|
|
131
|
+
- タイプ(タグ): `[0, 0, 1]`
|
|
132
|
+
- ユニバーサル: `[0, 0, 0]`
|
|
133
|
+
- `:where()`: 常に `[0, 0, 0]`
|
|
134
|
+
5. 誤った計算を再現するテストケースを追加
|
|
135
|
+
6. 修正して確認
|
|
136
|
+
|
|
137
|
+
### 5. 新しい Regex コンビネータの追加
|
|
138
|
+
|
|
139
|
+
Regex セレクタに新しいコンビネータを追加するには:
|
|
140
|
+
|
|
141
|
+
1. `src/types.ts` の `RegexSelectorCombinator` 型を更新:
|
|
142
|
+
```typescript
|
|
143
|
+
export type RegexSelectorCombinator = ' ' | '>' | '+' | '~' | ':has(+)' | ':has(~)' | ':new()';
|
|
144
|
+
```
|
|
145
|
+
2. `src/match-selector.ts` の `SelectorTarget.match()` の switch にトラバーサルロジックを追加:
|
|
146
|
+
```typescript
|
|
147
|
+
case ':new()': {
|
|
148
|
+
// DOM トラバーサルロジックを実装
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
3. 新しいコンビネータのテストを追加
|
|
152
|
+
4. ドキュメントを更新
|
|
153
|
+
5. ビルド: `yarn build --scope @markuplint/selector`
|
|
154
|
+
|
|
155
|
+
## トラブルシューティング
|
|
156
|
+
|
|
157
|
+
### セレクタパースエラー
|
|
158
|
+
|
|
159
|
+
**症状:** 有効に見えるセレクタで `InvalidSelectorError` がスローされる。
|
|
160
|
+
|
|
161
|
+
**診断:**
|
|
162
|
+
|
|
163
|
+
1. `postcss-selector-parser` がそのセレクタ構文をサポートしているか確認
|
|
164
|
+
2. セレクタを分離してテスト:
|
|
165
|
+
```typescript
|
|
166
|
+
import parser from 'postcss-selector-parser';
|
|
167
|
+
parser().processSync('your-selector');
|
|
168
|
+
```
|
|
169
|
+
3. 一部のセレクタには特定の `postcss-selector-parser` バージョンが必要な場合がある
|
|
170
|
+
|
|
171
|
+
### マッチングの不一致
|
|
172
|
+
|
|
173
|
+
**症状:** セレクタがマッチすべき時にマッチしない、またはその逆。
|
|
174
|
+
|
|
175
|
+
**診断:**
|
|
176
|
+
|
|
177
|
+
1. デバッグログを有効化: `DEBUG=selector* yarn test --scope @markuplint/selector`
|
|
178
|
+
2. `SelectorTarget` のマッチング順序を確認 -- コンポーネントは順次チェックされ、最初の不一致で失敗
|
|
179
|
+
3. HTML/SVG 要素の名前空間解決を `resolveNamespace()` で確認
|
|
180
|
+
4. 拡張擬似クラスの場合、`specs` が `createSelector()` に渡されているか確認
|
|
181
|
+
|
|
182
|
+
### 詳細度計算の問題
|
|
183
|
+
|
|
184
|
+
**症状:** 詳細度の高いルールが優先されない。
|
|
185
|
+
|
|
186
|
+
**診断:**
|
|
187
|
+
|
|
188
|
+
1. `matchSelector()` または `Selector.match()` が返す詳細度の値をログ出力
|
|
189
|
+
2. `compareSpecificity()` の比較ロジックを確認
|
|
190
|
+
3. `:where()` が正しく `[0, 0, 0]` を返しているか確認
|
|
191
|
+
4. ネストされた擬似クラス(`:not(:is(.a, .b))`)の場合、再帰的な詳細度計算をトレース
|
|
192
|
+
|
|
193
|
+
## 依存関係メモ
|
|
194
|
+
|
|
195
|
+
### postcss-selector-parser
|
|
196
|
+
|
|
197
|
+
- `selector.ts` 全体で使用される CSS セレクタ AST を提供
|
|
198
|
+
- 主要な型: `parser.Selector`、`parser.Node`、`parser.Pseudo`、`parser.Attribute`、`parser.ClassName`、`parser.Identifier`、`parser.Tag`、`parser.Universal`、`parser.Combinator`
|
|
199
|
+
- メジャーバージョンの破壊的変更は AST ノード構造に影響する可能性あり
|
|
200
|
+
|
|
201
|
+
### @markuplint/ml-spec
|
|
202
|
+
|
|
203
|
+
- `getComputedRole()`、`getAccname()`、`contentModelCategoryToTagNames()`、`resolveNamespace()` を提供
|
|
204
|
+
- `MLMLSpec` の型変更は拡張擬似クラスの実装に影響する可能性あり
|
|
205
|
+
|
|
206
|
+
### jsdom(開発)
|
|
207
|
+
|
|
208
|
+
- テストで DOM 環境を作成するための `JSDOM` を提供
|
|
209
|
+
- `jsdom` で作成された要素はセレクタマッチングロジックが使用する標準 DOM API を持つ
|
|
@@ -0,0 +1,209 @@
|
|
|
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 { SelectorResult } from '../types.js';
|
|
45
|
+
|
|
46
|
+
export function customPseudoClass() {
|
|
47
|
+
return (content: string) =>
|
|
48
|
+
(el: Element): SelectorResult => {
|
|
49
|
+
// Parse content string and match against el
|
|
50
|
+
const matched = /* your matching logic */;
|
|
51
|
+
return {
|
|
52
|
+
specificity: [0, 1, 0],
|
|
53
|
+
matched,
|
|
54
|
+
...(matched ? { nodes: [el], has: [] } : {}),
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. Register it in `src/create-selector.ts`:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { customPseudoClass } from './extended-selector/custom-pseudo-class.js';
|
|
64
|
+
|
|
65
|
+
// In createSelector(), add to the extended object:
|
|
66
|
+
instance = new Selector(
|
|
67
|
+
selector,
|
|
68
|
+
specs
|
|
69
|
+
? {
|
|
70
|
+
model: contentModelPseudoClass(specs),
|
|
71
|
+
aria: ariaPseudoClass(),
|
|
72
|
+
role: ariaRolePseudoClass(specs),
|
|
73
|
+
custom: customPseudoClass(), // Add here
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
3. Add tests in `src/create-selector.spec.ts` or a new test file
|
|
80
|
+
4. Update `README.md` with the new pseudo-class documentation
|
|
81
|
+
5. Build: `yarn build --scope @markuplint/selector`
|
|
82
|
+
|
|
83
|
+
### 2. Adding Support for a New CSS Selector Type
|
|
84
|
+
|
|
85
|
+
To support a currently unsupported pseudo-class (e.g., `:empty`):
|
|
86
|
+
|
|
87
|
+
1. Open `src/selector.ts`
|
|
88
|
+
2. Find the `pseudoMatch()` function's switch statement
|
|
89
|
+
3. Move the pseudo-class from the "unsupported" case list to a new case with implementation:
|
|
90
|
+
```typescript
|
|
91
|
+
case ':empty': {
|
|
92
|
+
const hasChildren = el.childNodes.length === 0;
|
|
93
|
+
return {
|
|
94
|
+
specificity: [0, 1, 0],
|
|
95
|
+
matched: hasChildren,
|
|
96
|
+
...(hasChildren ? { nodes: [el], has: [] } : {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
4. Add tests for the new selector
|
|
101
|
+
5. Update `README.md` support table (change `❌` to `✅`)
|
|
102
|
+
6. Build: `yarn build --scope @markuplint/selector`
|
|
103
|
+
|
|
104
|
+
### 3. Handling postcss-selector-parser Major Updates
|
|
105
|
+
|
|
106
|
+
When `postcss-selector-parser` releases a major version:
|
|
107
|
+
|
|
108
|
+
1. Check the changelog for breaking changes in:
|
|
109
|
+
- AST node types (`parser.Selector`, `parser.Node`, etc.)
|
|
110
|
+
- Parser API (`parser()`, `.processSync()`)
|
|
111
|
+
- Node property names and types
|
|
112
|
+
2. Key integration points in `src/selector.ts`:
|
|
113
|
+
- `Ruleset.parse()` -- Uses `parser()` and `processSync()`
|
|
114
|
+
- `StructuredSelector` constructor -- Walks `parser.Node` types
|
|
115
|
+
- `SelectorTarget` -- Accesses node properties (`value`, `attribute`, `operator`, `raws`, etc.)
|
|
116
|
+
3. Update the dependency version in `package.json`
|
|
117
|
+
4. Fix any type errors or API changes
|
|
118
|
+
5. Run the full test suite to verify compatibility
|
|
119
|
+
6. Build: `yarn build --scope @markuplint/selector`
|
|
120
|
+
|
|
121
|
+
### 4. Fixing Specificity Calculation
|
|
122
|
+
|
|
123
|
+
If specificity is calculated incorrectly:
|
|
124
|
+
|
|
125
|
+
1. Check `src/compare-specificity.ts` for comparison logic
|
|
126
|
+
2. Check `src/selector.ts` for specificity assignment in `SelectorTarget` and `pseudoMatch()`
|
|
127
|
+
3. For regex selectors, check `src/match-selector.ts` `uncombinedRegexSelect()` for specificity tracking
|
|
128
|
+
4. Key specificity values:
|
|
129
|
+
- ID: `[1, 0, 0]`
|
|
130
|
+
- Class, attribute, pseudo-class: `[0, 1, 0]`
|
|
131
|
+
- Type (tag): `[0, 0, 1]`
|
|
132
|
+
- Universal: `[0, 0, 0]`
|
|
133
|
+
- `:where()`: Always `[0, 0, 0]`
|
|
134
|
+
5. Add a test case reproducing the incorrect calculation
|
|
135
|
+
6. Fix and verify
|
|
136
|
+
|
|
137
|
+
### 5. Adding a New Regex Combinator
|
|
138
|
+
|
|
139
|
+
To add a new combinator for regex selectors:
|
|
140
|
+
|
|
141
|
+
1. Update the `RegexSelectorCombinator` type in `src/types.ts`:
|
|
142
|
+
```typescript
|
|
143
|
+
export type RegexSelectorCombinator = ' ' | '>' | '+' | '~' | ':has(+)' | ':has(~)' | ':new()';
|
|
144
|
+
```
|
|
145
|
+
2. Add the traversal logic in `src/match-selector.ts` in the `SelectorTarget.match()` switch:
|
|
146
|
+
```typescript
|
|
147
|
+
case ':new()': {
|
|
148
|
+
// Implement DOM traversal logic
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
3. Add tests for the new combinator
|
|
152
|
+
4. Update documentation
|
|
153
|
+
5. Build: `yarn build --scope @markuplint/selector`
|
|
154
|
+
|
|
155
|
+
## Troubleshooting
|
|
156
|
+
|
|
157
|
+
### Selector Parse Errors
|
|
158
|
+
|
|
159
|
+
**Symptom:** `InvalidSelectorError` thrown for a valid-looking selector.
|
|
160
|
+
|
|
161
|
+
**Diagnosis:**
|
|
162
|
+
|
|
163
|
+
1. Check if `postcss-selector-parser` supports the selector syntax
|
|
164
|
+
2. Test the selector in isolation:
|
|
165
|
+
```typescript
|
|
166
|
+
import parser from 'postcss-selector-parser';
|
|
167
|
+
parser().processSync('your-selector');
|
|
168
|
+
```
|
|
169
|
+
3. Some selectors may require specific `postcss-selector-parser` versions
|
|
170
|
+
|
|
171
|
+
### Matching Mismatches
|
|
172
|
+
|
|
173
|
+
**Symptom:** A selector matches or doesn't match when it should/shouldn't.
|
|
174
|
+
|
|
175
|
+
**Diagnosis:**
|
|
176
|
+
|
|
177
|
+
1. Enable debug logging: `DEBUG=selector* yarn test --scope @markuplint/selector`
|
|
178
|
+
2. Check the `SelectorTarget` matching order -- components are checked sequentially and fail on first mismatch
|
|
179
|
+
3. Verify namespace resolution for HTML/SVG elements using `resolveNamespace()`
|
|
180
|
+
4. For extended pseudo-classes, verify that `specs` are being passed to `createSelector()`
|
|
181
|
+
|
|
182
|
+
### Specificity Calculation Issues
|
|
183
|
+
|
|
184
|
+
**Symptom:** Rules with higher specificity are not taking precedence.
|
|
185
|
+
|
|
186
|
+
**Diagnosis:**
|
|
187
|
+
|
|
188
|
+
1. Log the specificity values returned by `matchSelector()` or `Selector.match()`
|
|
189
|
+
2. Check `compareSpecificity()` comparison logic
|
|
190
|
+
3. Verify `:where()` is correctly returning `[0, 0, 0]`
|
|
191
|
+
4. For nested pseudo-classes (`:not(:is(.a, .b))`), trace through the recursive specificity calculation
|
|
192
|
+
|
|
193
|
+
## Dependency Notes
|
|
194
|
+
|
|
195
|
+
### postcss-selector-parser
|
|
196
|
+
|
|
197
|
+
- Provides the CSS selector AST used throughout `selector.ts`
|
|
198
|
+
- Key types: `parser.Selector`, `parser.Node`, `parser.Pseudo`, `parser.Attribute`, `parser.ClassName`, `parser.Identifier`, `parser.Tag`, `parser.Universal`, `parser.Combinator`
|
|
199
|
+
- Breaking changes in major versions may affect AST node structure
|
|
200
|
+
|
|
201
|
+
### @markuplint/ml-spec
|
|
202
|
+
|
|
203
|
+
- Provides `getComputedRole()`, `getAccname()`, `contentModelCategoryToTagNames()`, `resolveNamespace()`
|
|
204
|
+
- Type changes in `MLMLSpec` may affect extended pseudo-class implementations
|
|
205
|
+
|
|
206
|
+
### jsdom (dev)
|
|
207
|
+
|
|
208
|
+
- Provides `JSDOM` for creating DOM environments in tests
|
|
209
|
+
- Elements created via `jsdom` have standard DOM APIs used by the selector matching logic
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# セレクタマッチング
|
|
2
|
+
|
|
3
|
+
`@markuplint/selector` の 2 つのセレクタマッチングシステムの詳細ドキュメント。
|
|
4
|
+
|
|
5
|
+
## 概要
|
|
6
|
+
|
|
7
|
+
このパッケージは 2 つの独立したマッチングシステムを提供します:
|
|
8
|
+
|
|
9
|
+
1. **CSS セレクタマッチング** -- `postcss-selector-parser` でパースされる標準 CSS セレクタ
|
|
10
|
+
2. **Regex セレクタマッチング** -- 正規表現を使用したパターンベースのマッチング
|
|
11
|
+
|
|
12
|
+
両システムとも詳細度情報を返し、統合関数 `matchSelector()` を通じて使用できます。
|
|
13
|
+
|
|
14
|
+
## CSS セレクタマッチングフロー
|
|
15
|
+
|
|
16
|
+
### 1. エントリーポイント
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
createSelector(selectorString, specs?)
|
|
20
|
+
→ new Selector(selectorString, extendedPseudoClasses)
|
|
21
|
+
→ Ruleset.parse(selectorString, extended)
|
|
22
|
+
→ postcss-selector-parser がセレクタ文字列を処理
|
|
23
|
+
→ parser.Selector[] AST ノードを返却
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. パース
|
|
27
|
+
|
|
28
|
+
`Ruleset.parse()` は `postcss-selector-parser` を使用してセレクタ文字列を AST にパースします。カンマ区切りの各セレクタは `parser.Selector` ノードになります。`Ruleset` は各ノードを `StructuredSelector` でラップします。
|
|
29
|
+
|
|
30
|
+
### 3. StructuredSelector チェーンの構築
|
|
31
|
+
|
|
32
|
+
各 `StructuredSelector` は AST ノードを走査し、コンビネータで連結された `SelectorTarget` オブジェクトのチェーンを構築します:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
div > .class:not(.other) span
|
|
36
|
+
→ SelectorTarget("div") → 子コンビネータ →
|
|
37
|
+
SelectorTarget(".class:not(.other)") → 子孫コンビネータ →
|
|
38
|
+
SelectorTarget("span")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
チェーンは AST から左から右に構築されますが、マッチングは右から左に行われます(現在の要素から開始)。
|
|
42
|
+
|
|
43
|
+
### 4. SelectorTarget マッチング
|
|
44
|
+
|
|
45
|
+
各 `SelectorTarget` は複合セレクタのコンポーネントを以下の順序でマッチングします:
|
|
46
|
+
|
|
47
|
+
1. **名前空間チェック** -- 存在する場合、要素の名前空間を検証(`svg` と `*` のみサポート)
|
|
48
|
+
2. **ID セレクタ**(`#id`)-- `el.id` にマッチ、詳細度 `[1, 0, 0]`
|
|
49
|
+
3. **タグセレクタ**(`div`)-- `el.localName` にマッチ(純粋な HTML 要素は大文字小文字非区別)、詳細度 `[0, 0, 1]`。ユニバーサルセレクタ(`*`)はタグ型として扱われますが、詳細度は加算されません。
|
|
50
|
+
4. **クラスセレクタ**(`.class`)-- `el.classList` にマッチ、詳細度 `[0, 1, 0]`
|
|
51
|
+
5. **属性セレクタ**(`[attr=val]`)-- 要素属性を演算子付きでマッチ、詳細度 `[0, 1, 0]`
|
|
52
|
+
6. **擬似クラス**(`:not()`、`:has()` 等)-- 専用ハンドラにディスパッチ
|
|
53
|
+
|
|
54
|
+
いずれかのコンポーネントがマッチに失敗すると、`SelectorTarget` 全体が失敗します(早期終了)。
|
|
55
|
+
|
|
56
|
+
### 5. コンビネータマッチング
|
|
57
|
+
|
|
58
|
+
`SelectorTarget` がマッチすると、`StructuredSelector` はコンビネータに従って次のターゲットへ進みます:
|
|
59
|
+
|
|
60
|
+
| コンビネータ | 記号 | DOM トラバーサル |
|
|
61
|
+
| ------------ | --------------- | ----------------------------------------- |
|
|
62
|
+
| 子孫 | ` `(スペース) | `parentElement` チェーンをたどる |
|
|
63
|
+
| 子 | `>` | 直接の `parentElement` を確認 |
|
|
64
|
+
| 隣接兄弟 | `+` | `previousElementSibling` を確認 |
|
|
65
|
+
| 一般兄弟 | `~` | `previousElementSibling` チェーンをたどる |
|
|
66
|
+
|
|
67
|
+
## 擬似クラスの処理
|
|
68
|
+
|
|
69
|
+
### 標準擬似クラス
|
|
70
|
+
|
|
71
|
+
| 擬似クラス | 動作 |
|
|
72
|
+
| ------------------ | ---------------------------------------------------------------------------- |
|
|
73
|
+
| `:not(selector)` | 内部セレクタがマッチしない場合にマッチ。詳細度は内部セレクタに等しい。 |
|
|
74
|
+
| `:is(selector)` | いずれかの内部セレクタがマッチすればマッチ。詳細度は最も高いマッチに等しい。 |
|
|
75
|
+
| `:where(selector)` | `:is()` と同じだが、常に `[0, 0, 0]` の詳細度。 |
|
|
76
|
+
| `:has(selector)` | 子孫(またはコンビネータ `+`/`~` で兄弟)がマッチすればマッチ。 |
|
|
77
|
+
| `:scope` | スコープ要素にマッチ(スコープなしの場合はルート)。詳細度 `[0, 1, 0]`。 |
|
|
78
|
+
| `:root` | `<html>` 要素にマッチ。詳細度 `[0, 1, 0]`。 |
|
|
79
|
+
|
|
80
|
+
### カスタム: `:closest(selector)`
|
|
81
|
+
|
|
82
|
+
祖先チェーンをたどり、いずれかの祖先が内部セレクタにマッチすればマッチします。これは W3C 仕様にない markuplint の拡張です。
|
|
83
|
+
|
|
84
|
+
### 拡張擬似クラス
|
|
85
|
+
|
|
86
|
+
拡張擬似クラスは `ExtendedPseudoClass` レジストリを通じてディスパッチされます:
|
|
87
|
+
|
|
88
|
+
#### `:aria(syntax)`
|
|
89
|
+
|
|
90
|
+
| 構文 | 動作 |
|
|
91
|
+
| ------------- | --------------------------------------------------- |
|
|
92
|
+
| `has name` | `getAccname(el)` が空でない文字列を返す場合にマッチ |
|
|
93
|
+
| `has no name` | `getAccname(el)` が空文字列を返す場合にマッチ |
|
|
94
|
+
|
|
95
|
+
バージョン構文をサポート: `:aria(has name|1.2)`(バージョンパラメータはパースされますが、フィルタリングにはまだ使用されていません)。
|
|
96
|
+
|
|
97
|
+
#### `:role(roleName)` / `:role(roleName|version)`
|
|
98
|
+
|
|
99
|
+
`getComputedRole(specs, el, version)` が返すロールの `name` が指定された `roleName` と一致する場合にマッチします。バージョンのデフォルトは `ARIA_RECOMMENDED_VERSION` です。
|
|
100
|
+
|
|
101
|
+
#### `:model(category)`
|
|
102
|
+
|
|
103
|
+
指定された HTML コンテンツモデルカテゴリに要素が属する場合にマッチします。`contentModelCategoryToTagNames()` を使用してカテゴリのマッチングセレクタリストを取得し、各セレクタを要素に対してテストします。
|
|
104
|
+
|
|
105
|
+
特殊ケース:
|
|
106
|
+
|
|
107
|
+
- `#custom` -- カスタム要素(`isCustomElement` プロパティを持つ要素)にマッチ
|
|
108
|
+
- `#text` -- 常にマッチしない(テキストノードは要素ではない)
|
|
109
|
+
|
|
110
|
+
## Regex セレクタマッチングフロー
|
|
111
|
+
|
|
112
|
+
### 1. エントリーポイント
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
matchSelector(el, regexSelector)
|
|
116
|
+
→ regexSelect(el, regexSelector)
|
|
117
|
+
→ combination リンクから SelectorTarget チェーンを構築
|
|
118
|
+
→ エッジ(最深の combination)からルートへ向かってマッチング
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 2. SelectorTarget チェーンの構築
|
|
122
|
+
|
|
123
|
+
`RegexSelector` 型はチェーンされた combination をサポートします:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
{
|
|
127
|
+
nodeName: "/^div$/",
|
|
128
|
+
combination: {
|
|
129
|
+
combinator: ">",
|
|
130
|
+
nodeName: "/^span$/",
|
|
131
|
+
combination: {
|
|
132
|
+
combinator: "+",
|
|
133
|
+
attrName: "/^data-/"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
これにより次のチェーンが構築されます: `SelectorTarget(div) → > → SelectorTarget(span) → + → SelectorTarget([data-*])`
|
|
140
|
+
|
|
141
|
+
### 3. パターンマッチング
|
|
142
|
+
|
|
143
|
+
`regexSelectorMatches(pattern, value, ignoreCase)` がパターンマッチングを処理します:
|
|
144
|
+
|
|
145
|
+
- **プレーン文字列**: `^pattern$` としてラップ(完全一致)
|
|
146
|
+
- **正規表現リテラル**(`/pattern/flags`): 指定されたフラグでそのまま使用
|
|
147
|
+
- **大文字小文字の区別**: HTML 要素は大文字小文字を区別しないマッチングを使用(`isPureHTMLElement()` の場合 `ignoreCase = true`)
|
|
148
|
+
|
|
149
|
+
### 4. Regex コンビネータ
|
|
150
|
+
|
|
151
|
+
標準 CSS コンビネータに加え、2 つの追加コンビネータをサポートします:
|
|
152
|
+
|
|
153
|
+
| コンビネータ | 記号 | DOM トラバーサル |
|
|
154
|
+
| ------------ | ----------- | ----------------------------------------- |
|
|
155
|
+
| 子孫 | `' '` | `parentElement` チェーンをたどる |
|
|
156
|
+
| 子 | `'>'` | 直接の `parentElement` を確認 |
|
|
157
|
+
| 隣接兄弟 | `'+'` | `previousElementSibling` を確認 |
|
|
158
|
+
| 一般兄弟 | `'~'` | `previousElementSibling` チェーンをたどる |
|
|
159
|
+
| 前方隣接兄弟 | `':has(+)'` | `nextElementSibling` を確認 |
|
|
160
|
+
| 前方一般兄弟 | `':has(~)'` | `nextElementSibling` チェーンをたどる |
|
|
161
|
+
|
|
162
|
+
### 5. データキャプチャ
|
|
163
|
+
|
|
164
|
+
マッチした正規表現キャプチャグループは `data` オブジェクトに収集されます:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// パターン: "/^(?<prefix>[a-z]+)-(?<suffix>[a-z]+)$/"
|
|
168
|
+
// 値: "data-value"
|
|
169
|
+
// 結果: { $0: "data-value", $1: "data", $2: "value", prefix: "data", suffix: "value" }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`nodeName` マッチングの `$0` キャプチャは削除されます(完全一致であり、要素名と冗長なため)。チェーン内の全ターゲットのデータはマージされます。
|
|
173
|
+
|
|
174
|
+
### 6. 詳細度の計算
|
|
175
|
+
|
|
176
|
+
Regex セレクタの詳細度はターゲットごとに計算されます:
|
|
177
|
+
|
|
178
|
+
- `nodeName` マッチ: `[0, 0, 1]`(タイプ詳細度)
|
|
179
|
+
- マッチした各属性: `[0, 1, 0]`(クラスレベル詳細度)
|
|
180
|
+
- 結合されたターゲットの詳細度は合算されます
|
|
181
|
+
|
|
182
|
+
## キャッシュ
|
|
183
|
+
|
|
184
|
+
`createSelector()` は `Map<string, Selector>` キャッシュを保持します。同じセレクタ文字列での後続の呼び出しは同じ `Selector` インスタンスを返し、`postcss-selector-parser` による繰り返しのパースを回避します。
|
|
185
|
+
|
|
186
|
+
## サポート対象・非対象セレクタ
|
|
187
|
+
|
|
188
|
+
### サポート対象
|
|
189
|
+
|
|
190
|
+
- ユニバーサル(`*`)、タイプ(`div`)、ID(`#id`)、クラス(`.class`)
|
|
191
|
+
- 全属性セレクタ演算子(`=`、`~=`、`|=`、`*=`、`^=`、`$=`、大文字小文字非区別 `i` フラグ)
|
|
192
|
+
- コンビネータ: 子孫(` `)、子(`>`)、隣接兄弟(`+`)、一般兄弟(`~`)
|
|
193
|
+
- 複数セレクタ(`,`)
|
|
194
|
+
- `:not()`、`:is()`、`:where()`、`:has()`、`:scope`、`:root`
|
|
195
|
+
- `:closest()`(markuplint 拡張)
|
|
196
|
+
- 拡張: `:aria()`、`:role()`、`:model()`
|
|
197
|
+
- 名前空間セレクタ(`svg|text`、`*|div`)。注: `svg` と `*` の名前空間のみサポート。他の名前空間(例: `html`)は `InvalidSelectorError` をスローします。
|
|
198
|
+
|
|
199
|
+
### 非サポート(エラーをスロー)
|
|
200
|
+
|
|
201
|
+
構造擬似クラス: `:empty`、`:nth-child()`、`:nth-last-child()`、`:first-child`、`:last-child`、`:only-child`、`:nth-of-type()`、`:nth-last-of-type()`、`:first-of-type`、`:last-of-type`、`:only-of-type`、`:nth-col()`、`:nth-last-col()`
|
|
202
|
+
|
|
203
|
+
入力擬似クラス: `:enable`、`:disable`、`:read-write`、`:read-only`、`:placeholder-shown`、`:default`、`:checked`、`:indeterminate`、`:valid`、`:invalid`、`:in-range`、`:out-of-range`、`:required`、`:optional`、`:blank`、`:user-invalid`
|
|
204
|
+
|
|
205
|
+
### 無視(エラーをスロー)
|
|
206
|
+
|
|
207
|
+
ユーザーインタラクション/動的擬似クラス: `:dir()`、`:lang()`、`:any-link`、`:link`、`:visited`、`:local-link`、`:target`、`:target-within`、`:current`、`:past`、`:future`、`:active`、`:hover`、`:focus`、`:focus-within`、`:focus-visible`
|
|
208
|
+
|
|
209
|
+
擬似要素: `::before`、`::after`
|
|
210
|
+
|
|
211
|
+
カラムコンビネータ: `||`
|