@markuplint/types 4.8.1 → 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.
Files changed (116) hide show
  1. package/ARCHITECTURE.ja.md +256 -0
  2. package/ARCHITECTURE.md +256 -0
  3. package/CHANGELOG.md +18 -2
  4. package/README.md +37 -2
  5. package/SKILL.md +100 -0
  6. package/docs/check-pipeline.ja.md +494 -0
  7. package/docs/check-pipeline.md +494 -0
  8. package/docs/token-system.ja.md +584 -0
  9. package/docs/token-system.md +584 -0
  10. package/docs/type-system.ja.md +556 -0
  11. package/docs/type-system.md +556 -0
  12. package/docs/validators.ja.md +524 -0
  13. package/docs/validators.md +524 -0
  14. package/lib/check-base.d.ts +81 -1
  15. package/lib/check-base.js +87 -0
  16. package/lib/check-multi-types.d.ts +12 -1
  17. package/lib/check-multi-types.js +11 -0
  18. package/lib/check.d.ts +13 -0
  19. package/lib/check.js +13 -0
  20. package/lib/css-defs.d.ts +8 -0
  21. package/lib/css-defs.js +8 -0
  22. package/lib/css-overrides.d.ts +7 -0
  23. package/lib/css-overrides.js +7 -0
  24. package/lib/css-syntax.d.ts +11 -0
  25. package/lib/css-syntax.js +12 -1
  26. package/lib/css-tokenizers.d.ts +6 -0
  27. package/lib/css-tokenizers.js +6 -0
  28. package/lib/debug.d.ts +3 -0
  29. package/lib/debug.js +3 -0
  30. package/lib/defs.d.ts +8 -0
  31. package/lib/defs.js +26 -0
  32. package/lib/directive.d.ts +14 -0
  33. package/lib/directive.js +14 -0
  34. package/lib/enum.d.ts +11 -0
  35. package/lib/enum.js +11 -0
  36. package/lib/get-candidate.d.ts +11 -0
  37. package/lib/get-candidate.js +11 -0
  38. package/lib/index.d.ts +10 -1
  39. package/lib/index.js +9 -1
  40. package/lib/keyword-type.d.ts +13 -0
  41. package/lib/keyword-type.js +13 -0
  42. package/lib/list.d.ts +13 -0
  43. package/lib/list.js +13 -0
  44. package/lib/match-result.d.ts +22 -1
  45. package/lib/match-result.js +21 -0
  46. package/lib/number.d.ts +12 -0
  47. package/lib/number.js +12 -0
  48. package/lib/pattern.d.ts +12 -0
  49. package/lib/pattern.js +33 -0
  50. package/lib/primitive/is-float.d.ts +4 -1
  51. package/lib/primitive/is-float.js +4 -1
  52. package/lib/primitive/is-int.d.ts +4 -1
  53. package/lib/primitive/is-int.js +4 -1
  54. package/lib/primitive/is-non-zero-uint.d.ts +3 -2
  55. package/lib/primitive/is-non-zero-uint.js +3 -2
  56. package/lib/primitive/is-quantity.d.ts +5 -3
  57. package/lib/primitive/is-quantity.js +5 -3
  58. package/lib/primitive/is-uint.d.ts +5 -1
  59. package/lib/primitive/is-uint.js +5 -1
  60. package/lib/primitive/range.d.ts +5 -4
  61. package/lib/primitive/range.js +5 -4
  62. package/lib/primitive/split-unit.d.ts +3 -2
  63. package/lib/primitive/split-unit.js +3 -2
  64. package/lib/rfc/is-bcp-47.d.ts +2 -0
  65. package/lib/rfc/is-bcp-47.js +4 -2
  66. package/lib/token/token-collection.d.ts +108 -3
  67. package/lib/token/token-collection.js +110 -3
  68. package/lib/token/token.d.ts +66 -2
  69. package/lib/token/token.js +89 -21
  70. package/lib/token/types.d.ts +9 -0
  71. package/lib/types.d.ts +108 -1
  72. package/lib/types.schema.d.ts +4 -1
  73. package/lib/w3c/check-serialized-permissions-policy.d.ts +2 -0
  74. package/lib/w3c/check-serialized-permissions-policy.js +2 -0
  75. package/lib/whatwg/check-autocomplete.d.ts +10 -0
  76. package/lib/whatwg/check-autocomplete.js +214 -159
  77. package/lib/whatwg/check-datetime/date-string.d.ts +2 -0
  78. package/lib/whatwg/check-datetime/date-string.js +2 -0
  79. package/lib/whatwg/check-datetime/datetime-tokens.d.ts +13 -0
  80. package/lib/whatwg/check-datetime/datetime-tokens.js +14 -1
  81. package/lib/whatwg/check-datetime/duration-string.d.ts +7 -0
  82. package/lib/whatwg/check-datetime/duration-string.js +7 -0
  83. package/lib/whatwg/check-datetime/global-date-and-time-string.d.ts +2 -0
  84. package/lib/whatwg/check-datetime/global-date-and-time-string.js +2 -0
  85. package/lib/whatwg/check-datetime/index.d.ts +5 -0
  86. package/lib/whatwg/check-datetime/index.js +5 -0
  87. package/lib/whatwg/check-datetime/local-date-and-time-string.d.ts +4 -0
  88. package/lib/whatwg/check-datetime/local-date-and-time-string.js +4 -0
  89. package/lib/whatwg/check-datetime/month-string.d.ts +2 -0
  90. package/lib/whatwg/check-datetime/month-string.js +2 -0
  91. package/lib/whatwg/check-datetime/time-string.d.ts +2 -0
  92. package/lib/whatwg/check-datetime/time-string.js +2 -0
  93. package/lib/whatwg/check-datetime/time-zone-offset-string.d.ts +8 -0
  94. package/lib/whatwg/check-datetime/time-zone-offset-string.js +8 -0
  95. package/lib/whatwg/check-datetime/week-string.d.ts +2 -0
  96. package/lib/whatwg/check-datetime/week-string.js +2 -0
  97. package/lib/whatwg/check-datetime/year-string.d.ts +2 -0
  98. package/lib/whatwg/check-datetime/year-string.js +2 -0
  99. package/lib/whatwg/check-datetime/yearless-date-string.d.ts +2 -0
  100. package/lib/whatwg/check-datetime/yearless-date-string.js +2 -0
  101. package/lib/whatwg/check-link-type.d.ts +108 -1
  102. package/lib/whatwg/check-link-type.js +17 -7
  103. package/lib/whatwg/check-mime-type.d.ts +4 -1
  104. package/lib/whatwg/check-mime-type.js +6 -3
  105. package/lib/whatwg/is-abs-url.d.ts +2 -0
  106. package/lib/whatwg/is-abs-url.js +3 -4
  107. package/lib/whatwg/is-browser-context-name.d.ts +2 -2
  108. package/lib/whatwg/is-browser-context-name.js +2 -2
  109. package/lib/whatwg/is-custom-element-name.d.ts +1 -1
  110. package/lib/whatwg/is-custom-element-name.js +1 -1
  111. package/lib/whatwg/is-itemprop-name.d.ts +1 -0
  112. package/lib/whatwg/is-itemprop-name.js +1 -0
  113. package/lib/whatwg/is-navigable-target-name.d.ts +2 -0
  114. package/lib/whatwg/is-navigable-target-name.js +2 -0
  115. package/package.json +8 -6
  116. package/types.schema.json +10 -1
