@markuplint/types 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.
Files changed (112) hide show
  1. package/ARCHITECTURE.ja.md +252 -0
  2. package/ARCHITECTURE.md +252 -0
  3. package/CHANGELOG.md +11 -9
  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 +514 -0
  13. package/docs/validators.md +514 -0
  14. package/lib/check-base.d.ts +71 -0
  15. package/lib/check-base.js +71 -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 +8 -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 +6 -0
  39. package/lib/index.js +6 -0
  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/primitive/is-float.d.ts +4 -1
  49. package/lib/primitive/is-float.js +4 -1
  50. package/lib/primitive/is-int.d.ts +4 -1
  51. package/lib/primitive/is-int.js +4 -1
  52. package/lib/primitive/is-non-zero-uint.d.ts +3 -2
  53. package/lib/primitive/is-non-zero-uint.js +3 -2
  54. package/lib/primitive/is-quantity.d.ts +5 -3
  55. package/lib/primitive/is-quantity.js +5 -3
  56. package/lib/primitive/is-uint.d.ts +5 -1
  57. package/lib/primitive/is-uint.js +5 -1
  58. package/lib/primitive/range.d.ts +5 -4
  59. package/lib/primitive/range.js +5 -4
  60. package/lib/primitive/split-unit.d.ts +3 -2
  61. package/lib/primitive/split-unit.js +3 -2
  62. package/lib/rfc/is-bcp-47.d.ts +2 -0
  63. package/lib/rfc/is-bcp-47.js +2 -0
  64. package/lib/token/token-collection.d.ts +107 -2
  65. package/lib/token/token-collection.js +103 -3
  66. package/lib/token/token.d.ts +66 -2
  67. package/lib/token/token.js +66 -2
  68. package/lib/token/types.d.ts +9 -0
  69. package/lib/types.d.ts +107 -0
  70. package/lib/w3c/check-serialized-permissions-policy.d.ts +2 -0
  71. package/lib/w3c/check-serialized-permissions-policy.js +2 -0
  72. package/lib/whatwg/check-autocomplete.d.ts +5 -0
  73. package/lib/whatwg/check-autocomplete.js +6 -0
  74. package/lib/whatwg/check-datetime/date-string.d.ts +2 -0
  75. package/lib/whatwg/check-datetime/date-string.js +2 -0
  76. package/lib/whatwg/check-datetime/datetime-tokens.d.ts +13 -0
  77. package/lib/whatwg/check-datetime/datetime-tokens.js +13 -0
  78. package/lib/whatwg/check-datetime/duration-string.d.ts +7 -0
  79. package/lib/whatwg/check-datetime/duration-string.js +7 -0
  80. package/lib/whatwg/check-datetime/global-date-and-time-string.d.ts +2 -0
  81. package/lib/whatwg/check-datetime/global-date-and-time-string.js +2 -0
  82. package/lib/whatwg/check-datetime/index.d.ts +5 -0
  83. package/lib/whatwg/check-datetime/index.js +5 -0
  84. package/lib/whatwg/check-datetime/local-date-and-time-string.d.ts +4 -0
  85. package/lib/whatwg/check-datetime/local-date-and-time-string.js +4 -0
  86. package/lib/whatwg/check-datetime/month-string.d.ts +2 -0
  87. package/lib/whatwg/check-datetime/month-string.js +2 -0
  88. package/lib/whatwg/check-datetime/time-string.d.ts +2 -0
  89. package/lib/whatwg/check-datetime/time-string.js +2 -0
  90. package/lib/whatwg/check-datetime/time-zone-offset-string.d.ts +8 -0
  91. package/lib/whatwg/check-datetime/time-zone-offset-string.js +8 -0
  92. package/lib/whatwg/check-datetime/week-string.d.ts +2 -0
  93. package/lib/whatwg/check-datetime/week-string.js +2 -0
  94. package/lib/whatwg/check-datetime/year-string.d.ts +2 -0
  95. package/lib/whatwg/check-datetime/year-string.js +2 -0
  96. package/lib/whatwg/check-datetime/yearless-date-string.d.ts +2 -0
  97. package/lib/whatwg/check-datetime/yearless-date-string.js +2 -0
  98. package/lib/whatwg/check-link-type.d.ts +5 -1
  99. package/lib/whatwg/check-link-type.js +5 -1
  100. package/lib/whatwg/check-mime-type.d.ts +4 -1
  101. package/lib/whatwg/check-mime-type.js +4 -1
  102. package/lib/whatwg/is-abs-url.d.ts +2 -0
  103. package/lib/whatwg/is-abs-url.js +2 -0
  104. package/lib/whatwg/is-browser-context-name.d.ts +2 -2
  105. package/lib/whatwg/is-browser-context-name.js +2 -2
  106. package/lib/whatwg/is-custom-element-name.d.ts +1 -1
  107. package/lib/whatwg/is-custom-element-name.js +1 -1
  108. package/lib/whatwg/is-itemprop-name.d.ts +1 -0
  109. package/lib/whatwg/is-itemprop-name.js +1 -0
  110. package/lib/whatwg/is-navigable-target-name.d.ts +2 -0
  111. package/lib/whatwg/is-navigable-target-name.js +2 -0
  112. package/package.json +6 -6
