@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.
- package/ARCHITECTURE.ja.md +256 -0
- package/ARCHITECTURE.md +256 -0
- package/CHANGELOG.md +18 -2
- 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 +524 -0
- package/docs/validators.md +524 -0
- package/lib/check-base.d.ts +81 -1
- package/lib/check-base.js +87 -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 +26 -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 +10 -1
- package/lib/index.js +9 -1
- 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/pattern.d.ts +12 -0
- package/lib/pattern.js +33 -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 +4 -2
- package/lib/token/token-collection.d.ts +108 -3
- package/lib/token/token-collection.js +110 -3
- package/lib/token/token.d.ts +66 -2
- package/lib/token/token.js +89 -21
- package/lib/token/types.d.ts +9 -0
- package/lib/types.d.ts +108 -1
- package/lib/types.schema.d.ts +4 -1
- 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 +10 -0
- package/lib/whatwg/check-autocomplete.js +214 -159
- 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 +14 -1
- 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 +108 -1
- package/lib/whatwg/check-link-type.js +17 -7
- package/lib/whatwg/check-mime-type.d.ts +4 -1
- package/lib/whatwg/check-mime-type.js +6 -3
- package/lib/whatwg/is-abs-url.d.ts +2 -0
- package/lib/whatwg/is-abs-url.js +3 -4
- 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 +8 -6
- 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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
//
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
acLog('[Unmatched ("%s")]
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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: '
|
|
258
|
-
value: '
|
|
291
|
+
type: 'common',
|
|
292
|
+
value: 'autofill named group',
|
|
259
293
|
},
|
|
260
294
|
],
|
|
261
|
-
ref:
|
|
295
|
+
ref: URL_PART_OF_ADDRESS,
|
|
262
296
|
});
|
|
263
297
|
}
|
|
264
|
-
return matched();
|
|
265
298
|
}
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
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
|
-
|
|
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()) /
|
|
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;
|