package/SKILL.md ADDED
@@ -0,0 +1,100 @@
1
+ ---
2
+ description: Verify documentation claims against web standards and source code
3
+ ---
4
+
5
+ # verify-docs
6
+
7
+ Verify that documentation claims in `@markuplint/types` are accurate by cross-referencing them against official web standards (WHATWG, W3C, RFC, CSS, SVG) and the actual source code implementation.
8
+
9
+ ## Input
10
+
11
+ `$ARGUMENTS` specifies the target: a package name (e.g., `@markuplint/types`) or a specific documentation file path. If omitted, defaults to all `docs/*.md` files in `packages/@markuplint/types/`.
12
+
13
+ ## Phase 1: Discovery
14
+
15
+ Read all target documentation files and extract every claim that references an external specification. Look for:
16
+
17
+ - Specification URLs (e.g., `https://html.spec.whatwg.org/...`, `https://www.w3.org/TR/...`)
18
+ - ABNF grammar blocks
19
+ - RFC numbers (e.g., RFC 5646, BCP 47)
20
+ - Specific numeric claims (e.g., "46 names", "8 reserved names")
21
+ - Regex patterns claimed to match a spec definition
22
+ - CSS value definition syntax references
23
+ - Statements like "Implements the X spec" or "according to Y specification"
24
+
25
+ Collect each claim as a structured item:
26
+
27
+ ```
28
+ { file, line, claim, specDomain, specURL }
29
+ ```
30
+
31
+ ## Phase 2: Categorize
32
+
33
+ Group extracted claims by specification domain:
34
+
35
+ | Domain | Examples |
36
+ | ------ | ----------------------------------------------------------------------- |
37
+ | WHATWG | HTML Living Standard microsyntaxes, autofill, link types, MIME sniffing |
38
+ | W3C | Permissions Policy, CSS Values and Units |
39
+ | RFC | BCP 47, other IETF standards |
40
+ | CSS | css-tree fork API, CSS value definition syntax |
41
+ | SVG | viewBox, preserveAspectRatio, transform unitless values |
42
+
43
+ ## Phase 3: Parallel Verification
44
+
45
+ For each domain group, launch a parallel agent using the **Task tool** with `subagent_type: "general-purpose"`. Each agent:
46
+
47
+ 1. **Searches the official specification** using WebSearch to find the authoritative definition
48
+ 2. **Compares the documentation claim** against the spec definition
49
+ 3. **Reads the corresponding source code** to confirm the implementation matches
50
+ 4. **Assigns a verdict** to each claim:
51
+ - **PASS** — Documentation accurately describes the spec and matches the implementation
52
+ - **FAIL** — Documentation is inaccurate (wrong number, incorrect grammar, misleading description)
53
+ - **WARN** — Documentation is technically correct but potentially misleading or incomplete
54
+
55
+ Each agent returns its results as a structured list:
56
+
57
+ ```
58
+ | # | File:Line | Claim | Spec Source | Verdict | Notes |
59
+ ```
60
+
61
+ ## Phase 4: Report
62
+
63
+ Collect results from all parallel agents and produce a consolidated PASS/FAIL table:
64
+
65
+ ```markdown
66
+ ## Verification Report
67
+
68
+ | # | Domain | File:Line | Claim Summary | Verdict | Notes |
69
+ | --- | ------ | ----------------- | ----------------------- | ------- | ---------------------------------------- |
70
+ | 1 | WHATWG | validators.md:214 | 46 autofill field names | FAIL | Code has 44 |
71
+ | 2 | W3C | validators.md:315 | ABNF grammar | WARN | allow-list optionality differs from spec |
72
+ | ... | ... | ... | ... | ... | ... |
73
+
74
+ **Summary:** X PASS / Y FAIL / Z WARN out of N total claims
75
+ ```
76
+
77
+ ## Phase 5: Correction Plan
78
+
79
+ For every FAIL item, produce a concrete correction instruction:
80
+
81
+ ```markdown
82
+ ### Correction 1: [Brief description]
83
+
84
+ - **File:** `path/to/file.md`
85
+ - **Line:** 214
86
+ - **Current:** `(46 names)`
87
+ - **Corrected:** `(44 names)`
88
+ - **Reason:** Source code `check-autocomplete.ts` contains 44 entries in `autofillFieldNames` array
89
+ ```
90
+
91
+ For WARN items, produce an advisory note with a suggested improvement (not mandatory).
92
+
93
+ ## Rules
94
+
95
+ 1. **Source code is the source of truth for implementation behavior.** If the docs say "implements X" but the code does something slightly different, the docs should be corrected to accurately describe what the code does.
96
+ 2. **Specification is the source of truth for what the standard defines.** If the code intentionally deviates from the spec, the docs should note the deviation explicitly.
97
+ 3. **Code bugs are a separate category.** If the code itself doesn't match the spec, flag it as a code issue (not a documentation issue) and note it separately.
98
+ 4. **Numeric claims must be exact.** Count arrays, enum values, and lists in the source code to verify numbers stated in documentation.
99
+ 5. **ABNF and regex patterns must be character-for-character accurate** when presented as quotes from a specification.
100
+ 6. **Use WebSearch for spec verification.** Always fetch the latest version of the referenced specification — do not rely on cached knowledge.
@@ -0,0 +1,494 @@
1
+ # チェックパイプライン
2
+
3
+ ## 概要
4
+
5
+ `@markuplint/types` パッケージは、HTML属性値の型バリデーションパイプラインを提供します。文字列値と型定義を受け取り、その値が期待される型に適合するかどうかを判定します。結果として、成功(一致)もしくは、不一致箇所の位置情報・理由コード・修正候補を含む詳細なエラーレポートを返します。
6
+
7
+ パイプラインが対応する型定義は5種類あります:
8
+
9
+ | カテゴリ | 説明 | 例 |
10
+ | -------------------- | ---------------------------------------------- | ----------------------------------------- |
11
+ | **キーワード型** | 定義レジストリから名前で解決される型 | `"URL"`, `"<color>"`, `"BCP47"` |
12
+ | **リスト型** | 空白区切りまたはカンマ区切りのトークン列 | `{ separator: "comma", token: "URL" }` |
13
+ | **列挙型** | 許可された文字列の固定集合 | `{ enum: ["auto", "lazy", "eager"] }` |
14
+ | **数値型** | 整数・浮動小数点数と範囲制約 | `{ type: "integer", gte: 0 }` |
15
+ | **ディレクティブ型** | プレフィックスパターンとトークン値の組み合わせ | `{ directive: ["/path/"], token: "URL" }` |
16
+
17
+ ## エントリポイント: `check()`
18
+
19
+ **ソース:** `src/check.ts`
20
+
21
+ `check()` 関数は、パッケージにおける型バリデーションの主要エントリポイントです。文字列値と型定義を受け取り、HTML定義とCSS定義を統合した定義レジストリと共にコアディスパッチャーへ処理を委譲します。
22
+
23
+ ```typescript
24
+ // src/check.ts
25
+ export function check(value: string, type: ReadonlyDeep<Type>, ref?: string, cache = true): Result {
26
+ return checkBase(value, type, { ...defs, ...cssDefs }, ref, cache);
27
+ }
28
+ ```
29
+
30
+ **パラメータ:**
31
+
32
+ | パラメータ | 型 | 説明 |
33
+ | ---------- | --------- | -------------------------------------------------- |
34
+ | `value` | `string` | バリデーション対象の属性値 |
35
+ | `type` | `Type` | バリデーションに使用する型定義 |
36
+ | `ref` | `string?` | エラー報告に使用する参照URL(省略可) |
37
+ | `cache` | `boolean` | キャッシュを使用するかどうか(デフォルト: `true`) |
38
+
39
+ **戻り値:** `Result` 型(判別可能なユニオン型):
40
+
41
+ - `MatchedResult` -- `{ matched: true }`
42
+ - `UnmatchedResult` -- `{ matched: false, raw, offset, length, line, column, reason, ref, ... }`
43
+
44
+ ## ディスパッチロジック: `checkBase()`
45
+
46
+ **ソース:** `src/check-base.ts`
47
+
48
+ `checkBase()` 関数は、型定義の構造を検査して適切なチェッカーへルーティングします。型の判別には一連の型ガード関数が使われます:
49
+
50
+ ```typescript
51
+ // src/check-base.ts
52
+ export function checkBase(value: string, type: ReadonlyDeep<Type>, defs: Defs, ref?: string, cache = true): Result {
53
+ if (isKeyword(type)) return checkKeywordType(value, type, defs, cache);
54
+ if (isList(type)) return checkList(value, type, defs, ref, cache);
55
+ if (isEnum(type)) return checkEnum(value, type, ref);
56
+ if (isNumber(type)) return checkNumber(value, type, ref);
57
+ if (isDirective(type)) return checkDirective(value, type, defs, ref, cache);
58
+ throw new Error('Unknown type');
59
+ }
60
+ ```
61
+
62
+ ### 型ガードの判定表
63
+
64
+ | ガード関数 | 判定条件 | 呼び出されるチェッカー |
65
+ | ------------- | ------------------------------------------------ | ---------------------- |
66
+ | `isKeyword` | `typeof type === 'string'` | `checkKeywordType` |
67
+ | `isList` | `separator` プロパティを持つオブジェクト | `checkList` |
68
+ | `isEnum` | `enum` プロパティを持つオブジェクト | `checkEnum` |
69
+ | `isNumber` | `type` プロパティが `'float'` または `'integer'` | `checkNumber` |
70
+ | `isDirective` | `directive` プロパティを持つオブジェクト | `checkDirective` |
71
+
72
+ ### ディスパッチフローチャート
73
+
74
+ ```mermaid
75
+ flowchart TD
76
+ A["checkBase(value, type, defs)"] --> B{typeof type === 'string'?}
77
+ B -->|Yes| C["checkKeywordType()"]
78
+ B -->|No| D{"'separator' in type?"}
79
+ D -->|Yes| E["checkList()"]
80
+ D -->|No| F{"'enum' in type?"}
81
+ F -->|Yes| G["checkEnum()"]
82
+ F -->|No| H{"type.type === 'float' | 'integer'?"}
83
+ H -->|Yes| I["checkNumber()"]
84
+ H -->|No| J{"'directive' in type?"}
85
+ J -->|Yes| K["checkDirective()"]
86
+ J -->|No| L["throw Error('Unknown type')"]
87
+ ```
88
+
89
+ ## 各チェッカーの詳細
90
+
91
+ ### `checkEnum()`
92
+
93
+ **ソース:** `src/enum.ts`
94
+
95
+ 許可された文字列の集合に対して値を検証します。
96
+
97
+ ```typescript
98
+ // src/enum.ts
99
+ export function checkEnum(value: string, type: ReadonlyDeep<Enum>, ref?: string): Result;
100
+ ```
101
+
102
+ **動作の流れ:**
103
+
104
+ 1. デフォルトでは、前後の空白は**許可されません**(`disallowToSurroundBySpaces` のデフォルト値は `true`)。許可されている場合はトリムされます。
105
+ 2. デフォルトでは、比較は**大文字小文字を区別しません**(`caseInsensitive` のデフォルト値は `true`)。値と列挙値の両方を小文字に変換してから比較します。
106
+ 3. いずれかの列挙値に一致すれば `matched()` を返します。
107
+ 4. 一致しなければ、理由 `'doesnt-exist-in-enum'` と共に、期待される値のリスト全体を含む `unmatched()` を返します。
108
+
109
+ **型定義の例:**
110
+
111
+ ```json
112
+ {
113
+ "enum": ["auto", "lazy", "eager"],
114
+ "caseInsensitive": true
115
+ }
116
+ ```
117
+
118
+ ### `checkList()`
119
+
120
+ **ソース:** `src/list.ts`
121
+
122
+ 区切り文字で分割されたトークン列として値を検証します。各トークンはネストされた型定義に対してバリデーションされます。
123
+
124
+ ```typescript
125
+ // src/list.ts
126
+ export function checkList(value: string, type: ReadonlyDeep<List>, defs: Defs, ref?: string, cache = true): Result;
127
+ ```
128
+
129
+ **動作の流れ:**
130
+
131
+ 1. リストの `separator`(`'space'` または `'comma'`)に基づいて、値を `TokenCollection` にパースします。
132
+ 2. トークンコレクションに対して構造的なチェック(トークン数、重複、順序)を実行します。
133
+ 3. 識別子トークンを取り出し、それぞれに対して `checkBase()` を再帰的に呼び出し、リストの `token` 型で検証します。
134
+ 4. トークン単位で失敗した場合、`Token.shiftLocation()` を使ってエラーの offset/line/column を調整し、問題のあるトークンを正確に指し示します。
135
+
136
+ **リスト型のプロパティ:**
137
+
138
+ | プロパティ | 型 | 説明 |
139
+ | ---------------------------- | ---------------------- | ------------------------ |
140
+ | `separator` | `'space' \| 'comma'` | トークンの区切り文字 |
141
+ | `token` | `ExtendedType \| Enum` | 各トークンの型定義 |
142
+ | `allowEmpty` | `boolean?` | 空の値を許可するか |
143
+ | `ordered` | `boolean?` | トークンの順序が重要か |
144
+ | `unique` | `boolean?` | 重複トークンを禁止するか |
145
+ | `number` | `string \| object?` | トークン数の制約 |
146
+ | `caseInsensitive` | `boolean?` | 大文字小文字の区別 |
147
+ | `disallowToSurroundBySpaces` | `boolean?` | 前後の空白を許可するか |
148
+
149
+ ### `checkNumber()`
150
+
151
+ **ソース:** `src/number.ts`
152
+
153
+ 数値型の値を範囲制約付きで検証します。
154
+
155
+ ```typescript
156
+ // src/number.ts
157
+ export function checkNumber(value: string, type: Readonly<TypeNumber>, ref?: string): Result;
158
+ ```
159
+
160
+ **動作の流れ:**
161
+
162
+ 1. 値が空の場合は `'empty-token'` として不一致を返します。
163
+ 2. `type.type` に応じて `isFloat()` または `isInt()` でフォーマットを検証します。
164
+ 3. フォーマットが合致した場合、数値をパースして範囲制約をチェックします:
165
+ - `gt` -- より大きい(厳密な不等式)
166
+ - `gte` -- 以上
167
+ - `lt` -- より小さい(厳密な不等式)
168
+ - `lte` -- 以下
169
+ 4. `clampable` が `true` の場合、範囲違反時に最も近い境界値を `candidate` として提案します。
170
+
171
+ **範囲チェックの流れ:**
172
+
173
+ ```mermaid
174
+ flowchart TD
175
+ A["checkNumber(value, type)"] --> B{値が空?}
176
+ B -->|Yes| C["unmatched: 'empty-token'"]
177
+ B -->|No| D{フォーマット一致?}
178
+ D -->|No| E["unmatched: 'unexpected-token'"]
179
+ D -->|Yes| F["数値としてパース"]
180
+ F --> G{gt 制約チェック}
181
+ G -->|失敗| H["unmatched: 'out-of-range'"]
182
+ G -->|通過| I{gte 制約チェック}
183
+ I -->|失敗| H
184
+ I -->|通過| J{lt 制約チェック}
185
+ J -->|失敗| H
186
+ J -->|通過| K{lte 制約チェック}
187
+ K -->|失敗| H
188
+ K -->|通過| L["matched()"]
189
+ ```
190
+
191
+ ### `checkDirective()`
192
+
193
+ **ソース:** `src/directive.ts`
194
+
195
+ プレフィックスパターン(ディレクティブ)とそれに続くトークン部分で構成される値を検証します。テンプレートの補間構文やURLスキームのプレフィックスなど、既知のプレフィックスの後に型付きの値が続く属性に使われます。
196
+
197
+ ```typescript
198
+ // src/directive.ts
199
+ export function checkDirective(
200
+ value: string,
201
+ type: ReadonlyDeep<Directive>,
202
+ defs: Defs,
203
+ ref?: string,
204
+ cache = true,
205
+ ): Result;
206
+ ```
207
+
208
+ **動作の流れ:**
209
+
210
+ 1. `type.directive` 内の各ディレクティブパターンを順に処理します。
211
+ 2. 各パターンは**プレーンな文字列**プレフィックスまたは**正規表現**(`regexParser()` でパース)のいずれかです。
212
+ - 正規表現: 値に対して実行し、名前付きグループ `token` またはキャプチャグループ `[1]` からトークン部分を抽出します。
213
+ - 文字列: 値がディレクティブで始まるか確認し、プレフィックスを除去します。
214
+ 3. 抽出されたトークン部分を `checkBase()` で `type.token` に対して検証します。
215
+ 4. 最初に成功したマッチを返すか、全ディレクティブが失敗した場合は最初の不一致結果を返します。
216
+
217
+ ### `checkKeywordType()`
218
+
219
+ **ソース:** `src/keyword-type.ts`
220
+
221
+ キーワード型名を定義レジストリから解決し、値を検証します。カスタムのプログラム的バリデータとCSSシンタックスマッチングの両方への入口となります。
222
+
223
+ ```typescript
224
+ // src/keyword-type.ts
225
+ export function checkKeywordType(value: string, type: KeywordDefinedType, defs: Defs, cache = true): Result;
226
+ ```
227
+
228
+ **動作の流れ:**
229
+
230
+ 1. **キャッシュ確認:** キャッシュが有効な場合、`value + type` をキーとして過去の結果を検索します。
231
+ 2. **定義の検索:** `defs` レジストリで `type` を検索します。
232
+ 3. **定義が見つからない場合:** `cssSyntaxMatch(value, type)` にフォールバックします。CSSシンタックスマッチングが `MARKUPLINT_TYPE_NO_EXIST` をスローした場合、値は寛容に受け入れられます(`matched()` を返す)。
233
+ 4. **定義が見つかった場合:** 定義がCSSシンタックスかカスタムシンタックスかを判別します:
234
+ - **CSSシンタックス** (`isCSSSyntax`): `cssSyntaxMatch()` に委譲
235
+ - **カスタムシンタックス** (`isCustomSyntax`): 定義の `is(value)` 関数を呼び出し
236
+ 5. 不一致の場合、定義の `ref` と `expects` で結果を補完します。
237
+
238
+ ```mermaid
239
+ flowchart TD
240
+ A["checkKeywordType(value, type, defs)"] --> B{キャッシュあり?}
241
+ B -->|Yes| C["キャッシュ結果を返す"]
242
+ B -->|No| D{defsに型が存在?}
243
+ D -->|No| E["cssSyntaxMatch(value, type)"]
244
+ E --> F{MARKUPLINT_TYPE_NO_EXIST?}
245
+ F -->|Yes| G["matched()"]
246
+ F -->|No| H["CSS結果を返す"]
247
+ D -->|Yes| I{isCSSSyntax(def)?}
248
+ I -->|Yes| J["cssSyntaxMatch(value, def)"]
249
+ I -->|No| K["def.is(value)"]
250
+ J --> L{一致?}
251
+ K --> L
252
+ L -->|Yes| M["matched を返す"]
253
+ L -->|No| N["ref/expects を補完して unmatched を返す"]
254
+ ```
255
+
256
+ ## CSSシンタックスマッチング
257
+
258
+ **ソース:** `src/css-syntax.ts`
259
+
260
+ `cssSyntaxMatch()` 関数は、[css-tree](https://github.com/csstree/csstree) ライブラリを利用して、CSSバリューの定義構文に対して値を検証します。
261
+
262
+ ```typescript
263
+ // src/css-syntax.ts
264
+ export function cssSyntaxMatch(value: string, type: CssSyntax | CustomCssSyntax): Result;
265
+ ```
266
+
267
+ ### 処理の仕組み
268
+
269
+ 1. **設定:** `type` がプレーンな文字列か `CustomCssSyntax` オブジェクトかに応じて処理が分岐します:
270
+ - **文字列:** CSSの型名やプロパティ名として直接使用(例: `"<color>"`)
271
+ - **オブジェクト:** `syntax.apply` を定義名、`syntax.def` を拡張型・カスタムトークナイザ、`syntax.properties` をCSSプロパティ拡張として取り出す
272
+
273
+ 2. **レキサー生成:** 以下を統合した、フォーク版 css-tree レキサーを生成します:
274
+ - **CSSオーバーライド** (`css-overrides.ts`): transform関数やレガシーSVG型の代替構文
275
+ - **拡張型定義:** カスタムシンタックス定義からマージされたもの
276
+ - **カスタムトークナイザ** (`css-tokenizers.ts`): BCP-47言語タグなど、プログラム的なトークンレベルのマッチャー
277
+
278
+ 3. **名前の検出:** 定義がCSSプロパティ(例: `<'color'>`)かCSS型(例: `<color>`)かを判定し、適切なマッチャーを設定します。
279
+
280
+ 4. **大文字小文字の区別:** `caseSensitive` が `true` の場合、大文字をミミックタグで包むことで、通常は大文字小文字を区別しない css-tree でのマッチング中にケースを保持します。
281
+
282
+ 5. **マッチング実行:** `lexer.match(defName, value)` を呼び出します。エラーがなければ `matched()` を返します。`var()` 関数が検出された場合も `matched()` を返します(css-treeの既知の制限)。
283
+
284
+ 6. **エラーハンドリング:** 不一致の場合、`SyntaxMatchError` から位置情報を抽出し、CSSシンタックスの期待値を含む `UnmatchedResult` を返します。
285
+
286
+ ### CSSオーバーライド
287
+
288
+ **ソース:** `src/css-overrides.ts`
289
+
290
+ SVG属性のバリデーションをサポートするため、CSS transform関数の代替構文を提供します:
291
+
292
+ ```typescript
293
+ export const cssOverrides: Record<string, string> = {
294
+ 'legacy-length-percentage': '<length> | <percentage> | <svg-length>',
295
+ 'legacy-angle': '<angle> | <zero> | <number>',
296
+ 'translate()': 'translate( <legacy-length-percentage> , ... )',
297
+ 'scale()': 'scale( [ <number> | <percentage> ]#{1,2} )',
298
+ 'rotate()': 'rotate( <legacy-angle> )',
299
+ 'skew()': 'skew( <legacy-angle> , <legacy-angle>? ) | ...',
300
+ };
301
+ ```
302
+
303
+ ### カスタムトークナイザ
304
+
305
+ **ソース:** `src/css-tokenizers.ts`
306
+
307
+ カスタムパースが必要な型に対して、トークンレベルのマッチャーを提供します:
308
+
309
+ ```typescript
310
+ export const cssTokenizers: Record<string, CssSyntaxTokenizer> = {
311
+ 'bcp-47'(token) {
312
+ if (!token) return 0;
313
+ return isBCP47()(token.value) ? 1 : 0;
314
+ },
315
+ };
316
+ ```
317
+
318
+ ## マルチタイプチェック
319
+
320
+ **ソース:** `src/check-multi-types.ts`
321
+
322
+ `checkMultiTypes()` 関数は、ひとつの値に対して複数の型チェッカー関数を順に試行し、最初に一致した結果か、最も有益な失敗結果を返します。
323
+
324
+ ```typescript
325
+ // src/check-multi-types.ts
326
+ export function checkMultiTypes(value: string, checks: readonly CustomSyntaxCheck[]): Result;
327
+ ```
328
+
329
+ **動作の流れ:**
330
+
331
+ 1. チェック関数を順番に実行します。
332
+ 2. いずれかのチェックが `matched` を返した時点で、その結果を即座に返します。
333
+ 3. すべてのチェックが失敗した場合、以下のヒューリスティックで「最良の」不一致結果を選定します:
334
+ - `passCount`(失敗前に通過したサブチェックの数)がより多い結果を優先
335
+ - `passCount` が同じ場合、`offset`(値の中でより後方で失敗した)がより大きい結果を優先
336
+ 4. チェック関数が提供されなかった場合、フォールバックとして `matched()` を返します。
337
+
338
+ ```mermaid
339
+ flowchart TD
340
+ A["checkMultiTypes(value, checks)"] --> B["各チェック関数を順に処理"]
341
+ B --> C["result = check(value)"]
342
+ C --> D{result.matched?}
343
+ D -->|Yes| E["一致結果を返す"]
344
+ D -->|No| F{passCount > 現在の最良?}
345
+ F -->|Yes| G["最良の不一致を更新"]
346
+ F -->|No| H{passCount == 最良 かつ offset >= 最良?}
347
+ H -->|Yes| G
348
+ H -->|No| I["現在の最良を維持"]
349
+ G --> B
350
+ I --> B
351
+ B -->|全て完了| J["最良の不一致 または matched() を返す"]
352
+ ```
353
+
354
+ **使用例**(`defs.ts` の `ItemProp` 型より):
355
+
356
+ ```typescript
357
+ is(value) {
358
+ return checkMultiTypes(value, [
359
+ value => (isAbsURL()(value) ? matched() : unmatched(value, 'unexpected-token')),
360
+ value => (isItempropName()(value) ? matched() : unmatched(value, 'unexpected-token')),
361
+ ]);
362
+ }
363
+ ```
364
+
365
+ ## 修正候補の提案
366
+
367
+ **ソース:** `src/get-candidate.ts`
368
+
369
+ `getCandidate()` 関数は、[レーベンシュタイン距離](https://ja.wikipedia.org/wiki/%E3%83%AC%E3%83%BC%E3%83%99%E3%83%B3%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%A4%E3%83%B3%E8%B7%9D%E9%9B%A2)(`leven` ライブラリ経由)を用いて、候補文字列の中から最も近い一致を見つけます。
370
+
371
+ ```typescript
372
+ // src/get-candidate.ts
373
+ export function getCandidate(
374
+ value: NullableString,
375
+ ...candidates: readonly (NullableString | readonly NullableString[])[]
376
+ ): string | undefined;
377
+ ```
378
+
379
+ **アルゴリズム:**
380
+
381
+ 1. 候補の配列を2階層まで平坦化し、null/undefined を除外します。
382
+ 2. 各候補に対して類似度を算出: `ratio = 1 - levenshtein(value, candidate) / candidate.length`
383
+ 3. 値と候補の両方を小文字化・トリムしてから比較します。
384
+ 4. **閾値:** `ratio >= 0.5`(50%以上の類似度)を持つ候補のみが対象です。
385
+ 5. 最も高い ratio を持つ候補を返します。閾値を満たすものがなければ `undefined` を返します。
386
+ 6. 値が候補と完全一致する場合は `undefined` を返します(提案は不要)。
387
+
388
+ **使用場面:** `getCandidate()` は、特定の型バリデータ(例: `NavigableTargetNameOrKeyword`)でタイプミスの修正候補を提案する際に呼び出されます。たとえば `_blank` のスペルミスに対して正しい値を提案するといった用途です。
389
+
390
+ ## パイプライン全体図
391
+
392
+ エントリポイントから結果出力までのパイプライン全体を以下の図で示します:
393
+
394
+ ```mermaid
395
+ flowchart TD
396
+ START["check(value, type)"] --> MERGE["defs + cssDefs を統合"]
397
+ MERGE --> BASE["checkBase(value, type, defs)"]
398
+
399
+ BASE --> KW{キーワード?}
400
+ KW -->|Yes| CKT["checkKeywordType()"]
401
+ KW -->|No| LS{リスト?}
402
+ LS -->|Yes| CL["checkList()"]
403
+ LS -->|No| EN{列挙?}
404
+ EN -->|Yes| CE["checkEnum()"]
405
+ EN -->|No| NM{数値?}
406
+ NM -->|Yes| CN["checkNumber()"]
407
+ NM -->|No| DR{ディレクティブ?}
408
+ DR -->|Yes| CD["checkDirective()"]
409
+ DR -->|No| ERR["throw Error"]
410
+
411
+ CKT --> LOOKUP{defsに定義あり?}
412
+ LOOKUP -->|No| CSS1["cssSyntaxMatch()"]
413
+ LOOKUP -->|Yes| DEFTYPE{isCSSSyntax?}
414
+ DEFTYPE -->|Yes| CSS2["cssSyntaxMatch()"]
415
+ DEFTYPE -->|No| CUSTOM["def.is(value)"]
416
+
417
+ CSS1 --> CSSTREE["css-tree lexer.match()"]
418
+ CSS2 --> CSSTREE
419
+
420
+ CL --> PARSE["TokenCollection(value, type)"]
421
+ PARSE --> STRUCT["構造チェック"]
422
+ STRUCT -->|失敗| UNMATCH1["UnmatchedResult"]
423
+ STRUCT -->|通過| TOKENS["各識別子トークンに対して"]
424
+ TOKENS --> RECURSE["checkBase(token, type.token, defs)"]
425
+ RECURSE -->|失敗| UNMATCH2["位置補正つき UnmatchedResult"]
426
+ RECURSE -->|通過| MATCH1["matched()"]
427
+
428
+ CE --> COMPARE["列挙値と比較"]
429
+ COMPARE -->|一致| MATCH2["matched()"]
430
+ COMPARE -->|不一致| UNMATCH3["unmatched: 'doesnt-exist-in-enum'"]
431
+
432
+ CN --> FORMAT{フォーマット一致?}
433
+ FORMAT -->|No| UNMATCH4["unmatched: 'unexpected-token'"]
434
+ FORMAT -->|Yes| RANGE{範囲内?}
435
+ RANGE -->|No| UNMATCH5["unmatched: 'out-of-range'"]
436
+ RANGE -->|Yes| MATCH3["matched()"]
437
+
438
+ CD --> DIRS["各ディレクティブパターンを処理"]
439
+ DIRS --> EXTRACT["トークン部分を抽出"]
440
+ EXTRACT --> RECURSE2["checkBase(tokenPart, type.token, defs)"]
441
+ RECURSE2 -->|一致| MATCH4["matched()"]
442
+ RECURSE2 -->|不一致| NEXT["次のディレクティブを試行"]
443
+
444
+ CUSTOM --> RESULT["Result"]
445
+ CSSTREE --> RESULT
446
+ MATCH1 --> RESULT
447
+ MATCH2 --> RESULT
448
+ MATCH3 --> RESULT
449
+ MATCH4 --> RESULT
450
+ UNMATCH1 --> RESULT
451
+ UNMATCH2 --> RESULT
452
+ UNMATCH3 --> RESULT
453
+ UNMATCH4 --> RESULT
454
+ UNMATCH5 --> RESULT
455
+ ```
456
+
457
+ ## 型定義レジストリ
458
+
459
+ **ソース:** `src/defs.ts`, `src/css-defs.ts`
460
+
461
+ 型定義レジストリ(`Defs`)は、型名の文字列から `CustomSyntax` または `CustomCssSyntax` オブジェクトへのマップです。エントリポイントでは2つのレジストリが統合されます:
462
+
463
+ | レジストリ | ソース | 内容 |
464
+ | ---------- | ----------------- | ------------------------------------------------------------------------------------------------------ |
465
+ | `defs` | `src/defs.ts` | HTML属性型: `Any`, `URL`, `Number`, `DOMID`, `DateTime`, `BCP47`, `MIMEType`, `CustomElementName` など |
466
+ | `cssDefs` | `src/css-defs.ts` | CSS/SVG型: `<css-declaration-list>`, `<view-box>`, `<preserve-aspect-ratio>`, `<dasharray>` など |
467
+
468
+ 各定義は以下の2つの形式のいずれかです:
469
+
470
+ **カスタムシンタックス**(プログラム的チェッカー):
471
+
472
+ ```typescript
473
+ {
474
+ ref: 'https://...',
475
+ expects: [{ type: 'format', value: 'date time' }],
476
+ is: (value: string) => Result
477
+ }
478
+ ```
479
+
480
+ **CSSシンタックス**(css-tree 文法):
481
+
482
+ ```typescript
483
+ {
484
+ ref: 'https://...',
485
+ syntax: {
486
+ apply: '<view-box>',
487
+ def: {
488
+ 'view-box': '<min-x> [,]? <min-y> [,]? <width> [,]? <height>',
489
+ 'min-x': '<number>',
490
+ // ...
491
+ }
492
+ }
493
+ }
494
+ ```