@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
@@ -82,20 +82,45 @@ const contactableFieldNames = [
82
82
  /**
83
83
  * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete-webauthn
84
84
  */
85
- const webauthnFieldNames = ['webauthn'];
85
+ const webauthnFieldNames = new Set(['webauthn']);
86
86
  const URL_AUTOCOMPLETE = 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete';
87
87
  const URL_ON_OFF = 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute:attr-fe-autocomplete-on-2';
88
88
  const URL_NAMED_GROUP = 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete-section';
89
89
  const URL_PART_OF_ADDRESS = 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete-shipping';
90
90
  const URL_AUTOFILL_FIELD = 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill-field';
91
- const URL_CONTACTABLE_FIELD = 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute:attr-fe-autocomplete-tel';
92
91
  /**
92
+ * Determines the field category and maximum allowed token count
93
+ * based on the last meaningful token (the field name).
94
+ *
95
+ * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill-field
96
+ */
97
+ function determineFieldCategory(value) {
98
+ const lower = value.toLowerCase();
99
+ if (autofillFieldNames.includes(lower)) {
100
+ return { category: 'Normal' };
101
+ }
102
+ if (contactableFieldNames.includes(lower)) {
103
+ return { category: 'Contact' };
104
+ }
105
+ if (webauthnFieldNames.has(lower)) {
106
+ return { category: 'Credential' };
107
+ }
108
+ return null;
109
+ }
110
+ /**
111
+ * Validates the `autocomplete` attribute value according to the WHATWG specification.
112
+ *
113
+ * Uses backward parsing (right-to-left) to match the spec algorithm:
114
+ * 1. Determine field name from the last token
115
+ * 2. Handle `webauthn` credential token and category re-determination
116
+ * 3. Validate optional contacting token (home/work/mobile/fax/pager)
117
+ * 4. Validate optional shipping/billing token
118
+ * 5. Validate optional section-* named group
119
+ * 6. Check maximum token count per category
120
+ *
93
121
  * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete
94
122
  */
95
123
  export const checkAutoComplete = () => value => {
96
- let hasNamedGroup = false;
97
- let hasPartOfAddress = false;
98
- let hasContactingToken = false;
99
124
  const tokens = new TokenCollection(value, {
100
125
  disallowToSurroundBySpaces: false,
101
126
  allowEmpty: false,
@@ -117,21 +142,16 @@ export const checkAutoComplete = () => value => {
117
142
  return listingChecked;
118
143
  }
119
144
  const identTokens = tokens.getIdentTokens();
120
- const headAndTail1 = identTokens.headAndTail();
121
- let { head, tail } = headAndTail1;
122
- if (!head) {
123
- // Never
145
+ if (identTokens.length === 0) {
146
+ // Never TokenCollection.check would catch empty
124
147
  throw new Error('TokenCollection is empty');
125
148
  }
126
- // > When wearing the autofill anchor mantle, the autocomplete attribute,
127
- // > if specified, must have a value that is
128
- // > an ordered set of space-separated tokens consisting of
129
- // > just autofill detail tokens
130
- // > (i.e. the "on" and "off" keywords are not allowed).
131
- if (head.matches(['on', 'off'], true)) {
132
- if (tail[0]) {
133
- acLog('[Unmatched ("%s")] Unexpected pair with "on" or "off": "%s"', value, tail.value);
134
- return tail[0].unmatched({
149
+ const firstToken = identTokens[0];
150
+ // Check for "on" / "off"
151
+ if (firstToken.matches(['on', 'off'], true)) {
152
+ if (identTokens[1]) {
153
+ acLog('[Unmatched ("%s")] Unexpected pair with "on" or "off": "%s"', value, identTokens[1].value);
154
+ return identTokens[1].unmatched({
135
155
  reason: 'extra-token',
136
156
  expects: [
137
157
  {
@@ -144,172 +164,207 @@ export const checkAutoComplete = () => value => {
144
164
  }
145
165
  return matched();
146
166
  }
147
- // > Optionally, a token whose first eight characters are
148
- // > an ASCII case-insensitive match for the string "section-",
149
- // > meaning that the field belongs to the named group.
150
- if (head.matches(namedGroup, true)) {
151
- hasNamedGroup = true;
152
- const sectionToken = tail.search(namedGroup);
153
- if (sectionToken) {
154
- acLog('[Unmatched ("%s")] Deprecated in autofill named group: "%s"', value, sectionToken.value);
155
- return sectionToken.unmatched({
156
- partName: 'autofill named group',
157
- reason: 'duplicated',
158
- ref: URL_NAMED_GROUP,
159
- });
160
- }
161
- const headAndTail2 = tail.headAndTail();
162
- head = headAndTail2.head;
163
- tail = headAndTail2.tail;
164
- if (!head) {
165
- // Missing autofill field name but it is valid
166
- return matched();
167
- }
167
+ // Check for "on" / "off" appearing as the last token in a multi-token context
168
+ const lastIdentToken = identTokens.at(-1);
169
+ if (lastIdentToken.matches(['on', 'off'], true) && identTokens.length > 1) {
170
+ acLog('[Unmatched ("%s")] Extra token "on"/"off" at end: "%s"', value, lastIdentToken.value);
171
+ return lastIdentToken.unmatched({
172
+ reason: 'extra-token',
173
+ expects: [
174
+ {
175
+ type: 'format',
176
+ value: 'autocomplete',
177
+ },
178
+ ],
179
+ ref: URL_AUTOFILL_FIELD,
180
+ });
168
181
  }
169
- // > Optionally, a token that is an ASCII case-insensitive match for
170
- // > one of the following strings:
171
- // > - "shipping", meaning the field is part of the shipping address or contact information
172
- // > - "billing", meaning the field is part of the billing address or contact information
173
- if (head.matches(partOfAddress, true)) {
174
- hasPartOfAddress = true;
175
- const partToken = tail.search(partOfAddress);
176
- if (partToken) {
177
- acLog('[Unmatched ("%s")] Duplicated values: "%s"', value, partToken.value);
178
- return partToken.unmatched({
179
- reason: 'duplicated',
180
- expects: [
181
- {
182
- type: 'format',
183
- value: 'autocomplete',
184
- },
185
- ],
186
- ref: URL_PART_OF_ADDRESS,
182
+ // --- Backward parsing ---
183
+ let index = identTokens.length - 1;
184
+ // Step 1: Determine field category from last token
185
+ const lastToken = identTokens[index];
186
+ const fieldResult = determineFieldCategory(lastToken.value);
187
+ if (!fieldResult) {
188
+ // Last token is not a valid field name
189
+ const allFieldNames = [...autofillFieldNames, ...contactableFieldNames];
190
+ const expects = [
191
+ {
192
+ type: 'common',
193
+ value: 'autofill field name',
194
+ },
195
+ ];
196
+ // If single token, also suggest named group
197
+ if (identTokens.length === 1) {
198
+ expects.unshift({
199
+ type: 'common',
200
+ value: 'autofill named group',
187
201
  });
188
202
  }
189
- const headAndTail3 = tail.headAndTail();
190
- head = headAndTail3.head;
191
- tail = headAndTail3.tail;
192
- if (!head) {
193
- // Missing autofill field name but it is valid
194
- return matched();
203
+ let candidate = getCandidate(lastToken.value, allFieldNames);
204
+ // If single token, also check for section- typo
205
+ if (!candidate && identTokens.length === 1) {
206
+ const [prefix, namedGroupStr] = lastToken.value.split('-');
207
+ const candidatePrefix = getCandidate(prefix, 'section');
208
+ if (candidatePrefix) {
209
+ candidate = `${candidatePrefix}-${namedGroupStr ?? ''}`;
210
+ }
195
211
  }
212
+ acLog('[Unmatched ("%s")] Unexpected token: "%s"', value, lastToken.value);
213
+ return lastToken.unmatched({
214
+ reason: 'unexpected-token',
215
+ expects,
216
+ candidate,
217
+ ref: URL_AUTOFILL_FIELD,
218
+ });
196
219
  }
197
- if (head.matches(contactingTokens, true)) {
198
- hasContactingToken = true;
199
- const contactableFiledToken = tail[0];
200
- if (!contactableFiledToken) {
201
- // Missing autofill field name but it is valid
202
- return matched();
203
- }
204
- if (!contactableFiledToken.matches(contactableFieldNames, true)) {
205
- const candidate = getCandidate(contactableFiledToken.value, contactableFieldNames);
206
- acLog('[Unmatched ("%s")] Unexpected token: "%s"', value, contactableFiledToken.value);
207
- return contactableFiledToken.unmatched({
208
- reason: 'unexpected-token',
209
- expects: contactableFieldNames.map(token => ({
210
- type: 'const',
211
- value: token,
212
- })),
213
- candidate,
214
- ref: URL_CONTACTABLE_FIELD,
215
- });
216
- }
217
- if (tail[1]) {
218
- if (tail[1].matches(webauthnFieldNames)) {
219
- return matched();
220
+ let { category } = fieldResult;
221
+ index--;
222
+ // Step 2: Handle webauthn (Credential category re-determination)
223
+ if (category === 'Credential') {
224
+ // webauthn token consumed; if there are more tokens, re-determine category
225
+ if (index >= 0) {
226
+ const preWebauthnToken = identTokens[index];
227
+ const reResult = determineFieldCategory(preWebauthnToken.value);
228
+ if (reResult && reResult.category !== 'Credential') {
229
+ // Re-determine: the token before webauthn is the actual field name
230
+ category = reResult.category;
231
+ index--;
220
232
  }
221
- const candidate = getCandidate(tail[1].value, webauthnFieldNames);
222
- if (candidate) {
223
- acLog('[Unmatched ("%s")] Unnecessarily token: "%s", Do you mean "%s"? ', value, tail[1].value, candidate);
233
+ else if (reResult && reResult.category === 'Credential') {
234
+ // webauthn webauthn — duplicate caught by TokenCollection.check unique
235
+ acLog('[Unmatched ("%s")] Duplicate webauthn', value);
236
+ return preWebauthnToken.unmatched({
237
+ reason: 'extra-token',
238
+ expects: [{ type: 'format', value: 'autocomplete' }],
239
+ ref: URL_AUTOFILL_FIELD,
240
+ });
224
241
  }
225
242
  else {
226
- acLog('[Unmatched ("%s")] Unnecessarily token: "%s"', value, tail[1].value);
243
+ // Token before webauthn is not a valid field name
244
+ const allFieldNames = [...autofillFieldNames, ...contactableFieldNames];
245
+ const candidate = getCandidate(preWebauthnToken.value, allFieldNames);
246
+ acLog('[Unmatched ("%s")] Unexpected token before webauthn: "%s"', value, preWebauthnToken.value);
247
+ return preWebauthnToken.unmatched({
248
+ reason: 'unexpected-token',
249
+ expects: [{ type: 'common', value: 'autofill field name' }],
250
+ candidate,
251
+ ref: URL_AUTOFILL_FIELD,
252
+ });
227
253
  }
228
- return tail[1].unmatched({
229
- reason: 'extra-token',
230
- expects: [
231
- {
232
- type: 'format',
233
- value: 'autocomplete',
234
- },
235
- ],
236
- ref: URL_AUTOFILL_FIELD,
237
- });
238
254
  }
255
+ else {
256
+ // Standalone "webauthn" is valid
257
+ return matched();
258
+ }
259
+ }
260
+ // No more tokens to validate — only the field name was present
261
+ if (index < 0) {
239
262
  return matched();
240
263
  }
241
- if (head.matches([...autofillFieldNames, ...contactableFieldNames], true)) {
242
- if (tail[0]) {
243
- if (tail[0].matches(webauthnFieldNames)) {
264
+ // Track which optional prefixes have been consumed
265
+ let hasPartOfAddress = false;
266
+ let hasNamedGroup = false;
267
+ // Step 3: If Contact category, optionally consume contacting token
268
+ if (category === 'Contact') {
269
+ const currentToken = identTokens[index];
270
+ if (currentToken.matches(contactingTokens, true)) {
271
+ index--;
272
+ if (index < 0) {
244
273
  return matched();
245
274
  }
246
- const candidate = getCandidate(tail[0].value, webauthnFieldNames);
247
- if (candidate) {
248
- acLog('[Unmatched ("%s")] Unnecessarily token: "%s", Do you mean "%s"? ', value, tail[0].value, candidate);
249
- }
250
- else {
251
- acLog('[Unmatched ("%s")] Unnecessarily token: "%s"', value, tail[0].value);
252
- }
253
- return tail[0].unmatched({
254
- reason: 'extra-token',
275
+ }
276
+ }
277
+ // Step 4: If Normal category, the current token must NOT be a contacting token
278
+ // (contacting tokens are only valid before contactable field names)
279
+ if (category === 'Normal') {
280
+ const currentToken = identTokens[index];
281
+ if (currentToken.matches(contactingTokens, true)) {
282
+ acLog('[Unmatched ("%s")] Contacting token not valid for Normal field', value, currentToken.value);
283
+ return currentToken.unmatched({
284
+ reason: 'unexpected-token',
255
285
  expects: [
286
+ ...partOfAddress.map(token => ({
287
+ type: 'const',
288
+ value: token,
289
+ })),
256
290
  {
257
- type: 'format',
258
- value: 'autocomplete',
291
+ type: 'common',
292
+ value: 'autofill named group',
259
293
  },
260
294
  ],
261
- ref: URL_AUTOFILL_FIELD,
295
+ ref: URL_PART_OF_ADDRESS,
262
296
  });
263
297
  }
264
- return matched();
265
298
  }
266
- if (head.matches(webauthnFieldNames)) {
267
- return matched();
299
+ // Step 5: Optionally consume shipping/billing
300
+ if (index >= 0) {
301
+ const currentToken = identTokens[index];
302
+ if (currentToken.matches(partOfAddress, true)) {
303
+ hasPartOfAddress = true;
304
+ index--;
305
+ if (index < 0) {
306
+ return matched();
307
+ }
308
+ }
268
309
  }
269
- const expects = [
270
- {
271
- type: 'common',
272
- value: 'autofill field name',
273
- },
274
- ];
275
- let candidate;
276
- if (!hasNamedGroup) {
277
- expects.unshift({
278
- type: 'common',
279
- value: 'autofill named group',
280
- });
281
- // Potentially typo a named group
282
- const [prefix, namedGroupStr] = head.value.split('-');
310
+ // Step 6: Optionally consume section-*
311
+ if (index >= 0) {
312
+ const currentToken = identTokens[index];
313
+ if (currentToken.matches(namedGroup, true)) {
314
+ hasNamedGroup = true;
315
+ index--;
316
+ if (index < 0) {
317
+ return matched();
318
+ }
319
+ }
320
+ }
321
+ // Step 7: If there are remaining tokens, they are extra
322
+ if (index >= 0) {
323
+ const extraToken = identTokens[index];
324
+ // Build expects based on what hasn't been consumed yet
325
+ const extraExpects = [];
326
+ if (!hasPartOfAddress && !hasNamedGroup) {
327
+ // Neither shipping/billing nor section-* consumed — could be either
328
+ extraExpects.push(...partOfAddress.map(token => ({
329
+ type: 'const',
330
+ value: token,
331
+ })), {
332
+ type: 'common',
333
+ value: 'autofill field name',
334
+ });
335
+ }
336
+ else if (hasNamedGroup) {
337
+ // Both consumed — nothing expected, pure extra
338
+ extraExpects.push({
339
+ type: 'common',
340
+ value: 'autofill named group',
341
+ });
342
+ }
343
+ else {
344
+ // shipping/billing consumed but section-* not — expect section-*
345
+ extraExpects.push({
346
+ type: 'common',
347
+ value: 'autofill named group',
348
+ });
349
+ }
350
+ // Check if it's a section-* typo
351
+ let candidate;
352
+ const [prefix, namedGroupStr] = extraToken.value.split('-');
283
353
  const candidatePrefix = getCandidate(prefix, 'section');
284
354
  if (candidatePrefix) {
285
355
  candidate = `${candidatePrefix}-${namedGroupStr ?? ''}`;
286
356
  }
357
+ if (!candidate) {
358
+ candidate = getCandidate(extraToken.value, partOfAddress, autofillFieldNames, contactableFieldNames);
359
+ }
360
+ const ref = !hasPartOfAddress && !hasNamedGroup ? URL_AUTOFILL_FIELD : URL_NAMED_GROUP;
361
+ acLog('[Unmatched ("%s")] Extra token: "%s"', value, extraToken.value);
362
+ return extraToken.unmatched({
363
+ reason: 'unexpected-token',
364
+ expects: extraExpects,
365
+ candidate,
366
+ ref,
367
+ });
287
368
  }
288
- else if (!hasPartOfAddress) {
289
- expects.unshift(...[...partOfAddress].reverse().map(token => ({
290
- type: 'const',
291
- value: token,
292
- })));
293
- candidate = getCandidate(head.value, partOfAddress, autofillFieldNames, contactingTokens, contactableFieldNames);
294
- }
295
- else if (!hasContactingToken) {
296
- expects.push(...contactingTokens.map(token => ({
297
- type: 'const',
298
- value: token,
299
- })));
300
- candidate = getCandidate(head.value, autofillFieldNames, contactingTokens, contactableFieldNames);
301
- }
302
- candidate = candidate ?? getCandidate(head.value, autofillFieldNames);
303
- if (candidate) {
304
- acLog('[Unmatched ("%s")] Unexpected token: "%s", Do you mean "%s"? ', value, head.value, candidate);
305
- }
306
- else {
307
- acLog('[Unmatched ("%s")] Unexpected token: "%s"', value, head.value);
308
- }
309
- return head.unmatched({
310
- reason: 'unexpected-token',
311
- expects,
312
- candidate,
313
- ref: URL_AUTOFILL_FIELD,
314
- });
369
+ return matched();
315
370
  };
@@ -1,5 +1,7 @@
1
1
  import type { CustomSyntaxChecker } from '../../types.js';
2
2
  /**
3
+ * Validates a date string in the format `YYYY-MM-DD`.
4
+ *
3
5
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates
4
6
  */
5
7
  export declare const checkDateString: CustomSyntaxChecker;
@@ -2,6 +2,8 @@ import { log } from '../../debug.js';
2
2
  import { TokenCollection } from '../../token/index.js';
3
3
  import { datetimeTokenCheck } from './datetime-tokens.js';
4
4
  /**
5
+ * Validates a date string in the format `YYYY-MM-DD`.
6
+ *
5
7
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates
6
8
  */
7
9
  export const checkDateString = () => function checkDateString(value) {
@@ -1,3 +1,16 @@
1
1
  import type { TokenEachCheck } from '../../token/token-collection.js';
2
+ /**
3
+ * Collection of token check functions for datetime component validation.
4
+ *
5
+ * Each property is a {@link TokenEachCheck} function that validates a specific
6
+ * datetime component (year, month, date, hour, minute, second, etc.)
7
+ * according to the WHATWG specification.
8
+ */
2
9
  export declare const datetimeTokenCheck: Record<'year' | 'month' | 'date' | 'hour' | 'minute' | 'second' | 'secondFractionalPart' | 'week' | 'hyphen' | 'colon' | 'extra' | 'colonOrEnd' | 'decimalPointOrEnd' | 'localDateTimeSeparator' | 'normalizedlocalDateTimeSeparator' | 'plusOrMinusSign' | 'weekSign', TokenEachCheck> & Record<'_year' | '_month', number | null>;
10
+ /**
11
+ * Calculates the maximum ISO week number for a given year.
12
+ *
13
+ * @param year - The year to calculate for
14
+ * @returns The maximum week number (52 or 53) for the year
15
+ */
3
16
  export declare function getMaxWeekNum(year: number): number;
@@ -1,5 +1,12 @@
1
1
  import { log } from '../../debug.js';
2
2
  import { matched, unmatched } from '../../match-result.js';
3
+ /**
4
+ * Collection of token check functions for datetime component validation.
5
+ *
6
+ * Each property is a {@link TokenEachCheck} function that validates a specific
7
+ * datetime component (year, month, date, hour, minute, second, etc.)
8
+ * according to the WHATWG specification.
9
+ */
3
10
  export const datetimeTokenCheck = {
4
11
  /**
5
12
  * Temporary year state
@@ -489,6 +496,12 @@ const daysOfMonth = [
489
496
  // 12
490
497
  31,
491
498
  ];
499
+ /**
500
+ * Calculates the maximum ISO week number for a given year.
501
+ *
502
+ * @param year - The year to calculate for
503
+ * @returns The maximum week number (52 or 53) for the year
504
+ */
492
505
  export function getMaxWeekNum(year) {
493
506
  let date = 31;
494
507
  while (date > 0) {
@@ -496,7 +509,7 @@ export function getMaxWeekNum(year) {
496
509
  const day = d.getDay();
497
510
  d.setDate(d.getDate() + 4 - (day > 0 ? day : 7));
498
511
  const yearStart = new Date(d.getFullYear(), 0, 1);
499
- const weekNo = Math.ceil(((d.valueOf() - yearStart.valueOf()) / 86400000 + 1) / 7);
512
+ const weekNo = Math.ceil(((d.valueOf() - yearStart.valueOf()) / 86_400_000 + 1) / 7);
500
513
  if (weekNo !== 1) {
501
514
  return weekNo;
502
515
  }
@@ -1,6 +1,13 @@
1
1
  import type { CustomSyntaxChecker } from '../../types.js';
2
2
  /**
3
+ * Validates a duration string in ISO 8601-like format (e.g., `PT1H30M`).
4
+ *
3
5
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#durations
4
6
  */
5
7
  export declare const checkDurationISO8601LikeString: CustomSyntaxChecker;
8
+ /**
9
+ * Validates a duration string in component list format (e.g., `1h 30m 5s`).
10
+ *
11
+ * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#durations
12
+ */
6
13
  export declare const checkDurationComponentListString: CustomSyntaxChecker;
@@ -3,6 +3,8 @@ import { matched, unmatched } from '../../match-result.js';
3
3
  import { TokenCollection } from '../../token/index.js';
4
4
  import { datetimeTokenCheck } from './datetime-tokens.js';
5
5
  /**
6
+ * Validates a duration string in ISO 8601-like format (e.g., `PT1H30M`).
7
+ *
6
8
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#durations
7
9
  */
8
10
  export const checkDurationISO8601LikeString = () => function checkDurationISO8601LikeString(value) {
@@ -201,6 +203,11 @@ export const checkDurationISO8601LikeString = () => function checkDurationISO860
201
203
  }
202
204
  return res;
203
205
  };
206
+ /**
207
+ * Validates a duration string in component list format (e.g., `1h 30m 5s`).
208
+ *
209
+ * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#durations
210
+ */
204
211
  export const checkDurationComponentListString = () => function checkDurationComponentListString(value) {
205
212
  log('CHECK: duration-string (duration component list)');
206
213
  if (!value) {
@@ -1,5 +1,7 @@
1
1
  import type { CustomSyntaxChecker } from '../../types.js';
2
2
  /**
3
+ * Validates a global date and time string (date + time + time-zone offset).
4
+ *
3
5
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#global-dates-and-times
4
6
  */
5
7
  export declare const checkGlobalDateAndTimeString: CustomSyntaxChecker;
@@ -4,6 +4,8 @@ import { TokenCollection } from '../../token/index.js';
4
4
  import { datetimeTokenCheck } from './datetime-tokens.js';
5
5
  import { parseTimeZone } from './time-zone-offset-string.js';
6
6
  /**
7
+ * Validates a global date and time string (date + time + time-zone offset).
8
+ *
7
9
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#global-dates-and-times
8
10
  */
9
11
  export const checkGlobalDateAndTimeString = () => function checkGlobalDateAndTimeString(value) {
@@ -1,5 +1,10 @@
1
1
  import type { CustomSyntaxChecker } from '../../types.js';
2
2
  /**
3
+ * Validates a datetime string against all WHATWG date and time formats.
4
+ *
5
+ * Tries each format (date, time, month, yearless date, local/global date-time,
6
+ * time-zone offset, week, year, and duration strings) and returns the best match.
7
+ *
3
8
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates-and-times
4
9
  */
5
10
  export declare const checkDateTime: CustomSyntaxChecker;
@@ -24,6 +24,11 @@ const checks = [
24
24
  checkDurationComponentListString(),
25
25
  ];
26
26
  /**
27
+ * Validates a datetime string against all WHATWG date and time formats.
28
+ *
29
+ * Tries each format (date, time, month, yearless date, local/global date-time,
30
+ * time-zone offset, week, year, and duration strings) and returns the best match.
31
+ *
27
32
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates-and-times
28
33
  */
29
34
  export const checkDateTime = () => value => {
@@ -1,9 +1,13 @@
1
1
  import type { CustomSyntaxChecker } from '../../types.js';
2
2
  /**
3
+ * Validates a local date and time string (date + `T` or space + time, no time-zone).
4
+ *
3
5
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string
4
6
  */
5
7
  export declare const checkLocalDateAndTimeString: CustomSyntaxChecker;
6
8
  /**
9
+ * Validates a normalized local date and time string (uses `T` separator only, omits zero seconds).
10
+ *
7
11
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-normalised-local-date-and-time-string
8
12
  */
9
13
  export declare const checkNormalizedLocalDateAndTimeString: CustomSyntaxChecker;
@@ -2,6 +2,8 @@ import { log } from '../../debug.js';
2
2
  import { TokenCollection } from '../../token/index.js';
3
3
  import { datetimeTokenCheck } from './datetime-tokens.js';
4
4
  /**
5
+ * Validates a local date and time string (date + `T` or space + time, no time-zone).
6
+ *
5
7
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string
6
8
  */
7
9
  export const checkLocalDateAndTimeString = () => function checkLocalDateAndTimeString(value) {
@@ -42,6 +44,8 @@ export const checkLocalDateAndTimeString = () => function checkLocalDateAndTimeS
42
44
  return res;
43
45
  };
44
46
  /**
47
+ * Validates a normalized local date and time string (uses `T` separator only, omits zero seconds).
48
+ *
45
49
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-normalised-local-date-and-time-string
46
50
  */
47
51
  export const checkNormalizedLocalDateAndTimeString = () => function checkNormalizedLocalDateAndTimeString(value) {
@@ -1,5 +1,7 @@
1
1
  import type { CustomSyntaxChecker } from '../../types.js';
2
2
  /**
3
+ * Validates a month string in the format `YYYY-MM`.
4
+ *
3
5
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-month-string
4
6
  */
5
7
  export declare const checkMonthString: CustomSyntaxChecker;
@@ -2,6 +2,8 @@ import { log } from '../../debug.js';
2
2
  import { TokenCollection } from '../../token/index.js';
3
3
  import { datetimeTokenCheck } from './datetime-tokens.js';
4
4
  /**
5
+ * Validates a month string in the format `YYYY-MM`.
6
+ *
5
7
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-month-string
6
8
  */
7
9
  export const checkMonthString = () => function checkMonthString(value) {
@@ -1,5 +1,7 @@
1
1
  import type { CustomSyntaxChecker } from '../../types.js';
2
2
  /**
3
+ * Validates a time string in the format `HH:MM[:SS[.sss]]`.
4
+ *
3
5
  * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#times
4
6
  */
5
7
  export declare const checkTimeString: CustomSyntaxChecker;