@markuplint/types 4.8.1 → 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.
- package/ARCHITECTURE.ja.md +252 -0
- package/ARCHITECTURE.md +252 -0
- package/CHANGELOG.md +3 -3
- package/README.md +37 -2
- package/SKILL.md +100 -0
- package/docs/check-pipeline.ja.md +494 -0
- package/docs/check-pipeline.md +494 -0
- package/docs/token-system.ja.md +584 -0
- package/docs/token-system.md +584 -0
- package/docs/type-system.ja.md +556 -0
- package/docs/type-system.md +556 -0
- package/docs/validators.ja.md +514 -0
- package/docs/validators.md +514 -0
- package/lib/check-base.d.ts +71 -0
- package/lib/check-base.js +71 -0
- package/lib/check-multi-types.d.ts +12 -1
- package/lib/check-multi-types.js +11 -0
- package/lib/check.d.ts +13 -0
- package/lib/check.js +13 -0
- package/lib/css-defs.d.ts +8 -0
- package/lib/css-defs.js +8 -0
- package/lib/css-overrides.d.ts +7 -0
- package/lib/css-overrides.js +7 -0
- package/lib/css-syntax.d.ts +11 -0
- package/lib/css-syntax.js +12 -1
- package/lib/css-tokenizers.d.ts +6 -0
- package/lib/css-tokenizers.js +6 -0
- package/lib/debug.d.ts +3 -0
- package/lib/debug.js +3 -0
- package/lib/defs.d.ts +8 -0
- package/lib/defs.js +8 -0
- package/lib/directive.d.ts +14 -0
- package/lib/directive.js +14 -0
- package/lib/enum.d.ts +11 -0
- package/lib/enum.js +11 -0
- package/lib/get-candidate.d.ts +11 -0
- package/lib/get-candidate.js +11 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +6 -0
- package/lib/keyword-type.d.ts +13 -0
- package/lib/keyword-type.js +13 -0
- package/lib/list.d.ts +13 -0
- package/lib/list.js +13 -0
- package/lib/match-result.d.ts +22 -1
- package/lib/match-result.js +21 -0
- package/lib/number.d.ts +12 -0
- package/lib/number.js +12 -0
- package/lib/primitive/is-float.d.ts +4 -1
- package/lib/primitive/is-float.js +4 -1
- package/lib/primitive/is-int.d.ts +4 -1
- package/lib/primitive/is-int.js +4 -1
- package/lib/primitive/is-non-zero-uint.d.ts +3 -2
- package/lib/primitive/is-non-zero-uint.js +3 -2
- package/lib/primitive/is-quantity.d.ts +5 -3
- package/lib/primitive/is-quantity.js +5 -3
- package/lib/primitive/is-uint.d.ts +5 -1
- package/lib/primitive/is-uint.js +5 -1
- package/lib/primitive/range.d.ts +5 -4
- package/lib/primitive/range.js +5 -4
- package/lib/primitive/split-unit.d.ts +3 -2
- package/lib/primitive/split-unit.js +3 -2
- package/lib/rfc/is-bcp-47.d.ts +2 -0
- package/lib/rfc/is-bcp-47.js +2 -0
- package/lib/token/token-collection.d.ts +107 -2
- package/lib/token/token-collection.js +103 -3
- package/lib/token/token.d.ts +66 -2
- package/lib/token/token.js +66 -2
- package/lib/token/types.d.ts +9 -0
- package/lib/types.d.ts +107 -0
- package/lib/w3c/check-serialized-permissions-policy.d.ts +2 -0
- package/lib/w3c/check-serialized-permissions-policy.js +2 -0
- package/lib/whatwg/check-autocomplete.d.ts +5 -0
- package/lib/whatwg/check-autocomplete.js +6 -0
- package/lib/whatwg/check-datetime/date-string.d.ts +2 -0
- package/lib/whatwg/check-datetime/date-string.js +2 -0
- package/lib/whatwg/check-datetime/datetime-tokens.d.ts +13 -0
- package/lib/whatwg/check-datetime/datetime-tokens.js +13 -0
- package/lib/whatwg/check-datetime/duration-string.d.ts +7 -0
- package/lib/whatwg/check-datetime/duration-string.js +7 -0
- package/lib/whatwg/check-datetime/global-date-and-time-string.d.ts +2 -0
- package/lib/whatwg/check-datetime/global-date-and-time-string.js +2 -0
- package/lib/whatwg/check-datetime/index.d.ts +5 -0
- package/lib/whatwg/check-datetime/index.js +5 -0
- package/lib/whatwg/check-datetime/local-date-and-time-string.d.ts +4 -0
- package/lib/whatwg/check-datetime/local-date-and-time-string.js +4 -0
- package/lib/whatwg/check-datetime/month-string.d.ts +2 -0
- package/lib/whatwg/check-datetime/month-string.js +2 -0
- package/lib/whatwg/check-datetime/time-string.d.ts +2 -0
- package/lib/whatwg/check-datetime/time-string.js +2 -0
- package/lib/whatwg/check-datetime/time-zone-offset-string.d.ts +8 -0
- package/lib/whatwg/check-datetime/time-zone-offset-string.js +8 -0
- package/lib/whatwg/check-datetime/week-string.d.ts +2 -0
- package/lib/whatwg/check-datetime/week-string.js +2 -0
- package/lib/whatwg/check-datetime/year-string.d.ts +2 -0
- package/lib/whatwg/check-datetime/year-string.js +2 -0
- package/lib/whatwg/check-datetime/yearless-date-string.d.ts +2 -0
- package/lib/whatwg/check-datetime/yearless-date-string.js +2 -0
- package/lib/whatwg/check-link-type.d.ts +5 -1
- package/lib/whatwg/check-link-type.js +5 -1
- package/lib/whatwg/check-mime-type.d.ts +4 -1
- package/lib/whatwg/check-mime-type.js +4 -1
- package/lib/whatwg/is-abs-url.d.ts +2 -0
- package/lib/whatwg/is-abs-url.js +2 -0
- package/lib/whatwg/is-browser-context-name.d.ts +2 -2
- package/lib/whatwg/is-browser-context-name.js +2 -2
- package/lib/whatwg/is-custom-element-name.d.ts +1 -1
- package/lib/whatwg/is-custom-element-name.js +1 -1
- package/lib/whatwg/is-itemprop-name.d.ts +1 -0
- package/lib/whatwg/is-itemprop-name.js +1 -0
- package/lib/whatwg/is-navigable-target-name.d.ts +2 -0
- package/lib/whatwg/is-navigable-target-name.js +2 -0
- package/package.json +3 -3
|
@@ -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
|
+
```
|