@@ -0,0 +1,494 @@
1
+ # Check Pipeline
2
+
3
+ ## Overview
4
+
5
+ The `@markuplint/types` package provides a type validation pipeline for HTML attribute values. Given a string value and a type definition, the pipeline determines whether the value conforms to the expected type, returning either a successful match or a detailed mismatch report with location information, reason codes, and suggested corrections.
6
+
7
+ The pipeline supports five categories of type definitions:
8
+
9
+ | Category | Description | Example |
10
+ | ------------- | -------------------------------------------------- | ----------------------------------------- |
11
+ | **Keyword** | Named types resolved via a definitions registry | `"URL"`, `"<color>"`, `"BCP47"` |
12
+ | **List** | Space-separated or comma-separated token sequences | `{ separator: "comma", token: "URL" }` |
13
+ | **Enum** | Fixed set of allowed string values | `{ enum: ["auto", "lazy", "eager"] }` |
14
+ | **Number** | Integer or float with optional range constraints | `{ type: "integer", gte: 0 }` |
15
+ | **Directive** | Prefix-pattern followed by a token value | `{ directive: ["/path/"], token: "URL" }` |
16
+
17
+ ## Entry Point: `check()`
18
+
19
+ **Source:** `src/check.ts`
20
+
21
+ The `check()` function is the primary entry point for all type validation in the package. It accepts a string value and a type definition, then delegates to the core dispatcher with the combined HTML and CSS definition registries.
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
+ **Parameters:**
31
+
32
+ | Parameter | Type | Description |
33
+ | --------- | --------- | ----------------------------------------------- |
34
+ | `value` | `string` | The attribute value to validate |
35
+ | `type` | `Type` | The type definition to validate against |
36
+ | `ref` | `string?` | Optional reference URL for error context |
37
+ | `cache` | `boolean` | Whether to use cached results (default: `true`) |
38
+
39
+ **Return value:** A `Result` type, which is a discriminated union:
40
+
41
+ - `MatchedResult` -- `{ matched: true }`
42
+ - `UnmatchedResult` -- `{ matched: false, raw, offset, length, line, column, reason, ref, ... }`
43
+
44
+ ## Dispatch Logic: `checkBase()`
45
+
46
+ **Source:** `src/check-base.ts`
47
+
48
+ The `checkBase()` function inspects the shape of the type definition and routes to the appropriate checker. The type discrimination uses a series of type guard functions:
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
+ ### Type Guard Decision Table
63
+
64
+ | Guard | Condition | Resulting Checker |
65
+ | ------------- | ---------------------------------------------------- | ------------------ |
66
+ | `isKeyword` | `typeof type === 'string'` | `checkKeywordType` |
67
+ | `isList` | object with `separator` property | `checkList` |
68
+ | `isEnum` | object with `enum` property | `checkEnum` |
69
+ | `isNumber` | object with `type` equal to `'float'` or `'integer'` | `checkNumber` |
70
+ | `isDirective` | object with `directive` property | `checkDirective` |
71
+
72
+ ### Dispatch Flowchart
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
+ ## Individual Checkers
90
+
91
+ ### `checkEnum()`
92
+
93
+ **Source:** `src/enum.ts`
94
+
95
+ Validates a value against a fixed set of allowed strings.
96
+
97
+ ```typescript
98
+ // src/enum.ts
99
+ export function checkEnum(value: string, type: ReadonlyDeep<Enum>, ref?: string): Result;
100
+ ```
101
+
102
+ **Behavior:**
103
+
104
+ 1. By default, surrounding spaces are **disallowed** (`disallowToSurroundBySpaces` defaults to `true`). If allowed, the value is trimmed.
105
+ 2. By default, comparison is **case-insensitive** (`caseInsensitive` defaults to `true`). Both the value and enum entries are lowercased before comparison.
106
+ 3. If the value matches an entry, returns `matched()`.
107
+ 4. Otherwise, returns `unmatched()` with reason `'doesnt-exist-in-enum'` and the full list of expected values.
108
+
109
+ **Example type definition:**
110
+
111
+ ```json
112
+ {
113
+ "enum": ["auto", "lazy", "eager"],
114
+ "caseInsensitive": true
115
+ }
116
+ ```
117
+
118
+ ### `checkList()`
119
+
120
+ **Source:** `src/list.ts`
121
+
122
+ Validates a value as a separated list of tokens, where each token is validated against a nested type definition.
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
+ **Behavior:**
130
+
131
+ 1. Parses the value into a `TokenCollection` based on the list's `separator` (`'space'` or `'comma'`).
132
+ 2. Performs structural checks on the token collection (token count, uniqueness, ordering).
133
+ 3. Extracts identifier tokens and validates each one by recursively calling `checkBase()` with the list's `token` type.
134
+ 4. On per-token failure, adjusts the error offset/line/column using `Token.shiftLocation()` to point to the exact failing token.
135
+
136
+ **List type properties:**
137
+
138
+ | Property | Type | Description |
139
+ | ---------------------------- | ---------------------- | --------------------------------------- |
140
+ | `separator` | `'space' \| 'comma'` | Token separator |
141
+ | `token` | `ExtendedType \| Enum` | Type definition for each token |
142
+ | `allowEmpty` | `boolean?` | Whether an empty value is valid |
143
+ | `ordered` | `boolean?` | Whether token order matters |
144
+ | `unique` | `boolean?` | Whether duplicate tokens are disallowed |
145
+ | `number` | `string \| object?` | Constraints on the number of tokens |
146
+ | `caseInsensitive` | `boolean?` | Case sensitivity for token comparison |
147
+ | `disallowToSurroundBySpaces` | `boolean?` | Whether outer spaces are allowed |
148
+
149
+ ### `checkNumber()`
150
+
151
+ **Source:** `src/number.ts`
152
+
153
+ Validates a value as a numeric type with optional range constraints.
154
+
155
+ ```typescript
156
+ // src/number.ts
157
+ export function checkNumber(value: string, type: Readonly<TypeNumber>, ref?: string): Result;
158
+ ```
159
+
160
+ **Behavior:**
161
+
162
+ 1. Returns `unmatched` with `'empty-token'` if the value is empty.
163
+ 2. Tests the value's format using `isFloat()` or `isInt()` based on `type.type`.
164
+ 3. If the format matches, parses the number and checks range constraints:
165
+ - `gt` -- value must be strictly greater than
166
+ - `gte` -- value must be greater than or equal to
167
+ - `lt` -- value must be strictly less than
168
+ - `lte` -- value must be less than or equal to
169
+ 4. When `clampable` is `true` and a range violation occurs, suggests the nearest boundary value as a `candidate`.
170
+
171
+ **Range check flow:**
172
+
173
+ ```mermaid
174
+ flowchart TD
175
+ A["checkNumber(value, type)"] --> B{value is empty?}
176
+ B -->|Yes| C["unmatched: 'empty-token'"]
177
+ B -->|No| D{matches format?}
178
+ D -->|No| E["unmatched: 'unexpected-token'"]
179
+ D -->|Yes| F["Parse as number"]
180
+ F --> G{Check gt constraint}
181
+ G -->|Fail| H["unmatched: 'out-of-range'"]
182
+ G -->|Pass| I{Check gte constraint}
183
+ I -->|Fail| H
184
+ I -->|Pass| J{Check lt constraint}
185
+ J -->|Fail| H
186
+ J -->|Pass| K{Check lte constraint}
187
+ K -->|Fail| H
188
+ K -->|Pass| L["matched()"]
189
+ ```
190
+
191
+ ### `checkDirective()`
192
+
193
+ **Source:** `src/directive.ts`
194
+
195
+ Validates a value that consists of a prefix pattern (directive) followed by a token portion. Directives are used for attribute values where a known prefix leads into a typed value (for example, an interpolation syntax or a URL scheme prefix).
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
+ **Behavior:**
209
+
210
+ 1. Iterates through each directive pattern in `type.directive`.
211
+ 2. Each pattern can be either a **plain string** prefix or a **regex** (parsed by `regexParser()`).
212
+ - For regex patterns: executes against the value, extracting the token from a named group `token` or capture group `[1]`.
213
+ - For string patterns: checks if the value starts with the directive, then slices off the prefix.
214
+ 3. The extracted token portion is validated via `checkBase()` with `type.token`.
215
+ 4. Returns the first successful match, or the first unmatched result if all directives fail.
216
+
217
+ ### `checkKeywordType()`
218
+
219
+ **Source:** `src/keyword-type.ts`
220
+
221
+ Resolves a keyword type name through the definitions registry and validates the value accordingly. This is the gateway to both custom programmatic validators and CSS syntax matching.
222
+
223
+ ```typescript
224
+ // src/keyword-type.ts
225
+ export function checkKeywordType(value: string, type: KeywordDefinedType, defs: Defs, cache = true): Result;
226
+ ```
227
+
228
+ **Behavior:**
229
+
230
+ 1. **Cache lookup:** If caching is enabled, checks for a previously computed result using the key `value + type`.
231
+ 2. **Definition lookup:** Searches for `type` in the `defs` registry.
232
+ 3. **If the type is not found in `defs`:** Falls back to `cssSyntaxMatch(value, type)`. If CSS syntax matching throws `MARKUPLINT_TYPE_NO_EXIST`, the value is permissively accepted (returns `matched()`).
233
+ 4. **If the type is found:** Determines whether the definition is a CSS syntax definition or a custom syntax definition:
234
+ - **CSS syntax** (`isCSSSyntax`): Delegates to `cssSyntaxMatch()`.
235
+ - **Custom syntax** (`isCustomSyntax`): Calls the definition's `is(value)` function.
236
+ 5. On mismatch, enriches the result with the definition's `ref` and `expects` if not already set.
237
+
238
+ ```mermaid
239
+ flowchart TD
240
+ A["checkKeywordType(value, type, defs)"] --> B{Cached?}
241
+ B -->|Yes| C["Return cached result"]
242
+ B -->|No| D{type in defs?}
243
+ D -->|No| E["cssSyntaxMatch(value, type)"]
244
+ E --> F{MARKUPLINT_TYPE_NO_EXIST?}
245
+ F -->|Yes| G["matched()"]
246
+ F -->|No| H["Return CSS result"]
247
+ D -->|Yes| I{isCSSSyntax(def)?}
248
+ I -->|Yes| J["cssSyntaxMatch(value, def)"]
249
+ I -->|No| K["def.is(value)"]
250
+ J --> L{matched?}
251
+ K --> L
252
+ L -->|Yes| M["Return matched"]
253
+ L -->|No| N["Enrich with ref/expects, return unmatched"]
254
+ ```
255
+
256
+ ## CSS Syntax Matching
257
+
258
+ **Source:** `src/css-syntax.ts`
259
+
260
+ The `cssSyntaxMatch()` function validates values against CSS value definition syntax using the [css-tree](https://github.com/csstree/csstree) library.
261
+
262
+ ```typescript
263
+ // src/css-syntax.ts
264
+ export function cssSyntaxMatch(value: string, type: CssSyntax | CustomCssSyntax): Result;
265
+ ```
266
+
267
+ ### How It Works
268
+
269
+ 1. **Configuration:** Depending on whether `type` is a plain string or a `CustomCssSyntax` object:
270
+ - **String:** Used directly as a CSS type or property name (e.g., `"<color>"`).
271
+ - **Object:** Extracts `syntax.apply` as the definition name, `syntax.def` for extended types and custom tokenizers, and `syntax.properties` for CSS property extensions.
272
+
273
+ 2. **Lexer creation:** Creates a forked css-tree lexer with:
274
+ - **CSS overrides** (`css-overrides.ts`): Alternative syntax for transform functions and legacy SVG types.
275
+ - **Extended type definitions:** Merged from the custom syntax definition.
276
+ - **Custom tokenizers** (`css-tokenizers.ts`): Programmatic token-level matchers (e.g., BCP-47 language tags).
277
+
278
+ 3. **Name detection:** Determines whether the definition is a CSS property (e.g., `<'color'>`) or a CSS type (e.g., `<color>`) and sets up the appropriate matcher.
279
+
280
+ 4. **Case sensitivity:** When `caseSensitive` is `true`, uppercase letters are wrapped in mimic tags to preserve case during css-tree matching (which is normally case-insensitive).
281
+
282
+ 5. **Matching:** Calls `lexer.match(defName, value)`. If no error, returns `matched()`. If a `var()` function is encountered, returns `matched()` (known css-tree limitation).
283
+
284
+ 6. **Error handling:** On mismatch, extracts position information from the `SyntaxMatchError` and returns an `UnmatchedResult` with the CSS syntax expectation.
285
+
286
+ ### CSS Overrides
287
+
288
+ **Source:** `src/css-overrides.ts`
289
+
290
+ Provides alternative syntax definitions for CSS transform functions to support SVG attribute validation:
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
+ ### Custom Tokenizers
304
+
305
+ **Source:** `src/css-tokenizers.ts`
306
+
307
+ Provides token-level matchers for types that require custom parsing:
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
+ ## Multi-Type Checking
319
+
320
+ **Source:** `src/check-multi-types.ts`
321
+
322
+ The `checkMultiTypes()` function tries multiple type checker functions against a single value, returning the first match or the best failure.
323
+
324
+ ```typescript
325
+ // src/check-multi-types.ts
326
+ export function checkMultiTypes(value: string, checks: readonly CustomSyntaxCheck[]): Result;
327
+ ```
328
+
329
+ **Behavior:**
330
+
331
+ 1. Iterates through each check function in order.
332
+ 2. If a check returns `matched`, immediately returns that result.
333
+ 3. If no check matches, selects the "best" unmatched result using the following heuristic:
334
+ - Prefers the result with the higher `passCount` (more sub-checks passed before failing).
335
+ - On equal `passCount`, prefers the result with the greater `offset` (the failure occurred later in the value string).
336
+ 4. If no checks were provided, returns `matched()` as a fallback.
337
+
338
+ ```mermaid
339
+ flowchart TD
340
+ A["checkMultiTypes(value, checks)"] --> B["For each check function"]
341
+ B --> C["result = check(value)"]
342
+ C --> D{result.matched?}
343
+ D -->|Yes| E["Return matched result"]
344
+ D -->|No| F{passCount > best?}
345
+ F -->|Yes| G["Update best unmatched"]
346
+ F -->|No| H{passCount == best AND offset >= best?}
347
+ H -->|Yes| G
348
+ H -->|No| I["Keep current best"]
349
+ G --> B
350
+ I --> B
351
+ B -->|All done| J["Return best unmatched or matched()"]
352
+ ```
353
+
354
+ **Usage example** (from `defs.ts`, `ItemProp` type):
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
+ ## Candidate Suggestion
366
+
367
+ **Source:** `src/get-candidate.ts`
368
+
369
+ The `getCandidate()` function finds the closest matching string from a set of candidates using [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) (via the `leven` library).
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
+ **Algorithm:**
380
+
381
+ 1. Flattens the candidate arrays (up to 2 levels deep) and filters out null/undefined entries.
382
+ 2. For each candidate, computes the similarity ratio: `ratio = 1 - levenshtein(value, candidate) / candidate.length`.
383
+ 3. Both value and candidate are lowercased and trimmed before comparison.
384
+ 4. **Threshold:** Only candidates with `ratio >= 0.5` (at least 50% similar) are considered.
385
+ 5. Returns the candidate with the highest ratio, or `undefined` if none meets the threshold.
386
+ 6. If the value exactly matches a candidate, returns `undefined` (no suggestion needed).
387
+
388
+ **Usage context:** `getCandidate()` is called in specific type validators (e.g., `NavigableTargetNameOrKeyword`) to suggest corrections for typos in attribute values (e.g., a misspelled `_blank`).
389
+
390
+ ## Complete Flow Diagram
391
+
392
+ The following diagram shows the full pipeline from entry point to result:
393
+
394
+ ```mermaid
395
+ flowchart TD
396
+ START["check(value, type)"] --> MERGE["Merge defs + cssDefs"]
397
+ MERGE --> BASE["checkBase(value, type, defs)"]
398
+
399
+ BASE --> KW{Keyword?}
400
+ KW -->|Yes| CKT["checkKeywordType()"]
401
+ KW -->|No| LS{List?}
402
+ LS -->|Yes| CL["checkList()"]
403
+ LS -->|No| EN{Enum?}
404
+ EN -->|Yes| CE["checkEnum()"]
405
+ EN -->|No| NM{Number?}
406
+ NM -->|Yes| CN["checkNumber()"]
407
+ NM -->|No| DR{Directive?}
408
+ DR -->|Yes| CD["checkDirective()"]
409
+ DR -->|No| ERR["throw Error"]
410
+
411
+ CKT --> LOOKUP{Found in 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["Structural check"]
422
+ STRUCT -->|Fail| UNMATCH1["UnmatchedResult"]
423
+ STRUCT -->|Pass| TOKENS["For each ident token"]
424
+ TOKENS --> RECURSE["checkBase(token, type.token, defs)"]
425
+ RECURSE -->|Fail| UNMATCH2["UnmatchedResult with shifted location"]
426
+ RECURSE -->|Pass| MATCH1["matched()"]
427
+
428
+ CE --> COMPARE["Compare against enum values"]
429
+ COMPARE -->|Match| MATCH2["matched()"]
430
+ COMPARE -->|No match| UNMATCH3["unmatched: 'doesnt-exist-in-enum'"]
431
+
432
+ CN --> FORMAT{Format ok?}
433
+ FORMAT -->|No| UNMATCH4["unmatched: 'unexpected-token'"]
434
+ FORMAT -->|Yes| RANGE{Range ok?}
435
+ RANGE -->|No| UNMATCH5["unmatched: 'out-of-range'"]
436
+ RANGE -->|Yes| MATCH3["matched()"]
437
+
438
+ CD --> DIRS["For each directive pattern"]
439
+ DIRS --> EXTRACT["Extract token portion"]
440
+ EXTRACT --> RECURSE2["checkBase(tokenPart, type.token, defs)"]
441
+ RECURSE2 -->|Match| MATCH4["matched()"]
442
+ RECURSE2 -->|No match| NEXT["Try next directive"]
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
+ ## Type Definitions Registry
458
+
459
+ **Source:** `src/defs.ts`, `src/css-defs.ts`
460
+
461
+ The definitions registry (`Defs`) is a map from type name strings to either `CustomSyntax` or `CustomCssSyntax` objects. There are two registries that are merged at the entry point:
462
+
463
+ | Registry | Source | Contents |
464
+ | --------- | ----------------- | ----------------------------------------------------------------------------------------------------------------- |
465
+ | `defs` | `src/defs.ts` | HTML attribute types: `Any`, `URL`, `Number`, `DOMID`, `DateTime`, `BCP47`, `MIMEType`, `CustomElementName`, etc. |
466
+ | `cssDefs` | `src/css-defs.ts` | CSS/SVG types: `<css-declaration-list>`, `<view-box>`, `<preserve-aspect-ratio>`, `<dasharray>`, etc. |
467
+
468
+ Each definition is one of two forms:
469
+
470
+ **Custom syntax** (programmatic checker):
471
+
472
+ ```typescript
473
+ {
474
+ ref: 'https://...',
475
+ expects: [{ type: 'format', value: 'date time' }],
476
+ is: (value: string) => Result
477
+ }
478
+ ```
479
+
480
+ **CSS syntax** (css-tree grammar):
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
+ ```