@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
@@ -0,0 +1,524 @@
1
+ # Validators
2
+
3
+ ## Overview
4
+
5
+ The `@markuplint/types` package provides a layered validation system for HTML attribute values. Validators are functions that check whether a given string conforms to a specific type defined by web standards (WHATWG, W3C, RFC) or common primitive formats.
6
+
7
+ ### Role in the Type System
8
+
9
+ Validators sit at the core of the type-checking pipeline. When markuplint evaluates an attribute value, it:
10
+
11
+ 1. Resolves the attribute's expected type from the schema (e.g., `DateTime`, `BCP47`, `Uint`)
12
+ 2. Looks up the corresponding validator in the `defs` registry (`src/defs.ts`)
13
+ 3. Invokes the validator, which returns a `Result` -- either `MatchedResult` or `UnmatchedResult` with detailed error information
14
+
15
+ ### Validator Patterns
16
+
17
+ Validators follow two primary patterns:
18
+
19
+ - **`FormattedPrimitiveTypeCreator`** -- A factory that returns a boolean predicate `(value: string) => boolean`. Used for simpler checks like `isInt`, `isAbsURL`, `isCustomElementName`. These are wrapped with the `matches()` helper when registered in `defs.ts`.
20
+ - **`CustomSyntaxChecker`** -- A factory that returns `(value: string) => Result`, providing detailed match/unmatch information with error positions, reasons, and suggestions. Used for complex validators like `checkDateTime`, `checkAutoComplete`, `checkSerializedPermissionsPolicy`.
21
+
22
+ ### Categories
23
+
24
+ Validators are organized into four categories by the specification they implement:
25
+
26
+ | Category | Directory | Description |
27
+ | --------- | ---------------- | -------------------------------------- |
28
+ | Primitive | `src/primitive/` | Basic numeric and string format checks |
29
+ | WHATWG | `src/whatwg/` | HTML Living Standard microsyntaxes |
30
+ | RFC | `src/rfc/` | IETF RFC-defined formats |
31
+ | W3C | `src/w3c/` | W3C specification formats |
32
+
33
+ ---
34
+
35
+ ## Primitive Validators
36
+
37
+ Primitive validators handle fundamental numeric and unit-based format checks. They are used both directly and as building blocks for higher-level validators.
38
+
39
+ **Source:** `src/primitive/index.ts`
40
+
41
+ | Function | File | Description | Parameters | Return |
42
+ | --------------- | ------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------- |
43
+ | `isInt` | `primitive/is-int.ts` | Checks for valid signed integer | `value: string` | `boolean` |
44
+ | `isFloat` | `primitive/is-float.ts` | Checks for valid floating-point number | `value: string` | `boolean` |
45
+ | `isUint` | `primitive/is-uint.ts` | Checks for valid non-negative integer | `value: string`, `options?: { gt?: number }` | `boolean` |
46
+ | `isNonZeroUint` | `primitive/is-non-zero-uint.ts` | Checks for valid non-negative integer > 0 | `value: string` | `boolean` |
47
+ | `isQuantity` | `primitive/is-quantity.ts` | Checks for number + unit suffix | `value: string`, `units: string[]`, `numberType?: 'int' \| 'uint' \| 'float'` | `boolean` |
48
+ | `range` | `primitive/range.ts` | Checks if value is within a numeric range | `value: string`, `from: number`, `to: number` | `boolean` |
49
+ | `splitUnit` | `primitive/split-unit.ts` | Splits a value into numeric and unit parts | `value: string` | `{ num: string, unit: string }` |
50
+
51
+ ### Validation Logic Details
52
+
53
+ **`isInt`** -- Uses the regex `/^-?\d+$/` to match an optional leading minus sign followed by one or more digits. Implements the [WHATWG signed integers](https://html.spec.whatwg.org/dev/common-microsyntaxes.html#signed-integers) microsyntax.
54
+
55
+ ```typescript
56
+ // src/primitive/is-int.ts
57
+ export function isInt(value: string) {
58
+ return /^-?\d+$/.test(value);
59
+ }
60
+ ```
61
+
62
+ **`isFloat`** -- Trims the value and parses it with `Number.parseFloat()`, checking that the result is finite. This allows standard floating-point notation including scientific notation. Loosely based on the [WHATWG floating-point numbers](https://html.spec.whatwg.org/dev/common-microsyntaxes.html#floating-point-numbers) microsyntax. Note that `Number.parseFloat()` is more permissive than the strict WHATWG grammar (e.g., it accepts leading-dot values like `".5"` and ignores trailing non-numeric characters).
63
+
64
+ ```typescript
65
+ // src/primitive/is-float.ts
66
+ export function isFloat(value: string) {
67
+ return value === value.trim() && Number.isFinite(Number.parseFloat(value));
68
+ }
69
+ ```
70
+
71
+ **`isUint`** -- Matches `/^\d+$/` for non-negative integers. Optionally accepts a `gt` constraint to require the parsed value to be strictly greater than a given number. Implements the [WHATWG non-negative integers](https://html.spec.whatwg.org/dev/common-microsyntaxes.html#non-negative-integers) microsyntax.
72
+
73
+ **`isNonZeroUint`** -- Matches `/^\d+$/` and additionally rejects strings consisting entirely of zeros (`/^0+$/`). This ensures the value is a positive integer.
74
+
75
+ **`isQuantity`** -- Composes `splitUnit` with `isInt`/`isUint`/`isFloat` to validate strings like `"10px"` or `"1.5em"`. First splits the value into numeric and unit parts, checks the unit against a list of allowed suffixes (case-insensitive), then validates the numeric part according to the specified `numberType`.
76
+
77
+ **`range`** -- Parses the value as a float and checks whether it falls within the inclusive range `[from, to]`. Returns `false` if the value cannot be parsed as a number.
78
+
79
+ **`splitUnit`** -- Uses the regex `/(^-?\.\d+|^-?\d+(?:\.\d+(?:e[+-]\d+)?)?)([a-z]+$)/i` to separate a value like `"10px"` into `{ num: "10", unit: "px" }`. If no unit suffix is found, returns `{ num: value, unit: "" }`.
80
+
81
+ ---
82
+
83
+ ## WHATWG Validators
84
+
85
+ WHATWG validators implement the microsyntaxes defined in the [HTML Living Standard](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html).
86
+
87
+ ### DateTime Subsystem
88
+
89
+ The DateTime subsystem validates all date and time formats defined by the WHATWG specification. It is the most complex validator group, consisting of 12 individual format checkers that share a common token validation layer.
90
+
91
+ **Entry point:** `src/whatwg/check-datetime/index.ts`
92
+
93
+ The top-level `checkDateTime` function tries all formats using `checkMultiTypes` and returns the best match:
94
+
95
+ ```typescript
96
+ // src/whatwg/check-datetime/index.ts
97
+ const checks = [
98
+ checkDateString(),
99
+ checkTimeString(),
100
+ checkMonthString(),
101
+ checkYearlessDateString(),
102
+ checkLocalDateAndTimeString(),
103
+ checkNormalizedLocalDateAndTimeString(),
104
+ checkTimeZoneOffsetString(),
105
+ checkGlobalDateAndTimeString(),
106
+ checkWeekString(),
107
+ checkYearString(),
108
+ checkDurationISO8601LikeString(),
109
+ checkDurationComponentListString(),
110
+ ];
111
+
112
+ export const checkDateTime: CustomSyntaxChecker = () => value => {
113
+ return checkMultiTypes(value, checks);
114
+ };
115
+ ```
116
+
117
+ #### DateTime Format Checkers
118
+
119
+ | Function | File | Format | Example | Spec Reference |
120
+ | --------------------------------------- | -------------------------------- | ----------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
121
+ | `checkDateString` | `date-string.ts` | `YYYY-MM-DD` | `2024-01-15` | [Dates](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates) |
122
+ | `checkMonthString` | `month-string.ts` | `YYYY-MM` | `2024-01` | [Months](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-month-string) |
123
+ | `checkWeekString` | `week-string.ts` | `YYYY-Www` | `2024-W03` | [Weeks](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#weeks) |
124
+ | `checkTimeString` | `time-string.ts` | `HH:MM[:SS[.sss]]` | `14:30:00` | [Times](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#times) |
125
+ | `checkYearlessDateString` | `yearless-date-string.ts` | `MM-DD` | `01-15` | [Yearless dates](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#yearless-dates) |
126
+ | `checkYearString` | `year-string.ts` | `YYYY` (4+ digits, > 0) | `2024` | [Common microsyntaxes](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html) |
127
+ | `checkLocalDateAndTimeString` | `local-date-and-time-string.ts` | `YYYY-MM-DDThh:mm[:ss[.sss]]` | `2024-01-15T14:30` | [Local dates and times](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string) |
128
+ | `checkNormalizedLocalDateAndTimeString` | `local-date-and-time-string.ts` | Normalized local date-time | `2024-01-15T14:30` | [Normalized local dates and times](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-normalised-local-date-and-time-string) |
129
+ | `checkGlobalDateAndTimeString` | `global-date-and-time-string.ts` | Date + time + time-zone | `2024-01-15T14:30:00Z` | [Global dates and times](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#global-dates-and-times) |
130
+ | `checkTimeZoneOffsetString` | `time-zone-offset-string.ts` | `Z` or `+HH:MM` / `-HH:MM` | `+09:00` | [Time zones](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#time-zones) |
131
+ | `checkDurationISO8601LikeString` | `duration-string.ts` | ISO 8601-like (`PnDTnHnMnS`) | `PT1H30M` | [Durations](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#durations) |
132
+ | `checkDurationComponentListString` | `duration-string.ts` | Component list (`1h 30m 5s`) | `1h 30m 5s` | [Durations](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#durations) |
133
+
134
+ #### Shared Token Definitions
135
+
136
+ All datetime checkers share a common token validation layer defined in `datetime-tokens.ts`. The `datetimeTokenCheck` object provides reusable `TokenEachCheck` functions for each datetime component:
137
+
138
+ ```mermaid
139
+ graph TD
140
+ A[checkDateTime] --> B[checkMultiTypes]
141
+ B --> C[checkDateString]
142
+ B --> D[checkTimeString]
143
+ B --> E[checkMonthString]
144
+ B --> F[checkYearlessDateString]
145
+ B --> G[checkLocalDateAndTimeString]
146
+ B --> H[checkNormalizedLocalDateAndTimeString]
147
+ B --> I[checkGlobalDateAndTimeString]
148
+ B --> J[checkTimeZoneOffsetString]
149
+ B --> K[checkWeekString]
150
+ B --> L[checkYearString]
151
+ B --> M[checkDurationISO8601LikeString]
152
+ B --> N[checkDurationComponentListString]
153
+
154
+ C --> O[datetimeTokenCheck]
155
+ D --> O
156
+ E --> O
157
+ F --> O
158
+ G --> O
159
+ H --> O
160
+ I --> O
161
+ J --> O
162
+ K --> O
163
+ L --> O
164
+ M --> O
165
+ N --> O
166
+
167
+ O --> P[year]
168
+ O --> Q[month]
169
+ O --> R[date]
170
+ O --> S[hour]
171
+ O --> T[minute]
172
+ O --> U[second]
173
+ O --> V[secondFractionalPart]
174
+ O --> W[week]
175
+ O --> X[hyphen / colon / separators]
176
+ ```
177
+
178
+ The shared token checks validate:
179
+
180
+ | Token Check | Validates | Constraints |
181
+ | ---------------------------------- | ---------------------- | --------------------------------------------- |
182
+ | `year` | Year component | 4+ ASCII digits, value > 0 |
183
+ | `month` | Month component | Exactly 2 digits, 1--12 |
184
+ | `date` | Day-of-month component | Exactly 2 digits, 1--maxday (leap-year aware) |
185
+ | `hour` | Hour component | Exactly 2 digits, 0--23 |
186
+ | `minute` | Minute component | Exactly 2 digits, 0--59 |
187
+ | `second` | Second component | Exactly 2 digits, 0--59 |
188
+ | `secondFractionalPart` | Fractional seconds | 1--3 ASCII digits |
189
+ | `week` | ISO week number | Exactly 2 digits, 1--maxweek for the year |
190
+ | `hyphen` | `-` separator | Exactly U+002D |
191
+ | `colon` | `:` separator | Exactly U+003A |
192
+ | `colonOrEnd` | `:` or end of input | Optional colon |
193
+ | `decimalPointOrEnd` | `.` or end of input | Optional period |
194
+ | `localDateTimeSeparator` | `T` or space | Date-time boundary |
195
+ | `normalizedlocalDateTimeSeparator` | `T` only | Strict normalized form |
196
+ | `plusOrMinusSign` | `+` or `-` | Time-zone sign |
197
+ | `weekSign` | `W` | Week string marker |
198
+ | `extra` | Trailing content | Must be empty (rejects extra tokens) |
199
+
200
+ The `datetimeTokenCheck` object also maintains `_year` and `_month` as mutable state during validation, enabling the `date` checker to compute the correct maximum day for the given year and month (including leap year logic).
201
+
202
+ ### Autocomplete Validator
203
+
204
+ **Source:** `src/whatwg/check-autocomplete.ts`
205
+
206
+ Validates the `autocomplete` attribute value according to the [WHATWG autofill specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete).
207
+
208
+ The validator uses **backward parsing** (right-to-left), matching the WHATWG specification algorithm. The attribute value is parsed as an ordered set of space-separated tokens (ASCII case-insensitive). It recognizes:
209
+
210
+ - **Keywords:** `on`, `off` (standalone, no other tokens allowed)
211
+ - **Named groups:** Tokens starting with `section-` (optional prefix)
212
+ - **Address parts:** `shipping`, `billing` (optional)
213
+ - **Contacting tokens:** `home`, `work`, `mobile`, `fax`, `pager` (optional, only valid before contactable field names)
214
+ - **Autofill field names:** `name`, `given-name`, `postal-code`, `cc-number`, etc. (44 names)
215
+ - **Contactable field names:** `tel`, `tel-country-code`, `email`, `impp`, etc. (10 names)
216
+ - **WebAuthn:** `webauthn` (optional trailing token with category re-determination)
217
+
218
+ The backward parsing algorithm:
219
+
220
+ 1. Determine the field name from the **last** token and classify into Normal, Contact, or Credential category
221
+ 2. Handle `webauthn` credential token: if present, consume it and re-determine the category from the preceding token
222
+ 3. Optionally consume a contacting token (only valid for Contact category fields)
223
+ 4. Reject contacting tokens preceding Normal category fields
224
+ 5. Optionally consume `shipping`/`billing`
225
+ 6. Optionally consume `section-*` named group
226
+ 7. Report any remaining tokens as extra
227
+
228
+ The grammar enforced is: `[section-*] [shipping|billing] [home|work|...] <field-name> [webauthn]`. A field name is always required -- prefix-only values (e.g., `section-foo` or `shipping` alone) are rejected. Duplicate tokens, incorrect ordering, and unrecognized tokens produce detailed error results with Levenshtein-distance-based typo correction candidates.
229
+
230
+ ### Link Type Validator
231
+
232
+ **Source:** `src/whatwg/check-link-type.ts`
233
+
234
+ Validates link type values (the `rel` attribute) against the [WHATWG link types registry](https://html.spec.whatwg.org/multipage/links.html#linkTypes) and the [Microformats existing-rel-values](https://microformats.org/wiki/existing-rel-values) registry.
235
+
236
+ The validator is parameterized by element context:
237
+
238
+ | Option | Context | Description |
239
+ | ----------------- | -------------------- | ------------------------------------------ |
240
+ | `el: 'link'` | `<link>` in `<head>` | All link types allowed on the link element |
241
+ | `el: 'body link'` | `<link>` in `<body>` | Only link types with `body-ok` flag |
242
+ | `el: 'a, area'` | `<a>`, `<area>` | Link types allowed on anchor/area elements |
243
+ | `el: 'form'` | `<form>` | Link types allowed on form elements |
244
+
245
+ The validator explicitly rejects keywords from the Microformats dropped, rejected, non-HTML, and dropped-without-prejudice lists. It builds an enumeration of allowed keywords for the given context and delegates to `checkList` for final validation.
246
+
247
+ This is registered in `defs.ts` as four separate type definitions: `LinkTypeForLinkElement`, `LinkTypeForLinkElementInBody`, `LinkTypeForAnchorAndAreaElement`, and `LinkTypeForFormElement`.
248
+
249
+ ### MIME Type Validator
250
+
251
+ **Source:** `src/whatwg/check-mime-type.ts`
252
+
253
+ Validates MIME type strings according to the [WHATWG MIME Sniffing specification](https://mimesniff.spec.whatwg.org/#valid-mime-type).
254
+
255
+ The validator integrates the `whatwg-mimetype` npm package for parsing. It:
256
+
257
+ 1. Attempts to parse the value using `MIMEType.parse()`
258
+ 2. If parsing succeeds, checks whether the serialized essence matches the input
259
+ 3. Optionally restricts to MIME types with no parameters (via the `withoutParameters` option)
260
+ 4. Reports extra tokens or syntax errors with appropriate candidates
261
+
262
+ ```typescript
263
+ // Invocation in defs.ts
264
+ MIMEType: {
265
+ ref: 'https://mimesniff.spec.whatwg.org/#valid-mime-type',
266
+ is: checkMIMEType(),
267
+ },
268
+ ```
269
+
270
+ ### URL and Name Validators
271
+
272
+ These validators implement simple predicate-based checks for various WHATWG-defined name formats.
273
+
274
+ | Function | File | Validates | Logic |
275
+ | ----------------------- | ------------------------------------ | ---------------------------------- | ----------------------------------------------------------------------------------- |
276
+ | `isAbsURL` | `whatwg/is-abs-url.ts` | Absolute URL | Uses `new URL(value)` constructor; returns `false` on `ERR_INVALID_URL` |
277
+ | `isBrowserContextName` | `whatwg/is-browser-context-name.ts` | Browsing context name (deprecated) | Non-empty and does not start with `_` |
278
+ | `isCustomElementName` | `whatwg/is-custom-element-name.ts` | Custom element name | Must start with `[a-z]`, contain `-`, use only PCENChar, and not be a reserved name |
279
+ | `isItempropName` | `whatwg/is-itemprop-name.ts` | Itemprop property name | Must not contain `:`, `.`, or space |
280
+ | `isNavigableTargetName` | `whatwg/is-navigable-target-name.ts` | Navigable target name | Non-empty, no ASCII tab/newline, does not start with `_` |
281
+
282
+ **`isCustomElementName`** is the most complex, implementing the [PotentialCustomElementName](https://html.spec.whatwg.org/multipage/custom-elements.html#prod-potentialcustomelementname) production rule. It rejects the eight reserved names (`annotation-xml`, `color-profile`, `font-face`, `font-face-src`, `font-face-uri`, `font-face-format`, `font-face-name`, `missing-glyph`), requires a leading ASCII lowercase alpha, a mandatory hyphen, and validates all characters against the PCENChar character class.
283
+
284
+ **`isBrowserContextName`** is deprecated in favor of `isNavigableTargetName`.
285
+
286
+ All of these are `FormattedPrimitiveTypeCreator` factories -- they return `() => (value: string) => boolean`.
287
+
288
+ ---
289
+
290
+ ## RFC Validators
291
+
292
+ ### BCP 47 (Language Tags)
293
+
294
+ **Source:** `src/rfc/is-bcp-47.ts`
295
+
296
+ Validates language tags according to [BCP 47](https://tools.ietf.org/rfc/bcp/bcp47.html) (RFC 5646 + RFC 4647).
297
+
298
+ The validator integrates the `bcp-47` npm package. It parses the value and checks that either a valid `language` subtag or a `privateuse` subtag (e.g., `x-default`) is present:
299
+
300
+ ```typescript
301
+ // src/rfc/is-bcp-47.ts
302
+ export const isBCP47: FormattedPrimitiveTypeCreator = () => {
303
+ return value => {
304
+ const { language, privateuse } = parse(value);
305
+ return !!language || (privateuse != null && privateuse.length > 0);
306
+ };
307
+ };
308
+ ```
309
+
310
+ This is registered in `defs.ts` as the `BCP47` type, used for the `lang` and `hreflang` attributes on HTML elements.
311
+
312
+ ---
313
+
314
+ ## W3C Validators
315
+
316
+ ### Serialized Permissions Policy
317
+
318
+ **Source:** `src/w3c/check-serialized-permissions-policy.ts`
319
+
320
+ Validates serialized permissions policy strings according to the [W3C Permissions Policy specification](https://w3c.github.io/webappsec-permissions-policy/#serialized-permissions-policy).
321
+
322
+ The validator parses the ABNF grammar:
323
+
324
+ ```abnf
325
+ serialized-permissions-policy = serialized-policy-directive *(";" serialized-policy-directive)
326
+ serialized-policy-directive = feature-identifier [RWS allow-list]
327
+ feature-identifier = 1*( ALPHA / DIGIT / "-")
328
+ allow-list = allow-list-value *(RWS allow-list-value)
329
+ allow-list-value = serialized-origin / "*" / "'self'" / "'src'" / "'none'"
330
+ ```
331
+
332
+ > **Note:** The ABNF above reflects the implementation's grammar, which makes `allow-list` optional (`[RWS allow-list]`). The W3C specification defines `serialized-policy-directive = feature-identifier RWS allow-list` with a mandatory `allow-list`. This deviation is intentional -- the implementation accepts feature identifiers without an explicit allow-list.
333
+
334
+ Key validation steps:
335
+
336
+ 1. Splits the value on `;` into policy directives
337
+ 2. For each directive, extracts the feature identifier and validates it matches `/^[\da-z-]+$/i`
338
+ 3. For each allow-list value, checks against the keywords `*`, `'self'`, `'src'`, `'none'`, or validates as a serialized origin
339
+ 4. Serialized origins are validated using the `URL` constructor, rejecting pathnames, queries, hashes, usernames, passwords, and characters that must be percent-encoded (`'`, `*`, `,`, `;`)
340
+
341
+ ---
342
+
343
+ ## Adding New Validators
344
+
345
+ Follow these steps to add a new validator to the system.
346
+
347
+ ### Step 1: Create the Validator File
348
+
349
+ Place the file in the appropriate directory based on the specification:
350
+
351
+ - `src/primitive/` for basic format checks
352
+ - `src/whatwg/` for WHATWG HTML spec validators
353
+ - `src/rfc/` for RFC-defined formats
354
+ - `src/w3c/` for W3C specification formats
355
+
356
+ **Template for a `FormattedPrimitiveTypeCreator`:**
357
+
358
+ ```typescript
359
+ // src/whatwg/is-my-format.ts
360
+ import type { FormattedPrimitiveTypeCreator } from '../types.js';
361
+
362
+ /**
363
+ * Checks whether a string is a valid my-format value.
364
+ *
365
+ * @see https://spec.example.org/my-format
366
+ */
367
+ export const isMyFormat: FormattedPrimitiveTypeCreator = () => {
368
+ return value => {
369
+ // Validation logic returning boolean
370
+ return /^[a-z]+-[a-z]+$/.test(value);
371
+ };
372
+ };
373
+ ```
374
+
375
+ **Template for a `CustomSyntaxChecker`:**
376
+
377
+ ```typescript
378
+ // src/whatwg/check-my-syntax.ts
379
+ import type { CustomSyntaxChecker } from '../types.js';
380
+
381
+ import { matched, unmatched } from '../match-result.js';
382
+
383
+ /**
384
+ * Validates a my-syntax value.
385
+ *
386
+ * @see https://spec.example.org/my-syntax
387
+ */
388
+ export const checkMySyntax: CustomSyntaxChecker = () => value => {
389
+ if (!value) {
390
+ return unmatched(value, 'empty-token');
391
+ }
392
+ // Validation logic returning Result
393
+ return matched();
394
+ };
395
+ ```
396
+
397
+ ### Step 2: Export from the Directory Index
398
+
399
+ If you placed the validator in a directory with an `index.ts`, add the export:
400
+
401
+ ```typescript
402
+ // src/primitive/index.ts
403
+ export { isMyFormat } from './is-my-format.js';
404
+ ```
405
+
406
+ ### Step 3: Register in defs.ts
407
+
408
+ Import the validator and add it to the `defs` object:
409
+
410
+ ```typescript
411
+ // src/defs.ts
412
+ import { isMyFormat } from './whatwg/is-my-format.js';
413
+ // or
414
+ import { checkMySyntax } from './whatwg/check-my-syntax.js';
415
+
416
+ export const defs: Defs = {
417
+ // ... existing definitions ...
418
+
419
+ // For FormattedPrimitiveTypeCreator (wrap with matches()):
420
+ MyFormat: {
421
+ ref: 'https://spec.example.org/my-format',
422
+ expects: [
423
+ {
424
+ type: 'format',
425
+ value: 'my format',
426
+ },
427
+ ],
428
+ is: matches(isMyFormat()),
429
+ },
430
+
431
+ // For CustomSyntaxChecker (use directly):
432
+ MySyntax: {
433
+ ref: 'https://spec.example.org/my-syntax',
434
+ expects: [
435
+ {
436
+ type: 'format',
437
+ value: 'my syntax',
438
+ },
439
+ ],
440
+ is: checkMySyntax(),
441
+ },
442
+ };
443
+ ```
444
+
445
+ ### Step 4: Add Tests
446
+
447
+ Create a test file alongside the validator:
448
+
449
+ ```typescript
450
+ // src/whatwg/is-my-format.spec.ts
451
+ import { isMyFormat } from './is-my-format.js';
452
+
453
+ const check = isMyFormat();
454
+
455
+ test('valid values', () => {
456
+ expect(check('foo-bar')).toBe(true);
457
+ expect(check('hello-world')).toBe(true);
458
+ });
459
+
460
+ test('invalid values', () => {
461
+ expect(check('')).toBe(false);
462
+ expect(check('UPPER-CASE')).toBe(false);
463
+ });
464
+ ```
465
+
466
+ ### Concrete Example: Adding a Hypothetical `isDataURL` Validator
467
+
468
+ Suppose you need to validate `data:` URLs specifically.
469
+
470
+ **1. Create the file:**
471
+
472
+ ```typescript
473
+ // src/whatwg/is-data-url.ts
474
+ import type { FormattedPrimitiveTypeCreator } from '../types.js';
475
+
476
+ /**
477
+ * Checks whether a string is a valid data URL.
478
+ *
479
+ * @see https://fetch.spec.whatwg.org/#data-urls
480
+ */
481
+ export const isDataURL: FormattedPrimitiveTypeCreator = () => {
482
+ return value => {
483
+ return /^data:[^,]*,/.test(value);
484
+ };
485
+ };
486
+ ```
487
+
488
+ **2. Register in `defs.ts`:**
489
+
490
+ ```typescript
491
+ import { isDataURL } from './whatwg/is-data-url.js';
492
+
493
+ // Inside the defs object:
494
+ DataURL: {
495
+ ref: 'https://fetch.spec.whatwg.org/#data-urls',
496
+ expects: [
497
+ {
498
+ type: 'format',
499
+ value: 'data URL',
500
+ },
501
+ ],
502
+ is: matches(isDataURL()),
503
+ },
504
+ ```
505
+
506
+ **3. Add tests:**
507
+
508
+ ```typescript
509
+ // src/whatwg/is-data-url.spec.ts
510
+ import { isDataURL } from './is-data-url.js';
511
+
512
+ const check = isDataURL();
513
+
514
+ test('valid data URLs', () => {
515
+ expect(check('data:text/plain,Hello')).toBe(true);
516
+ expect(check('data:text/html,<h1>Hello</h1>')).toBe(true);
517
+ expect(check('data:image/png;base64,abc123...')).toBe(true);
518
+ });
519
+
520
+ test('invalid data URLs', () => {
521
+ expect(check('https://example.com')).toBe(false);
522
+ expect(check('data:')).toBe(false);
523
+ });
524
+ ```
@@ -1,10 +1,90 @@
1
- import type { Type, Result, List, CustomSyntax, CustomCssSyntax, Enum, KeywordDefinedType, Number, Directive, Defs } from './types.js';
1
+ import type { Type, Result, List, CustomSyntax, CustomCssSyntax, Enum, KeywordDefinedType, Number, Directive, Pattern, Defs } from './types.js';
2
2
  import type { ReadonlyDeep } from 'type-fest';
3
+ /**
4
+ * Validates a string value against a specified type definition using the provided
5
+ * set of custom type definitions. This is the core type-checking dispatcher that
6
+ * routes validation to the appropriate checker based on the type's structure
7
+ * (keyword, list, enum, number, or directive).
8
+ *
9
+ * @param value - The string value to validate
10
+ * @param type - The type definition to validate against
11
+ * @param defs - A map of custom type definitions (both CSS syntax and custom syntax) used for resolving keyword types
12
+ * @param ref - An optional reference identifier used for error reporting context
13
+ * @param cache - Whether to use cached results for repeated checks with the same inputs
14
+ * @returns A result indicating whether the value matches the type, or details about the mismatch
15
+ * @throws Error if the type does not match any known type structure
16
+ */
3
17
  export declare function checkBase(value: string, type: ReadonlyDeep<Type>, defs: Defs, ref?: string, cache?: boolean): Result;
18
+ /**
19
+ * Determines whether a type definition is a keyword-based type.
20
+ * Keyword types are represented as plain strings (e.g., CSS syntax names
21
+ * like `"<color>"` or extended types like `"URL"`).
22
+ *
23
+ * @param type - The type definition to test
24
+ * @returns True if the type is a keyword-defined type string
25
+ */
4
26
  export declare function isKeyword(type: ReadonlyDeep<Type>): type is ReadonlyDeep<KeywordDefinedType>;
27
+ /**
28
+ * Determines whether a type definition represents a list type.
29
+ * List types define space-separated or comma-separated token sequences
30
+ * and are identified by having a `separator` property.
31
+ *
32
+ * @param type - The type definition to test
33
+ * @returns True if the type is a list definition
34
+ */
5
35
  export declare function isList(type: ReadonlyDeep<Type>): type is ReadonlyDeep<List>;
36
+ /**
37
+ * Determines whether a type definition represents an enumerated type.
38
+ * Enum types define a fixed set of allowed string values and are
39
+ * identified by having an `enum` property.
40
+ *
41
+ * @param type - The type definition to test
42
+ * @returns True if the type is an enum definition
43
+ */
6
44
  export declare function isEnum(type: ReadonlyDeep<Type>): type is ReadonlyDeep<Enum>;
45
+ /**
46
+ * Determines whether a type definition represents a numeric type.
47
+ * Number types specify either `float` or `integer` validation with
48
+ * optional range constraints, and are identified by having a `type`
49
+ * property set to one of those values.
50
+ *
51
+ * @param type - The type definition to test
52
+ * @returns True if the type is a number definition
53
+ */
7
54
  export declare function isNumber(type: ReadonlyDeep<Type>): type is ReadonlyDeep<Number>;
55
+ /**
56
+ * Determines whether a type definition represents a directive type.
57
+ * Directive types allow separating and individually validating parts of
58
+ * an attribute value, and are identified by having a `directive` property.
59
+ *
60
+ * @param type - The type definition to test
61
+ * @returns True if the type is a directive definition
62
+ */
8
63
  export declare function isDirective(type: ReadonlyDeep<Type>): type is ReadonlyDeep<Directive>;
64
+ /**
65
+ * Determines whether a type definition represents a pattern type.
66
+ * Pattern types validate attribute values against a regular expression
67
+ * or plain string, and are identified by having a `pattern` property.
68
+ *
69
+ * @param type - The type definition to test
70
+ * @returns True if the type is a pattern definition
71
+ */
72
+ export declare function isPattern(type: ReadonlyDeep<Type>): type is ReadonlyDeep<Pattern>;
73
+ /**
74
+ * Determines whether a custom type definition uses CSS syntax matching.
75
+ * CSS syntax definitions are either plain strings or objects with a `syntax`
76
+ * property that specifies CSS value definition syntax for matching.
77
+ *
78
+ * @param type - The custom type definition to test
79
+ * @returns True if the definition uses CSS syntax matching
80
+ */
9
81
  export declare function isCSSSyntax(type: CustomSyntax | CustomCssSyntax): type is CustomCssSyntax;
82
+ /**
83
+ * Determines whether a custom type definition uses a programmatic custom
84
+ * syntax checker rather than CSS syntax matching. This is the inverse
85
+ * of {@link isCSSSyntax}.
86
+ *
87
+ * @param type - The custom type definition to test
88
+ * @returns True if the definition uses a custom programmatic checker function
89
+ */
10
90
  export declare function isCustomSyntax(type: CustomSyntax | CustomCssSyntax): type is CustomSyntax;