@markuplint/spec-generator 4.8.0 → 4.8.2

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,252 @@
1
+ # Module Reference
2
+
3
+ Detailed documentation for each source module in `@markuplint/spec-generator`.
4
+
5
+ ## index.ts
6
+
7
+ The main orchestrator. Exports the public API consumed by `@markuplint/html-spec/build.mjs`.
8
+
9
+ ### `main(options: Options): Promise<void>`
10
+
11
+ Coordinates three parallel data-gathering tasks, assembles the results into an `ExtendedSpec` object, and writes it as JSON.
12
+
13
+ **Flow:**
14
+
15
+ 1. Launch three tasks concurrently via `Promise.all`:
16
+ - `getElements(htmlFilePattern)` -- element specifications
17
+ - `getGlobalAttrs(commonAttrsFilePath)` -- global attribute definitions
18
+ - `getAria()` -- ARIA definitions
19
+ 2. Collect all fetched URLs via `getReferences()`
20
+ 3. Read content models via `readJson(commonContentsFilePath).models`
21
+ 4. Assemble the `ExtendedSpec` object:
22
+ ```typescript
23
+ {
24
+ cites: string[], // Sorted URL list
25
+ def: {
26
+ "#globalAttrs": { ... }, // Global attribute categories
27
+ "#aria": { ... }, // ARIA data per version
28
+ "#contentModels": { ... } // Content model categories
29
+ },
30
+ specs: ExtendedElementSpec[] // Element specifications
31
+ }
32
+ ```
33
+ 5. Write the JSON to `outputFilePath`
34
+
35
+ ### `Options` type
36
+
37
+ | Field | Type | Description |
38
+ | ------------------------ | -------- | ------------------------------------------------- |
39
+ | `outputFilePath` | `string` | Absolute path where the generated JSON is written |
40
+ | `htmlFilePattern` | `string` | Absolute glob pattern for per-element JSON files |
41
+ | `commonAttrsFilePath` | `string` | Absolute path to global attributes JSON |
42
+ | `commonContentsFilePath` | `string` | Absolute path to content models JSON |
43
+
44
+ ---
45
+
46
+ ## html-elements.ts
47
+
48
+ Builds the complete list of HTML and SVG element specifications.
49
+
50
+ ### `getElements(filePattern: string): Promise<ExtendedElementSpec[]>`
51
+
52
+ **Flow:**
53
+
54
+ 1. Read all spec files matching the glob pattern via `readJsons()`. Element names are extracted from filenames using the regex `spec.([\w-]+).json` (e.g., `spec.a.json` becomes `a`)
55
+ 2. Fetch the deprecated SVG element list via `getSVGElementList()`
56
+ 3. Generate stubs for obsolete elements not already present via `fetchObsoleteElements()`
57
+ 4. For each element, construct the MDN URL and scrape metadata via `fetchHTMLElement()`:
58
+ - Heading elements (`h1`-`h6`) are mapped to MDN path `Heading_Elements`
59
+ - SVG elements use the path `/Web/SVG/Reference/Element/<name>`
60
+ - HTML elements use `/Web/HTML/Reference/Elements/<name>`
61
+ 5. Merge scraped data with local spec data. **Local spec data takes precedence**:
62
+ - `cite` -- local value wins if present, otherwise MDN URL
63
+ - `description`, `categories`, `omission` -- from MDN
64
+ - `contentModel`, `aria` -- from local spec only (never scraped)
65
+ - `attributes` -- merged per attribute name; local entries override MDN entries
66
+ 6. Sort alphabetically, with SVG elements placed after HTML elements
67
+
68
+ ### `obsoleteList`
69
+
70
+ A hardcoded list of 31 non-conforming HTML elements:
71
+
72
+ `applet`, `acronym`, `bgsound`, `dir`, `frame`, `frameset`, `noframes`, `isindex`, `keygen`, `listing`, `menuitem`, `nextid`, `noembed`, `param`, `plaintext`, `rb`, `rtc`, `strike`, `xmp`, `basefont`, `big`, `blink`, `center`, `font`, `marquee`, `multicol`, `nobr`, `spacer`, `tt`
73
+
74
+ These are combined with deprecated SVG elements fetched from MDN to form the complete obsolete set.
75
+
76
+ ---
77
+
78
+ ## scraping.ts
79
+
80
+ Scrapes MDN element reference pages for metadata. See [Scraping Details](scraping.md) for CSS selectors and fragile points.
81
+
82
+ ### `fetchHTMLElement(link: string): Promise<ExtendedElementSpec>`
83
+
84
+ Scrapes a single MDN element page and returns an element spec object containing:
85
+
86
+ - `description` -- from `.reference-layout__header .content-section`
87
+ - Compatibility flags (`experimental`, `obsolete`, `deprecated`, `nonStandard`) -- from the browser compatibility table or fallback indicators
88
+ - `categories` -- parsed from the "Content categories" row in the technical summary table
89
+ - `attributes` -- from definition lists in sections identified by `aria-labelledby` IDs: `attributes`, `deprecated_attributes`, `individual_attributes`, `non-standard_attributes`, `obsolete_attributes`
90
+
91
+ ### `fetchObsoleteElements(obsoleteList, specs): ExtendedElementSpec[]`
92
+
93
+ Generates minimal spec stubs for obsolete elements not already present in the existing specs array. Each stub has:
94
+
95
+ - `cite` pointing to the HTML spec obsolete features section
96
+ - `obsolete: true`
97
+ - `contents: true` (any content allowed)
98
+ - `permittedRoles: true`, `implicitRole: false`
99
+
100
+ ### Private helpers
101
+
102
+ - `getProperty($, prop)` -- Extracts a value from the MDN technical summary table (`#technical_summary ~ figure.table-container > table`)
103
+ - `getAttributes($, id)` -- Parses `<dt>`/`<dd>` pairs from a `.content-section[aria-labelledby="<id>"]` section
104
+ - `getItsHeading($start)` -- Traverses DOM upward to find the nearest preceding heading
105
+ - `upToPrevOrParent($start)` -- Moves to previous sibling or parent
106
+ - `isHeading($el)` -- Tests if an element is `<h1>` through `<h6>`
107
+
108
+ ---
109
+
110
+ ## aria.ts
111
+
112
+ Scrapes W3C ARIA specifications for role and property definitions. See [Scraping Details](scraping.md) for URL patterns and selectors.
113
+
114
+ ### `getAria(): Promise<Record<ARIAVersion, { roles, props, graphicsRoles }>>`
115
+
116
+ Returns ARIA data for all three supported versions. For each version:
117
+
118
+ 1. Fetch roles via `getRoles(version)`
119
+ 2. Fetch properties/states via `getProps(version, roles)`
120
+ 3. Fetch graphics ARIA roles via `getRoles(version, true)`
121
+
122
+ **Execution order:** Versions are processed sequentially (1.3, then 1.2, then 1.1). Within each version, roles must be fetched before properties (properties are discovered from the roles' `ownedProperties`).
123
+
124
+ ### URL mapping
125
+
126
+ | Version | ARIA Spec URL | Graphics ARIA URL |
127
+ | ------- | ------------------------------------- | ------------------------------------------ |
128
+ | 1.1 | `https://www.w3.org/TR/wai-aria-1.1/` | `https://www.w3.org/TR/graphics-aria-1.0/` |
129
+ | 1.2 | `https://www.w3.org/TR/wai-aria-1.2/` | `https://w3c.github.io/graphics-aria/` |
130
+ | 1.3 | `https://w3c.github.io/aria/` | `https://w3c.github.io/graphics-aria/` |
131
+
132
+ ### Private functions
133
+
134
+ - `getRoles(version, graphicsAria?)` -- Scrapes `#role_definitions section.role` elements. Extracts: name, description, generalization, owned properties (required/inherited/general), required context roles, required owned elements, accessible name settings, children presentational flag, prohibited properties. Handles role synonyms (`none`/`presentation`, `image`/`img`)
135
+ - `getProps(version, roles)` -- Builds a property list from all role `ownedProperties`, then scrapes each property's section for: type (property/state), value type, enum values, default value, global flag, equivalent HTML attributes. Applies conditional value overrides for `aria-checked` and `aria-hidden`
136
+ - `getAriaInHtml()` -- Scrapes `https://www.w3.org/TR/html-aria/` for the HTML attribute to ARIA property mapping table. Skips `contenteditable` (requires ancestor evaluation)
137
+ - `$$(el, selectors)` -- Tries multiple CSS selectors and returns the first non-empty match
138
+
139
+ ---
140
+
141
+ ## fetch.ts
142
+
143
+ HTTP fetching layer with caching and progress display.
144
+
145
+ ### Caching
146
+
147
+ Two in-memory `Map` caches:
148
+
149
+ | Cache | Key | Value | Purpose |
150
+ | ---------- | --- | --------------------- | ------------------------------- |
151
+ | `cache` | URL | Raw HTML string | Avoids re-fetching the same URL |
152
+ | `domCache` | URL | `CheerioAPI` instance | Avoids re-parsing the same HTML |
153
+
154
+ Caches are process-scoped. There is no persistence between builds.
155
+
156
+ ### `fetch(url: string): Promise<CheerioAPI>`
157
+
158
+ Returns a parsed Cheerio DOM instance. Checks `domCache` first, then delegates to `fetchText()` for the raw HTML.
159
+
160
+ ### `fetchText(url: string): Promise<string>`
161
+
162
+ Fetches the raw text content of a URL using `globalThis.fetch()`. On failure (any exception), caches and returns an empty string. Updates the CLI progress bar on each call.
163
+
164
+ ### `getReferences(): string[]`
165
+
166
+ Finalizes the progress bar and returns a sorted list of all fetched URLs. Called once after all scraping is complete.
167
+
168
+ ### Progress bar
169
+
170
+ Uses `cli-progress` with the `shades_grey` preset. The bar starts at module load time and is updated with each fetch call. Format:
171
+
172
+ ```
173
+ 🔎 Fetch references... ████░░░░ 45% | ETA: 30s | 90/200 🔗 https://develo...ments/div
174
+ ```
175
+
176
+ ---
177
+
178
+ ## read-json.ts
179
+
180
+ JSON file reading with comment support.
181
+
182
+ ### `readJson<T>(filePath: string): T`
183
+
184
+ Reads a single JSON file. Uses `strip-json-comments` to remove `//` and `/* */` comments before parsing. Throws if the path is not absolute.
185
+
186
+ ### `readJsons<T>(pattern: string, hook?): Promise<T[]>`
187
+
188
+ Reads all JSON files matching an absolute glob pattern. Optionally transforms each result via the `hook` function (receives filename and parsed body). All files are read in parallel via `Promise.all`.
189
+
190
+ ---
191
+
192
+ ## global-attrs.ts
193
+
194
+ ### `getGlobalAttrs(filePath: string): SpecDefs["#globalAttrs"]`
195
+
196
+ A thin wrapper around `readJson()` that reads and returns the global attributes definition from the specified JSON file.
197
+
198
+ ---
199
+
200
+ ## svg.ts
201
+
202
+ ### `getSVGElementList(): Promise<string[]>`
203
+
204
+ Fetches the MDN SVG element index page (`https://developer.mozilla.org/en-US/docs/Web/SVG/Element`) and extracts deprecated/obsolete SVG element names from the "Obsolete and deprecated elements" section.
205
+
206
+ **Processing:**
207
+
208
+ 1. Unwrap all `<section>` wrappers (replace with their children)
209
+ 2. Find the `#obsolete_and_deprecated_elements` heading
210
+ 3. Collect siblings until the next `<h2>` via `getThisOutline()`
211
+ 4. Extract element names from `<a>` tags, prefixed with `svg_`
212
+
213
+ Returns names like `["svg_altGlyph", "svg_altGlyphDef", ...]`.
214
+
215
+ ---
216
+
217
+ ## utils.ts
218
+
219
+ Shared helper functions used across multiple modules.
220
+
221
+ ### `nameCompare(a, b): number`
222
+
223
+ Case-insensitive comparison by `name` property (or string value). Used as a sort comparator throughout the codebase.
224
+
225
+ ### `sortObjectByKey<T>(o: T): T`
226
+
227
+ Returns a new object with the same key-value pairs sorted alphabetically by key (using `nameCompare`).
228
+
229
+ ### `arrayUnique<T extends { name: string }>(array: T[]): T[]`
230
+
231
+ Removes duplicate items based on their `name` property, keeping the first occurrence.
232
+
233
+ ### `getThisOutline($, $start): Cheerio<Element>`
234
+
235
+ Collects all sibling elements after `$start` until the next `<h2>` heading, wrapping them in a container `<div>`. Used by `svg.ts` to extract a section of content defined by a heading.
236
+
237
+ ### `mergeAttributes<T>(fromDocs: T, fromJSON: T): T`
238
+
239
+ Shallow-merges two attribute objects with `fromJSON` values taking precedence.
240
+
241
+ ### `keys<T, K>(object: T): K[]`
242
+
243
+ Returns `Object.keys()` with a custom type cast.
244
+
245
+ ### `getName(origin: string): { localName, namespace?, ml }`
246
+
247
+ Parses an element name string:
248
+
249
+ | Input | `localName` | `namespace` | `ml` |
250
+ | -------------- | ----------- | ------------------------------ | -------- |
251
+ | `"div"` | `"div"` | `undefined` | `"HTML"` |
252
+ | `"svg_circle"` | `"circle"` | `"http://www.w3.org/2000/svg"` | `"SVG"` |
@@ -0,0 +1,320 @@
1
+ # スクレイピング詳細
2
+
3
+ このドキュメントでは、`@markuplint/spec-generator` が使用するウェブスクレイピングの対象、CSS セレクタ、キャッシュ戦略、エラー処理について説明します。ビルドはネットワーク依存で、MDN および W3C 仕様に対して 200 以上の HTTP リクエストを発行します。
4
+
5
+ ## MDN 要素スクレイピング
6
+
7
+ **モジュール:** `scraping.ts`
8
+
9
+ ### URL パターン
10
+
11
+ HTML 要素:
12
+
13
+ ```
14
+ https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/<name>
15
+ ```
16
+
17
+ SVG 要素:
18
+
19
+ ```
20
+ https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/<name>
21
+ ```
22
+
23
+ **特殊ケース:** 見出し要素(`h1`-`h6`)は単一のページにマッピング:
24
+
25
+ ```
26
+ https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/Heading_Elements
27
+ ```
28
+
29
+ ### 抽出データ
30
+
31
+ 各要素について `fetchHTMLElement()` が抽出する項目:
32
+
33
+ | データ | セレクタ / メソッド | 備考 |
34
+ | -------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
35
+ | 説明 | `main#content .reference-layout__header .content-section` | テキストコンテンツ、空白正規化済み |
36
+ | 互換性 | `.bc-table tbody tr:first-child th`(アイコン) | BC テーブルが利用不可の場合は notecard ベースのインジケータにフォールバック |
37
+ | カテゴリ | `#technical_summary ~ figure.table-container > table`(「Content categories」) | 既知のカテゴリキーワードとマッチング |
38
+ | 属性 | `.content-section[aria-labelledby="<id>"] > dl > dt` | 複数セクションの定義リストからパース |
39
+
40
+ ### 互換性フラグの検出
41
+
42
+ ブラウザ互換性テーブルの有無に応じて2つの戦略を使用:
43
+
44
+ **戦略 1: ブラウザ互換性テーブル**(最初の行の `<code>` が要素名と一致する場合)
45
+
46
+ | フラグ | `tbody tr:first-child th` 内のセレクタ |
47
+ | -------------- | -------------------------------------- |
48
+ | `experimental` | `.ic-experimental` |
49
+ | `obsolete` | `.ic-obsolete` |
50
+ | `deprecated` | `.ic-deprecated` |
51
+ | `nonStandard` | `.ic-non-standard` |
52
+
53
+ **戦略 2: フォールバックインジケータ**(BC テーブルがない、またはマッチしない場合)
54
+
55
+ | フラグ | セレクタ |
56
+ | -------------- | -------------------------------------------------------------------------------------------------------- |
57
+ | `experimental` | `.blockIndicator.experimental` または `> div .notecard.experimental` |
58
+ | `obsolete` | `.obsoleteHeader` または `h1` テキストに "obsolete" を含む または `> div:first-child .notecard.obsolete` |
59
+ | `deprecated` | `.deprecatedHeader` または `> div:first-child .notecard.deprecated` または `h1 + * .notecard.deprecated` |
60
+ | `nonStandard` | `.nonStandardHeader` または `h4#Non-standard` |
61
+
62
+ ### コンテンツカテゴリのパース
63
+
64
+ 「Content categories」プロパティは技術サマリテーブルから抽出されます。テキストは以下のキーワードとマッチング(大文字小文字不区別):
65
+
66
+ | キーワード | カテゴリ |
67
+ | --------------------- | -------------------- |
68
+ | `metadata content` | `#metadata` |
69
+ | `flow content` | `#flow` |
70
+ | `sectioning content` | `#sectioning` |
71
+ | `heading content` | `#heading` |
72
+ | `phrasing content` | `#phrasing` |
73
+ | `embedded content` | `#embedded` |
74
+ | `interactive content` | `#interactive` |
75
+ | `palpable content` | `#palpable` |
76
+ | `script-supporting` | `#script-supporting` |
77
+
78
+ ### 属性の抽出
79
+
80
+ 属性は `aria-labelledby` ID で識別される最大5つのセクションから抽出:
81
+
82
+ | セクション ID | 適用されるステータスフラグ |
83
+ | ------------------------- | ------------------------------ |
84
+ | `attributes` | 属性ごとのアイコンからのフラグ |
85
+ | `deprecated_attributes` | 見出しから `deprecated: true` |
86
+ | `individual_attributes` | 属性ごとのアイコンからのフラグ |
87
+ | `non-standard_attributes` | 属性ごとのアイコンからのフラグ |
88
+ | `obsolete_attributes` | 見出しから `obsolete: true` |
89
+
90
+ 各 `<dt>` エントリについて:
91
+
92
+ 1. `<code>` テキストから属性名を抽出
93
+ 2. 次の `<dd>` 兄弟要素から説明を抽出
94
+ 3. アイコンクラスからステータスフラグを検出:
95
+ - `.icon-beaker`, `.icon.experimental`, `.icon.icon-experimental` -- experimental
96
+ - `.icon-trash`, `.icon.obsolete`, `.icon.icon-obsolete`, `.obsolete` -- obsolete
97
+ - `.icon-thumbs-down-alt`, `.icon.deprecated`, `.icon.icon-deprecated` -- deprecated
98
+ - `.icon-warning-sign`, `.icon.non-standard`, `.icon.icon-nonstandard` -- non-standard
99
+ 4. 見出しコンテキスト(`getItsHeading()`)でセクションレベルのフラグを確認
100
+
101
+ 抽出した全属性はマージされ、キーでソートされます。
102
+
103
+ ---
104
+
105
+ ## MDN SVG インデックススクレイピング
106
+
107
+ **モジュール:** `svg.ts`
108
+
109
+ ### 対象
110
+
111
+ ```
112
+ https://developer.mozilla.org/en-US/docs/Web/SVG/Element
113
+ ```
114
+
115
+ ### 抽出プロセス
116
+
117
+ 1. すべての `<section>` 要素をアンラップ(子要素で置換)してドキュメント構造をフラット化
118
+ 2. `id="obsolete_and_deprecated_elements"` の見出しを検索
119
+ 3. `getThisOutline()` で次の `<h2>` まで全兄弟要素を収集
120
+ 4. `div > a` 要素から要素名を抽出し、山括弧を除去
121
+ 5. 各名前に `svg_` プレフィックスを付加(例: `altGlyph` → `svg_altGlyph`)
122
+
123
+ ---
124
+
125
+ ## WAI-ARIA スクレイピング
126
+
127
+ **モジュール:** `aria.ts`
128
+
129
+ ### 仕様 URL
130
+
131
+ | バージョン | URL | ステータス |
132
+ | ---------- | ------------------------------------- | ---------------------- |
133
+ | 1.1 | `https://www.w3.org/TR/wai-aria-1.1/` | 勧告(Recommendation) |
134
+ | 1.2 | `https://www.w3.org/TR/wai-aria-1.2/` | 勧告(Recommendation) |
135
+ | 1.3 | `https://w3c.github.io/aria/` | 草案(Working Draft) |
136
+
137
+ ### ロールの抽出
138
+
139
+ **セレクタ:** `#role_definitions section.role`
140
+
141
+ 各ロールセクションについて:
142
+
143
+ | データ | セレクタ |
144
+ | -------------------------------- | ---------------------------------------------------- |
145
+ | 名前 | `.role-name[title]` |
146
+ | 説明 | `.role-description p`(`\n\n` で結合) |
147
+ | 抽象かどうか | `.role-abstract` テキストが "true" |
148
+ | 汎化 | `.role-parent a` |
149
+ | 必須プロパティ | `.role-required-properties li`(親にフォールバック) |
150
+ | 継承プロパティ | `.role-inherited li` |
151
+ | 所有プロパティ | `.role-properties li` または `.role-properties > a` |
152
+ | 必須コンテキストロール | `.role-scope li` または `.role-scope a` |
153
+ | 必須所有要素 | `.role-mustcontain li` または `.role-mustcontain a` |
154
+ | アクセシブル名必須 | `.role-namerequired` に "true" を含む |
155
+ | アクセシブル名(著者から) | `.role-namefrom` に "author" を含む |
156
+ | アクセシブル名(コンテンツから) | `.role-namefrom` に "content" を含む |
157
+ | アクセシブル名禁止 | `.role-namefrom` に "prohibited" を含む |
158
+ | 子の表示 | `.role-childpresentational` "true"/"false" |
159
+ | 禁止プロパティ | `.role-disallowed li code` |
160
+
161
+ **ロール同義語の処理:**
162
+
163
+ - ARIA 1.1/1.2: `none` は `presentation` からプロパティを継承
164
+ - ARIA 1.3: `presentation` は `none` から継承; `img` は `image` から継承
165
+
166
+ ### プロパティ/ステートの抽出
167
+
168
+ プロパティは、スクレイピングした全ロールの `ownedProperties` から検出されます。各プロパティについて:
169
+
170
+ **セレクタベース:** `#<property-name>`(例: `#aria-label`)
171
+
172
+ | データ | セレクタ |
173
+ | ------------------ | ---------------------------------------------------------------------------------------------- |
174
+ | 型 | セクションクラス: `/property/i` にマッチ → `"property"`、それ以外 → `"state"` |
175
+ | 非推奨 | セクションクラスに "deprecated" を含む |
176
+ | 値の型 | `table .${type}-value` または `table .property-value` または `.state-features .property-value` |
177
+ | 値の説明 | `table:is(.value-descriptions, .def:has(.value-description)) tbody tr` |
178
+ | 列挙値 | `.value-name` 要素から(`token` または `token list` 値型の場合のみ) |
179
+ | デフォルト値 | `.value-name .default` テキスト |
180
+ | グローバルかどうか | `#global_states li a` にリストされているか |
181
+
182
+ **条件付き値のオーバーライド:**
183
+
184
+ - `aria-checked`: 値を `"true/false"` に設定し、`checkbox` と `menuitemcheckbox` ロールに対して条件付きで `"tristate"` を追加
185
+ - `aria-hidden`: `hidden` HTML 属性の同等物を `isNotStrictEquivalent` としてマーク
186
+
187
+ ### グローバルステート/プロパティ
188
+
189
+ グローバル ARIA 属性は `#global_states li` 下の全 `<a>` リンクを収集して識別します。各リンクのハッシュフラグメントがプロパティ名として使用されます。
190
+
191
+ ---
192
+
193
+ ## Graphics ARIA スクレイピング
194
+
195
+ **モジュール:** `aria.ts`
196
+
197
+ Graphics ARIA ロールは、同じ `getRoles()` 関数に `graphicsAria = true` を渡してフェッチします。
198
+
199
+ | バージョン | URL |
200
+ | ---------- | ------------------------------------------ |
201
+ | 1.1 | `https://www.w3.org/TR/graphics-aria-1.0/` |
202
+ | 1.2 | `https://w3c.github.io/graphics-aria/` |
203
+ | 1.3 | `https://w3c.github.io/graphics-aria/` |
204
+
205
+ 標準 ARIA ロールと同じ CSS セレクタが Graphics ARIA ロールにも適用されます。
206
+
207
+ ---
208
+
209
+ ## HTML-ARIA マッピング
210
+
211
+ **モジュール:** `aria.ts`(`getAriaInHtml()`)
212
+
213
+ ### 対象
214
+
215
+ ```
216
+ https://www.w3.org/TR/html-aria/
217
+ ```
218
+
219
+ ### セレクタ
220
+
221
+ ```
222
+ #requirements-for-use-of-aria-attributes-in-place-of-equivalent-html-attributes table tbody tr
223
+ ```
224
+
225
+ 各行について:
226
+
227
+ - HTML 属性名: `th:nth-of-type(1) a`(最初のリンクテキスト)
228
+ - 暗黙の ARIA プロパティ: `td:nth-of-type(1) code`(最初のコード要素テキスト)
229
+ - プロパティ文字列を `=` で分割して ARIA プロパティ名と値を取得
230
+
231
+ **スキップ:** `contenteditable` 属性は祖先の評価が必要なため除外されます。
232
+
233
+ ---
234
+
235
+ ## キャッシュ
236
+
237
+ ### プロセス内キャッシュ
238
+
239
+ `fetch.ts` に2つの `Map` キャッシュが存在:
240
+
241
+ | キャッシュ | キー | 値 | スコープ |
242
+ | ---------- | ---- | ---------------- | -------------------------------------- |
243
+ | `cache` | URL | 生の HTML 文字列 | 単一ビルド実行(プロセスの存続期間中) |
244
+ | `domCache` | URL | `CheerioAPI` | 単一ビルド実行(プロセスの存続期間中) |
245
+
246
+ - 同じ URL は単一ビルド内で二度フェッチされない
247
+ - 失敗したフェッチは空文字列としてキャッシュされ、リトライを防止
248
+ - **ビルド間の永続化はなし** -- `yarn up:gen` を実行するたびに全 URL を新たにフェッチ
249
+
250
+ ### 失敗時のキャッシュ動作
251
+
252
+ `globalThis.fetch()` が例外をスローした場合:
253
+
254
+ 1. その URL に対して空文字列がキャッシュされる
255
+ 2. ビルドは継続(中断しない)
256
+ 3. ページのフェッチに失敗した要素は空/欠落したメタデータを持つ
257
+
258
+ ---
259
+
260
+ ## エラー処理
261
+
262
+ | シナリオ | 動作 |
263
+ | ---------------------- | ------------------------------------------------------------------------------------ |
264
+ | HTTP フェッチ失敗 | 空文字列をキャッシュ、ビルド継続、メタデータは空 |
265
+ | DOM 要素が見つからない | Cheerio は空の選択を返し、フィールドはデフォルトで空になる |
266
+ | MDN ページ構造変更 | CSS セレクタがサイレントに失敗、影響を受けた要素のデータが失われる |
267
+ | W3C 仕様 URL 変更 | フェッチがエラーページの HTML を返し、スクレイピングがゴミを抽出するか何も取得しない |
268
+
269
+ ジェネレータはスクレイピングしたデータを期待される構造に対してバリデーションしません。不正確または欠落したデータはサイレントに `index.json` に伝播します。
270
+
271
+ ---
272
+
273
+ ## 既知の脆弱ポイント
274
+
275
+ 以下の CSS セレクタは上流のページ構造変更に敏感です:
276
+
277
+ ### MDN ページ
278
+
279
+ | セレクタ | 用途 | リスクレベル |
280
+ | --------------------------------------------------------------- | ------------------- | ------------ |
281
+ | `main#content` | メイン記事 | 低 |
282
+ | `.reference-layout__header .content-section` | 説明 | 中 |
283
+ | `.bc-table tbody tr:first-child th` | 互換性フラグ | 中 |
284
+ | `#technical_summary ~ figure.table-container > table` | 技術サマリ | 高 |
285
+ | `.content-section[aria-labelledby="attributes"]` | 属性セクション | 中 |
286
+ | `.icon-beaker`, `.icon.experimental`, `.icon.icon-experimental` | experimental フラグ | 高 |
287
+ | `.icon-trash`, `.icon.obsolete`, `.icon.icon-obsolete` | obsolete フラグ | 高 |
288
+
289
+ ### W3C ARIA 仕様ページ
290
+
291
+ | セレクタ | 用途 | リスクレベル |
292
+ | -------------------------------- | -------------------- | ------------ |
293
+ | `#role_definitions section.role` | ロールセクション | 低 |
294
+ | `.role-name[title]` | ロール名 | 低 |
295
+ | `.role-required-properties li` | 必須プロパティ | 低 |
296
+ | `.role-properties li` | 所有プロパティ | 低 |
297
+ | `#global_states li a` | グローバルプロパティ | 低 |
298
+
299
+ W3C 仕様はより構造化されたクラス名を使用しており、MDN セレクタよりも変更される可能性は低いです。
300
+
301
+ ---
302
+
303
+ ## スクレイピング失敗の診断
304
+
305
+ `yarn up:gen` 実行後、`index.json` の差分を確認:
306
+
307
+ ```bash
308
+ git diff packages/@markuplint/html-spec/index.json
309
+ ```
310
+
311
+ **スクレイピング失敗の兆候:**
312
+
313
+ - **大量のデータ消失** -- `index.json` から仕様データの大きな部分が消失。これはほぼ確実にスクレイピングの失敗を示しており、実際の仕様変更ではない
314
+ - **空の説明** -- 複数の要素で突然 `description` フィールドが空になる
315
+ - **属性の欠落** -- 以前存在していた属性が消失
316
+ - **空の ARIA データ** -- ロールやプロパティの定義が空、または大幅に減少
317
+
318
+ **根本原因:** 参照サイト(MDN、W3C)の HTML 構造、情報の記述方法、要素の ID/クラスが変更された。
319
+
320
+ **対処法:** 実際のページ構造を調べてどのモジュールの CSS セレクタが壊れているかを特定し、`scraping.ts` または `aria.ts` のセレクタを新しい構造に合わせて更新する。`yarn up:gen` を再実行して確認。