@markuplint/types 5.0.0-rc.2 → 5.0.0-rc.5
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/CHANGELOG.md +38 -0
- package/README.md +83 -75
- package/lib/check-multi-types.d.ts +2 -9
- package/lib/check-multi-types.js +2 -9
- package/lib/css-defs.d.ts +0 -8
- package/lib/css-defs.js +0 -8
- package/lib/css-overrides.d.ts +2 -5
- package/lib/css-overrides.js +2 -5
- package/lib/css-syntax.d.ts +0 -11
- package/lib/css-syntax.js +6 -18
- package/lib/css-tokenizers.d.ts +0 -6
- package/lib/css-tokenizers.js +0 -6
- package/lib/debug.d.ts +0 -3
- package/lib/debug.js +0 -3
- package/lib/defs.d.ts +0 -8
- package/lib/defs.js +308 -37
- package/lib/directive.d.ts +0 -14
- package/lib/directive.js +0 -14
- package/lib/enum.d.ts +0 -11
- package/lib/enum.js +0 -11
- package/lib/keyword-type.d.ts +0 -13
- package/lib/keyword-type.js +0 -13
- package/lib/list.d.ts +0 -13
- package/lib/list.js +0 -13
- package/lib/match-result.d.ts +0 -21
- package/lib/match-result.js +0 -21
- package/lib/number.d.ts +0 -12
- package/lib/number.js +0 -12
- package/lib/primitive/is-float.d.ts +3 -4
- package/lib/primitive/is-float.js +3 -4
- package/lib/primitive/is-int.d.ts +0 -5
- package/lib/primitive/is-int.js +0 -5
- package/lib/primitive/is-non-zero-uint.d.ts +0 -6
- package/lib/primitive/is-non-zero-uint.js +0 -6
- package/lib/primitive/is-quantity.d.ts +0 -8
- package/lib/primitive/is-quantity.js +0 -8
- package/lib/primitive/is-uint.d.ts +0 -6
- package/lib/primitive/is-uint.js +0 -6
- package/lib/primitive/range.d.ts +0 -8
- package/lib/primitive/range.js +0 -8
- package/lib/primitive/split-unit.d.ts +0 -6
- package/lib/primitive/split-unit.js +0 -6
- package/lib/rfc/is-bcp-47.d.ts +24 -1
- package/lib/rfc/is-bcp-47.js +88 -3
- package/lib/token/token-collection.d.ts +1 -111
- package/lib/token/token-collection.js +0 -108
- package/lib/token/token.d.ts +1 -73
- package/lib/token/token.js +0 -72
- package/lib/token/types.d.ts +0 -9
- package/lib/types.schema.d.ts +1 -1
- package/lib/w3c/check-content-security-policy.d.ts +18 -0
- package/lib/w3c/check-content-security-policy.js +363 -0
- package/lib/w3c/check-serialized-permissions-policy.d.ts +0 -2
- package/lib/w3c/check-serialized-permissions-policy.js +0 -2
- package/lib/whatwg/check-autocomplete.d.ts +26 -9
- package/lib/whatwg/check-autocomplete.js +42 -15
- package/lib/whatwg/check-datetime/date-string-with-optional-time.d.ts +11 -0
- package/lib/whatwg/check-datetime/date-string-with-optional-time.js +16 -0
- package/lib/whatwg/check-datetime/date-string.d.ts +0 -2
- package/lib/whatwg/check-datetime/date-string.js +0 -2
- package/lib/whatwg/check-datetime/datetime-tokens.d.ts +0 -13
- package/lib/whatwg/check-datetime/datetime-tokens.js +0 -16
- package/lib/whatwg/check-datetime/duration-string.d.ts +0 -4
- package/lib/whatwg/check-datetime/duration-string.js +0 -4
- package/lib/whatwg/check-datetime/global-date-and-time-string.d.ts +0 -2
- package/lib/whatwg/check-datetime/global-date-and-time-string.js +4 -4
- package/lib/whatwg/check-datetime/index.d.ts +0 -5
- package/lib/whatwg/check-datetime/index.js +0 -5
- package/lib/whatwg/check-datetime/local-date-and-time-string.d.ts +0 -4
- package/lib/whatwg/check-datetime/local-date-and-time-string.js +0 -4
- package/lib/whatwg/check-datetime/month-string.d.ts +0 -2
- package/lib/whatwg/check-datetime/month-string.js +0 -2
- package/lib/whatwg/check-datetime/time-string.d.ts +0 -2
- package/lib/whatwg/check-datetime/time-string.js +0 -2
- package/lib/whatwg/check-datetime/time-zone-offset-string.d.ts +0 -8
- package/lib/whatwg/check-datetime/time-zone-offset-string.js +0 -8
- package/lib/whatwg/check-datetime/week-string.d.ts +0 -2
- package/lib/whatwg/check-datetime/week-string.js +0 -2
- package/lib/whatwg/check-datetime/year-string.d.ts +0 -2
- package/lib/whatwg/check-datetime/year-string.js +0 -2
- package/lib/whatwg/check-datetime/yearless-date-string.d.ts +0 -2
- package/lib/whatwg/check-datetime/yearless-date-string.js +0 -2
- package/lib/whatwg/check-email.js +0 -2
- package/lib/whatwg/check-http-equiv-content-type.d.ts +12 -0
- package/lib/whatwg/check-http-equiv-content-type.js +32 -0
- package/lib/whatwg/check-http-equiv-refresh.d.ts +21 -0
- package/lib/whatwg/check-http-equiv-refresh.js +106 -0
- package/lib/whatwg/check-media-query-list.d.ts +61 -0
- package/lib/whatwg/check-media-query-list.js +484 -0
- package/lib/whatwg/check-mime-type.d.ts +0 -4
- package/lib/whatwg/check-mime-type.js +55 -3
- package/lib/whatwg/check-simple-color.d.ts +2 -4
- package/lib/whatwg/check-simple-color.js +2 -4
- package/lib/whatwg/check-url.d.ts +15 -7
- package/lib/whatwg/check-url.js +333 -25
- package/lib/whatwg/is-abs-url.d.ts +0 -2
- package/lib/whatwg/is-abs-url.js +0 -2
- package/lib/whatwg/is-browser-context-name.d.ts +0 -2
- package/lib/whatwg/is-browser-context-name.js +0 -2
- package/lib/whatwg/is-itemprop-name.d.ts +0 -2
- package/lib/whatwg/is-itemprop-name.js +0 -2
- package/lib/whatwg/is-navigable-target-name.d.ts +0 -2
- package/lib/whatwg/is-navigable-target-name.js +0 -2
- package/package.json +6 -5
- package/types.schema.json +9 -0
- package/ARCHITECTURE.ja.md +0 -256
- package/ARCHITECTURE.md +0 -256
- package/SKILL.md +0 -100
- package/docs/check-pipeline.ja.md +0 -494
- package/docs/check-pipeline.md +0 -494
- package/docs/token-system.ja.md +0 -584
- package/docs/token-system.md +0 -584
- package/docs/type-system.ja.md +0 -623
- package/docs/type-system.md +0 -623
- package/docs/validators.ja.md +0 -536
- package/docs/validators.md +0 -536
package/lib/defs.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { checkMultiTypes } from './check-multi-types.js';
|
|
2
|
+
import { cssSyntaxMatch } from './css-syntax.js';
|
|
2
3
|
import { getCandidate } from './get-candidate.js';
|
|
3
4
|
import { matched, matches, unmatched } from './match-result.js';
|
|
4
5
|
import { splitUnit, isFloat, isUint, isInt } from './primitive/index.js';
|
|
5
6
|
import { isBCP47 } from './rfc/is-bcp-47.js';
|
|
6
7
|
import { Token, TokenCollection } from './token/index.js';
|
|
8
|
+
import { checkContentSecurityPolicy } from './w3c/check-content-security-policy.js';
|
|
7
9
|
import { checkSerializedPermissionsPolicy } from './w3c/check-serialized-permissions-policy.js';
|
|
8
10
|
import { checkAutoComplete } from './whatwg/check-autocomplete.js';
|
|
9
11
|
import { checkDateTime } from './whatwg/check-datetime/index.js';
|
|
10
12
|
import { checkDateString } from './whatwg/check-datetime/date-string.js';
|
|
13
|
+
import { checkDateStringWithOptionalTime } from './whatwg/check-datetime/date-string-with-optional-time.js';
|
|
11
14
|
import { checkLocalDateAndTimeString } from './whatwg/check-datetime/local-date-and-time-string.js';
|
|
12
15
|
import { checkMonthString } from './whatwg/check-datetime/month-string.js';
|
|
13
16
|
import { checkTimeString } from './whatwg/check-datetime/time-string.js';
|
|
14
17
|
import { checkWeekString } from './whatwg/check-datetime/week-string.js';
|
|
18
|
+
import { checkHTTPEquivContentType } from './whatwg/check-http-equiv-content-type.js';
|
|
19
|
+
import { checkHTTPEquivRefresh } from './whatwg/check-http-equiv-refresh.js';
|
|
20
|
+
import { checkMediaQueryList, findGeneralEnclosed } from './whatwg/check-media-query-list.js';
|
|
15
21
|
import { checkMIMEType } from './whatwg/check-mime-type.js';
|
|
16
22
|
import { checkURL } from './whatwg/check-url.js';
|
|
17
23
|
import { isAbsURL } from './whatwg/is-abs-url.js';
|
|
@@ -22,14 +28,27 @@ import { isNavigableTargetName } from './whatwg/is-navigable-target-name.js';
|
|
|
22
28
|
import { checkLinkType } from './whatwg/check-link-type.js';
|
|
23
29
|
import { isEmail } from './whatwg/check-email.js';
|
|
24
30
|
import { isSimpleColor } from './whatwg/check-simple-color.js';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// Hoist the URL Living Standard checker once so type entries that wrap it
|
|
32
|
+
// (e.g. `BaseURL`, which adds the `<base>`-specific data:/javascript: filter
|
|
33
|
+
// on top) do not pay closure-construction cost per attribute value.
|
|
34
|
+
//
|
|
35
|
+
// URL-family selection guide — when wiring an attribute in spec data, map
|
|
36
|
+
// the HTML Living Standard's exact production wording to the type name:
|
|
37
|
+
//
|
|
38
|
+
// - "valid URL potentially surrounded by spaces" → `URL`
|
|
39
|
+
// (empty is valid; it resolves to the document's own URL)
|
|
40
|
+
// - "valid non-empty URL potentially surrounded by spaces" → `NonEmptyURL`
|
|
41
|
+
// (the `src`-attribute production)
|
|
42
|
+
// - `<base href>` ("set the frozen base URL") → `BaseURL`
|
|
43
|
+
// - "valid absolute URL" as a per-token constraint in a list (e.g.
|
|
44
|
+
// `itemtype`) → `AbsoluteURL`
|
|
45
|
+
// - "if specified and not empty, must be a valid absolute URL potentially
|
|
46
|
+
// surrounded by ASCII whitespace" (HTML LS §4.10.5.1.7,
|
|
47
|
+
// `<input type=url value>`) → `AbsoluteURLOrEmpty`
|
|
48
|
+
//
|
|
49
|
+
// All of these layer their per-attribute constraint on top of the shared
|
|
50
|
+
// `checkURLOnce` so URL LS validation-error coverage stays uniform.
|
|
51
|
+
const checkURLOnce = checkURL();
|
|
33
52
|
export const defs = {
|
|
34
53
|
Any: {
|
|
35
54
|
ref: '',
|
|
@@ -225,6 +244,16 @@ export const defs = {
|
|
|
225
244
|
],
|
|
226
245
|
is: checkDateString(),
|
|
227
246
|
},
|
|
247
|
+
DateStringWithOptionalTime: {
|
|
248
|
+
ref: 'https://html.spec.whatwg.org/multipage/edits.html#attr-mod-datetime',
|
|
249
|
+
expects: [
|
|
250
|
+
{
|
|
251
|
+
type: 'format',
|
|
252
|
+
value: 'date string with optional time',
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
is: checkDateStringWithOptionalTime(),
|
|
256
|
+
},
|
|
228
257
|
TimeString: {
|
|
229
258
|
ref: 'https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#times',
|
|
230
259
|
expects: [
|
|
@@ -301,11 +330,6 @@ export const defs = {
|
|
|
301
330
|
is: value => (value === '' ? matched() : matches(isBCP47(), { reason: 'unexpected-token' })(value)),
|
|
302
331
|
},
|
|
303
332
|
/**
|
|
304
|
-
* Validates a URL (potentially surrounded by spaces) per WHATWG URL Standard.
|
|
305
|
-
* Uses `new URL()` for structural parsing plus strict checks for illegal
|
|
306
|
-
* whitespace, malformed percent-encoding, and C0 control characters.
|
|
307
|
-
* Relative URLs are resolved against a dummy base for syntax validation.
|
|
308
|
-
*
|
|
309
333
|
* Previously this was always-matched ("NO IMPLEMENT NEVER") because
|
|
310
334
|
* relative URLs accept almost any character string. After investigating
|
|
311
335
|
* nu-html-checker's galimatias parser, we found that resolving relative
|
|
@@ -317,7 +341,26 @@ export const defs = {
|
|
|
317
341
|
*/
|
|
318
342
|
URL: {
|
|
319
343
|
ref: 'https://html.spec.whatwg.org/multipage/urls-and-fetching.html#valid-url-potentially-surrounded-by-spaces',
|
|
320
|
-
is:
|
|
344
|
+
is: checkURLOnce,
|
|
345
|
+
},
|
|
346
|
+
/**
|
|
347
|
+
* Validates a "valid non-empty URL potentially surrounded by spaces". HTML
|
|
348
|
+
* LS uses this stricter production for `src` attributes on
|
|
349
|
+
* `<audio>`/`<embed>`/`<iframe>`/`<img>`/`<input type=image>`/`<script>`/
|
|
350
|
+
* `<source>`/`<track>`/`<video>` — the URL token MUST contain at least one
|
|
351
|
+
* non-whitespace character.
|
|
352
|
+
*
|
|
353
|
+
* @see https://html.spec.whatwg.org/multipage/urls-and-fetching.html#valid-non-empty-url
|
|
354
|
+
* @see https://html.spec.whatwg.org/multipage/urls-and-fetching.html#valid-non-empty-url-potentially-surrounded-by-spaces
|
|
355
|
+
*/
|
|
356
|
+
NonEmptyURL: {
|
|
357
|
+
ref: 'https://html.spec.whatwg.org/multipage/urls-and-fetching.html#valid-non-empty-url-potentially-surrounded-by-spaces',
|
|
358
|
+
is(value) {
|
|
359
|
+
if (value.replaceAll(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, '') === '') {
|
|
360
|
+
return unmatched(value, 'unexpected-token');
|
|
361
|
+
}
|
|
362
|
+
return checkURLOnce(value);
|
|
363
|
+
},
|
|
321
364
|
},
|
|
322
365
|
/**
|
|
323
366
|
* Fail if it inclides "data" or "javascript" scheme.
|
|
@@ -331,21 +374,57 @@ export const defs = {
|
|
|
331
374
|
BaseURL: {
|
|
332
375
|
ref: 'https://html.spec.whatwg.org/multipage/semantics.html#set-the-frozen-base-url',
|
|
333
376
|
is(value) {
|
|
334
|
-
|
|
335
|
-
|
|
377
|
+
// `<base href>` is forbidden from declaring `data:` / `javascript:`
|
|
378
|
+
// schemes (HTML LS — "set the frozen base URL"). Surface that
|
|
379
|
+
// constraint first because it is `<base>`-specific, then delegate
|
|
380
|
+
// to the shared URL Living Standard validator so all URL LS
|
|
381
|
+
// validation errors (invalid-credentials, scheme-missing-solidus,
|
|
382
|
+
// invalid-reverse-solidus, etc.) are reported uniformly with
|
|
383
|
+
// `URL` / `HTTPSchemaURL` / etc.
|
|
384
|
+
const normalised = value.toLowerCase().trim();
|
|
385
|
+
if (normalised.startsWith('data:') || normalised.startsWith('javascript:')) {
|
|
336
386
|
return unmatched(value, 'unexpected-token');
|
|
337
387
|
}
|
|
338
|
-
return
|
|
388
|
+
return checkURLOnce(value);
|
|
339
389
|
},
|
|
340
390
|
},
|
|
341
391
|
AbsoluteURL: {
|
|
342
392
|
ref: 'https://url.spec.whatwg.org/#syntax-url-absolute',
|
|
343
|
-
is
|
|
393
|
+
is(value) {
|
|
394
|
+
// `AbsoluteURL` is used by per-token list types (e.g. `itemtype`),
|
|
395
|
+
// where each token must be a valid absolute URL. Reject
|
|
396
|
+
// non-absolute / unparsable values first, then run the full URL
|
|
397
|
+
// Living Standard validator so callers get the same
|
|
398
|
+
// auto-correction error coverage as `URL` (invalid-credentials,
|
|
399
|
+
// invalid-reverse-solidus, multiple `#`, etc.).
|
|
400
|
+
if (!isAbsURL()(value)) {
|
|
401
|
+
return unmatched(value, 'unexpected-token');
|
|
402
|
+
}
|
|
403
|
+
return checkURLOnce(value);
|
|
404
|
+
},
|
|
344
405
|
},
|
|
345
406
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
407
|
+
* Validates `<input type="url" value="...">` per HTML LS §4.10.5.1.7:
|
|
408
|
+
* "if specified and not empty, must have a value that is a valid absolute
|
|
409
|
+
* URL potentially surrounded by ASCII whitespace." Accepts empty values
|
|
410
|
+
* (no default URL) and absolute URLs that pass URL Living Standard
|
|
411
|
+
* validation; rejects relative URLs and URL LS validation errors.
|
|
348
412
|
*
|
|
413
|
+
* @see https://html.spec.whatwg.org/multipage/input.html#url-state-(type=url)
|
|
414
|
+
*/
|
|
415
|
+
AbsoluteURLOrEmpty: {
|
|
416
|
+
ref: 'https://html.spec.whatwg.org/multipage/input.html#url-state-(type=url)',
|
|
417
|
+
is(value) {
|
|
418
|
+
if (value.replaceAll(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, '') === '') {
|
|
419
|
+
return matched();
|
|
420
|
+
}
|
|
421
|
+
if (!isAbsURL()(value)) {
|
|
422
|
+
return unmatched(value, 'unexpected-token');
|
|
423
|
+
}
|
|
424
|
+
return checkURLOnce(value);
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
/**
|
|
349
428
|
* Note: The SRI spec also allows `?options` suffix (e.g., `sha256-abc?ct=...`)
|
|
350
429
|
* but this is not widely used and not tested by nu-validator.
|
|
351
430
|
*
|
|
@@ -380,7 +459,18 @@ export const defs = {
|
|
|
380
459
|
value: 'hash name',
|
|
381
460
|
},
|
|
382
461
|
],
|
|
383
|
-
|
|
462
|
+
// HTML LS: "A string is a valid hash-name reference [...] if the string
|
|
463
|
+
// consists of a U+0023 NUMBER SIGN (#) followed by a string which
|
|
464
|
+
// exactly matches the value of the name attribute [...]". The spec
|
|
465
|
+
// requires the name part to be non-empty: `#` alone is not a valid
|
|
466
|
+
// hash-name reference.
|
|
467
|
+
is: value => {
|
|
468
|
+
if (value.length < 2)
|
|
469
|
+
return unmatched(value, 'unexpected-token');
|
|
470
|
+
if (value[0] !== '#')
|
|
471
|
+
return unmatched(value, 'unexpected-token');
|
|
472
|
+
return matched();
|
|
473
|
+
},
|
|
384
474
|
},
|
|
385
475
|
OneCodePointChar: {
|
|
386
476
|
ref: 'https://html.spec.whatwg.org/multipage/interaction.html#the-accesskey-attribute',
|
|
@@ -521,6 +611,52 @@ export const defs = {
|
|
|
521
611
|
],
|
|
522
612
|
is: checkMIMEType(),
|
|
523
613
|
},
|
|
614
|
+
MediaQueryList: {
|
|
615
|
+
ref: 'https://www.w3.org/TR/mediaqueries-5/',
|
|
616
|
+
expects: [
|
|
617
|
+
{
|
|
618
|
+
type: 'format',
|
|
619
|
+
value: 'media query list',
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
is: checkMediaQueryList(),
|
|
623
|
+
},
|
|
624
|
+
// `HTTPEquivRefresh` / `HTTPEquivContentType` / `ContentSecurityPolicy` are
|
|
625
|
+
// selected at runtime by a `ConditionalAttributeType[]` entry on
|
|
626
|
+
// `meta.content` in `@markuplint/html-spec`, keyed by the `http-equiv`
|
|
627
|
+
// value (`refresh` / `content-type` / `content-security-policy`). Other
|
|
628
|
+
// `http-equiv` values fall through to `Any` via the resolver in
|
|
629
|
+
// `@markuplint/rules`.
|
|
630
|
+
HTTPEquivRefresh: {
|
|
631
|
+
ref: 'https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-refresh',
|
|
632
|
+
expects: [
|
|
633
|
+
{
|
|
634
|
+
type: 'format',
|
|
635
|
+
value: 'refresh directive (a non-negative integer, optionally followed by a URL)',
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
is: checkHTTPEquivRefresh(),
|
|
639
|
+
},
|
|
640
|
+
HTTPEquivContentType: {
|
|
641
|
+
ref: 'https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type',
|
|
642
|
+
expects: [
|
|
643
|
+
{
|
|
644
|
+
type: 'format',
|
|
645
|
+
value: 'content-type directive (text/html; charset=<encoding>)',
|
|
646
|
+
},
|
|
647
|
+
],
|
|
648
|
+
is: checkHTTPEquivContentType(),
|
|
649
|
+
},
|
|
650
|
+
ContentSecurityPolicy: {
|
|
651
|
+
ref: 'https://www.w3.org/TR/CSP3/#framework-policy',
|
|
652
|
+
expects: [
|
|
653
|
+
{
|
|
654
|
+
type: 'format',
|
|
655
|
+
value: 'Content Security Policy (a semicolon-separated list of directives)',
|
|
656
|
+
},
|
|
657
|
+
],
|
|
658
|
+
is: checkContentSecurityPolicy(),
|
|
659
|
+
},
|
|
524
660
|
ItemProp: {
|
|
525
661
|
ref: 'https://html.spec.whatwg.org/multipage/microdata.html#names:-the-itemprop-attribute',
|
|
526
662
|
expects: [
|
|
@@ -548,6 +684,12 @@ export const defs = {
|
|
|
548
684
|
const images = value.split(',');
|
|
549
685
|
let hasWidth = false;
|
|
550
686
|
let hasDensity = false;
|
|
687
|
+
// Spec: "Let candidates be an initially empty source set. ... If candidates already has an
|
|
688
|
+
// entry whose descriptor's width is equal to width [or pixel density], then a parse error
|
|
689
|
+
// must be reported." Track normalised numeric values so 1x / 1.0x / omitted-descriptor
|
|
690
|
+
// (implicit 1x) all collide.
|
|
691
|
+
const usedWidths = new Set();
|
|
692
|
+
const usedDensities = new Set();
|
|
551
693
|
for (const image of images) {
|
|
552
694
|
// image candidate string
|
|
553
695
|
const [url, , descriptor, ...tail] = new TokenCollection(image.trim(), {
|
|
@@ -564,6 +706,22 @@ export const defs = {
|
|
|
564
706
|
],
|
|
565
707
|
});
|
|
566
708
|
}
|
|
709
|
+
// HTML LS srcset: each image candidate's URL must be a valid
|
|
710
|
+
// non-empty URL. css-tree's <url> token allows things like bare
|
|
711
|
+
// `http:` (URL LS `special-scheme-missing-following-solidus`),
|
|
712
|
+
// so we parse it explicitly via WHATWG URL with a dummy base.
|
|
713
|
+
try {
|
|
714
|
+
new URL(url.value, 'https://example.com/');
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
if (error instanceof TypeError) {
|
|
718
|
+
return url.unmatched({
|
|
719
|
+
reason: 'unexpected-token',
|
|
720
|
+
expects: [{ type: 'format', value: 'valid non-empty URL' }],
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
throw error;
|
|
724
|
+
}
|
|
567
725
|
if (descriptor) {
|
|
568
726
|
const { num, unit } = splitUnit(descriptor.value);
|
|
569
727
|
switch (unit) {
|
|
@@ -579,6 +737,13 @@ export const defs = {
|
|
|
579
737
|
],
|
|
580
738
|
});
|
|
581
739
|
}
|
|
740
|
+
const w = Number(num);
|
|
741
|
+
if (usedWidths.has(w)) {
|
|
742
|
+
return unmatched(value, 'duplicated', {
|
|
743
|
+
expects: [{ type: 'format', value: 'unique width descriptor' }],
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
usedWidths.add(w);
|
|
582
747
|
hasWidth = true;
|
|
583
748
|
break;
|
|
584
749
|
}
|
|
@@ -594,6 +759,13 @@ export const defs = {
|
|
|
594
759
|
],
|
|
595
760
|
});
|
|
596
761
|
}
|
|
762
|
+
const d = Number(num);
|
|
763
|
+
if (usedDensities.has(d)) {
|
|
764
|
+
return unmatched(value, 'duplicated', {
|
|
765
|
+
expects: [{ type: 'format', value: 'unique pixel density descriptor' }],
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
usedDensities.add(d);
|
|
597
769
|
hasDensity = true;
|
|
598
770
|
break;
|
|
599
771
|
}
|
|
@@ -614,7 +786,14 @@ export const defs = {
|
|
|
614
786
|
}
|
|
615
787
|
}
|
|
616
788
|
else {
|
|
617
|
-
//
|
|
789
|
+
// Spec: "If image candidate's descriptor is the empty string, [...] add an
|
|
790
|
+
// image source with a pixel density of 1.0."
|
|
791
|
+
if (usedDensities.has(1)) {
|
|
792
|
+
return unmatched(value, 'duplicated', {
|
|
793
|
+
expects: [{ type: 'format', value: 'unique pixel density descriptor' }],
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
usedDensities.add(1);
|
|
618
797
|
hasDensity = true;
|
|
619
798
|
}
|
|
620
799
|
if (tail[0]) {
|
|
@@ -649,21 +828,90 @@ export const defs = {
|
|
|
649
828
|
value: '<source-size-list>',
|
|
650
829
|
},
|
|
651
830
|
],
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
831
|
+
is(value) {
|
|
832
|
+
const result = cssSyntaxMatch(value, {
|
|
833
|
+
ref: 'https://html.spec.whatwg.org/multipage/images.html#sizes-attributes',
|
|
834
|
+
syntax: {
|
|
835
|
+
apply: '<source-size-list>',
|
|
836
|
+
def: {
|
|
837
|
+
'source-size-list': '[ <source-size># , ]? <source-size-value>',
|
|
838
|
+
'source-size': '<media-condition> <source-size-value> | auto',
|
|
839
|
+
/**
|
|
840
|
+
* > Percentages are not allowed in a `<source-size-value>`,
|
|
841
|
+
* > to avoid confusion about what it would be relative to.
|
|
842
|
+
* > The 'vw' unit can be used for sizes relative to the viewport width.
|
|
843
|
+
*
|
|
844
|
+
* `<length>` doesn't allow percentages.
|
|
845
|
+
* @see https://csstree.github.io/docs/syntax/#Type:length
|
|
846
|
+
*/
|
|
847
|
+
'source-size-value': '<length> | auto',
|
|
848
|
+
},
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
if (!result.matched) {
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
// HTML LS imposes a non-negative additional constraint on
|
|
855
|
+
// <source-size-value> ("a <length> that does not contain
|
|
856
|
+
// percentages [...] and that is greater than or equal to zero")
|
|
857
|
+
// beyond what css-tree's <length> grammar checks. A
|
|
858
|
+
// <source-size-value> always appears either at the start of the
|
|
859
|
+
// list, immediately after the `,` separator, or immediately after
|
|
860
|
+
// the `)` that closes a <media-condition>; a `-` followed by a
|
|
861
|
+
// digit at any of those boundaries is a negative length token.
|
|
862
|
+
//
|
|
863
|
+
// Regex breakdown: boundary (start-of-string | `,` | `)`)
|
|
864
|
+
// → optional whitespace → literal `-` → optional whitespace
|
|
865
|
+
// → digit. Note this only catches negative numbers that *start*
|
|
866
|
+
// a <source-size-value>; positive scientific notation like
|
|
867
|
+
// `1e-5px` is unaffected because the `-` is preceded by `e`.
|
|
868
|
+
const negativeAtSourceSizeBoundary = /(?:^|[,)])\s*-\s*\d/u;
|
|
869
|
+
if (negativeAtSourceSizeBoundary.test(value)) {
|
|
870
|
+
return unmatched(value, 'out-of-range', {
|
|
871
|
+
expects: [{ type: 'format', value: 'non-negative <length>' }],
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
// Each top-level `(<any-value>?)` group inside the sizes list is a
|
|
875
|
+
// `<media-condition>` per HTML LS. Media Queries Level 5 §3 forbids
|
|
876
|
+
// `<general-enclosed>` in author stylesheets, but css-tree accepts
|
|
877
|
+
// it grammatically (that's the point of the fallback — future syntax
|
|
878
|
+
// must parse in older UAs). Extract each balanced-parens group and
|
|
879
|
+
// route it through the media-query walker so malformed feature forms
|
|
880
|
+
// like `(min-width:)` (empty value) and `(123)` (non-ident content)
|
|
881
|
+
// surface as syntax errors rather than silently accepted GE matches.
|
|
882
|
+
let depth = 0;
|
|
883
|
+
let groupStart = -1;
|
|
884
|
+
for (let i = 0; i < value.length; i++) {
|
|
885
|
+
const c = value[i];
|
|
886
|
+
if (c === '(') {
|
|
887
|
+
if (depth === 0) {
|
|
888
|
+
// A `(` preceded by an identifier / digit character is a CSS
|
|
889
|
+
// function call (`clamp(...)`, `min(...)`, `calc(...)`, `env(...)`)
|
|
890
|
+
// inside a `<source-size-value>`, not a `<media-condition>`.
|
|
891
|
+
// Skip: its contents are function arguments, not a media query.
|
|
892
|
+
const prev = i > 0 ? (value[i - 1] ?? '') : '';
|
|
893
|
+
const isFunctionCall = /[\w-]/u.test(prev);
|
|
894
|
+
groupStart = isFunctionCall ? -1 : i;
|
|
895
|
+
}
|
|
896
|
+
depth++;
|
|
897
|
+
}
|
|
898
|
+
else if (c === ')') {
|
|
899
|
+
depth--;
|
|
900
|
+
if (depth === 0 && groupStart >= 0) {
|
|
901
|
+
const groupText = value.slice(groupStart, i + 1);
|
|
902
|
+
const hit = findGeneralEnclosed(groupText);
|
|
903
|
+
if (hit) {
|
|
904
|
+
return new Token(hit.raw, groupStart + hit.offset, value).unmatched({
|
|
905
|
+
reason: 'syntax-error',
|
|
906
|
+
expects: [{ type: 'format', value: 'media condition' }],
|
|
907
|
+
partName: `unknown or malformed media condition ${JSON.stringify(hit.raw)} (Media Queries Level 5 §3 forbids <general-enclosed> in author stylesheets)`,
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
groupStart = -1;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return matched();
|
|
667
915
|
},
|
|
668
916
|
},
|
|
669
917
|
IconSize: {
|
|
@@ -719,6 +967,29 @@ export const defs = {
|
|
|
719
967
|
ref: 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete',
|
|
720
968
|
is: checkAutoComplete(),
|
|
721
969
|
},
|
|
970
|
+
/**
|
|
971
|
+
* `autocomplete` variant for elements where the `webauthn` token is not
|
|
972
|
+
* valid. Per HTML LS §attr-fe-autocomplete-webauthn: "webauthn is only
|
|
973
|
+
* valid for input and textarea elements." Applied to `button`,
|
|
974
|
+
* `fieldset`, `object`, `output`, and `select`.
|
|
975
|
+
*
|
|
976
|
+
* @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete-webauthn
|
|
977
|
+
*/
|
|
978
|
+
AutoCompleteNoWebauthn: {
|
|
979
|
+
ref: 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete-webauthn',
|
|
980
|
+
is: checkAutoComplete({ noWebauthn: true }),
|
|
981
|
+
},
|
|
982
|
+
/**
|
|
983
|
+
* `autocomplete` variant for the autofill anchor mantle: on `<input
|
|
984
|
+
* type=hidden>`, the `on` / `off` keywords are not allowed and the
|
|
985
|
+
* value must consist of just autofill detail tokens.
|
|
986
|
+
*
|
|
987
|
+
* @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill-anchor-mantle
|
|
988
|
+
*/
|
|
989
|
+
AutoCompleteAnchorMantle: {
|
|
990
|
+
ref: 'https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill-anchor-mantle',
|
|
991
|
+
is: checkAutoComplete({ anchorMantle: true }),
|
|
992
|
+
},
|
|
722
993
|
Accept: {
|
|
723
994
|
ref: 'https://html.spec.whatwg.org/multipage/input.html#attr-input-accept',
|
|
724
995
|
expects: [
|
package/lib/directive.d.ts
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
1
|
import type { Result, Directive, Defs } from './types.js';
|
|
2
2
|
import type { ReadonlyDeep } from 'type-fest';
|
|
3
|
-
/**
|
|
4
|
-
* Checks a value against a directive type definition.
|
|
5
|
-
*
|
|
6
|
-
* A directive type consists of a prefix pattern (string or regex) followed by
|
|
7
|
-
* a token value. This function extracts the token portion after the directive
|
|
8
|
-
* prefix and validates it against the token type.
|
|
9
|
-
*
|
|
10
|
-
* @param value - The string value to check
|
|
11
|
-
* @param type - The directive type definition containing directive patterns and token type
|
|
12
|
-
* @param defs - The type definitions registry for resolving nested types
|
|
13
|
-
* @param ref - Optional reference URL for the unmatched result
|
|
14
|
-
* @param cache - Whether to use cached results (defaults to `true`)
|
|
15
|
-
* @returns The validation result
|
|
16
|
-
*/
|
|
17
3
|
export declare function checkDirective(value: string, type: ReadonlyDeep<Directive>, defs: Defs, ref?: string, cache?: boolean): Result;
|
package/lib/directive.js
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import { regexParser } from '@markuplint/shared';
|
|
2
2
|
import { checkBase } from './check-base.js';
|
|
3
3
|
import { matched, unmatched } from './match-result.js';
|
|
4
|
-
/**
|
|
5
|
-
* Checks a value against a directive type definition.
|
|
6
|
-
*
|
|
7
|
-
* A directive type consists of a prefix pattern (string or regex) followed by
|
|
8
|
-
* a token value. This function extracts the token portion after the directive
|
|
9
|
-
* prefix and validates it against the token type.
|
|
10
|
-
*
|
|
11
|
-
* @param value - The string value to check
|
|
12
|
-
* @param type - The directive type definition containing directive patterns and token type
|
|
13
|
-
* @param defs - The type definitions registry for resolving nested types
|
|
14
|
-
* @param ref - Optional reference URL for the unmatched result
|
|
15
|
-
* @param cache - Whether to use cached results (defaults to `true`)
|
|
16
|
-
* @returns The validation result
|
|
17
|
-
*/
|
|
18
4
|
export function checkDirective(value, type, defs, ref, cache = true) {
|
|
19
5
|
const unmatches = [];
|
|
20
6
|
for (const directive of type.directive) {
|
package/lib/enum.d.ts
CHANGED
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
import type { Result } from './types.js';
|
|
2
2
|
import type { Enum } from './types.schema.js';
|
|
3
3
|
import type { ReadonlyDeep } from 'type-fest';
|
|
4
|
-
/**
|
|
5
|
-
* Checks whether a value matches one of the allowed enumeration values.
|
|
6
|
-
*
|
|
7
|
-
* By default, comparison is case-insensitive and surrounding spaces are not allowed.
|
|
8
|
-
* These behaviors can be configured through the type definition.
|
|
9
|
-
*
|
|
10
|
-
* @param value - The string value to check
|
|
11
|
-
* @param type - The enum type definition containing the allowed values and options
|
|
12
|
-
* @param ref - Optional reference URL for the unmatched result
|
|
13
|
-
* @returns The validation result
|
|
14
|
-
*/
|
|
15
4
|
export declare function checkEnum(value: string, type: ReadonlyDeep<Enum>, ref?: string): Result;
|
package/lib/enum.js
CHANGED
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
import { matched, unmatched } from './match-result.js';
|
|
2
|
-
/**
|
|
3
|
-
* Checks whether a value matches one of the allowed enumeration values.
|
|
4
|
-
*
|
|
5
|
-
* By default, comparison is case-insensitive and surrounding spaces are not allowed.
|
|
6
|
-
* These behaviors can be configured through the type definition.
|
|
7
|
-
*
|
|
8
|
-
* @param value - The string value to check
|
|
9
|
-
* @param type - The enum type definition containing the allowed values and options
|
|
10
|
-
* @param ref - Optional reference URL for the unmatched result
|
|
11
|
-
* @returns The validation result
|
|
12
|
-
*/
|
|
13
2
|
export function checkEnum(value, type, ref) {
|
|
14
3
|
const disallowToSurroundBySpaces = type.disallowToSurroundBySpaces ?? true;
|
|
15
4
|
const caseInsensitive = type.caseInsensitive ?? true;
|
package/lib/keyword-type.d.ts
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
1
|
import type { Defs, Result } from './types.js';
|
|
2
2
|
import type { KeywordDefinedType } from './types.schema.js';
|
|
3
|
-
/**
|
|
4
|
-
* Checks a value against a keyword-defined type or CSS syntax type.
|
|
5
|
-
*
|
|
6
|
-
* Looks up the type in the definitions registry and validates the value.
|
|
7
|
-
* If the type is not found in custom definitions, falls back to CSS syntax matching.
|
|
8
|
-
* Results are cached by default for performance.
|
|
9
|
-
*
|
|
10
|
-
* @param value - The string value to check
|
|
11
|
-
* @param type - The keyword type identifier to look up
|
|
12
|
-
* @param defs - The type definitions registry
|
|
13
|
-
* @param cache - Whether to use cached results (defaults to `true`)
|
|
14
|
-
* @returns The validation result
|
|
15
|
-
*/
|
|
16
3
|
export declare function checkKeywordType(value: string, type: KeywordDefinedType, defs: Defs, cache?: boolean): Result;
|
package/lib/keyword-type.js
CHANGED
|
@@ -4,19 +4,6 @@ import { log } from './debug.js';
|
|
|
4
4
|
import { matched } from './match-result.js';
|
|
5
5
|
const resultCache = new Map();
|
|
6
6
|
const CACHE_KEY_PREFIX = '@markuplint/types/checkKeywordType/cache:::';
|
|
7
|
-
/**
|
|
8
|
-
* Checks a value against a keyword-defined type or CSS syntax type.
|
|
9
|
-
*
|
|
10
|
-
* Looks up the type in the definitions registry and validates the value.
|
|
11
|
-
* If the type is not found in custom definitions, falls back to CSS syntax matching.
|
|
12
|
-
* Results are cached by default for performance.
|
|
13
|
-
*
|
|
14
|
-
* @param value - The string value to check
|
|
15
|
-
* @param type - The keyword type identifier to look up
|
|
16
|
-
* @param defs - The type definitions registry
|
|
17
|
-
* @param cache - Whether to use cached results (defaults to `true`)
|
|
18
|
-
* @returns The validation result
|
|
19
|
-
*/
|
|
20
7
|
export function checkKeywordType(value, type, defs, cache = true) {
|
|
21
8
|
const key = `${CACHE_KEY_PREFIX}${value}${type}`;
|
|
22
9
|
if (cache) {
|
package/lib/list.d.ts
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
import type { Defs, Result } from './types.js';
|
|
2
2
|
import type { List } from './types.schema.js';
|
|
3
3
|
import type { ReadonlyDeep } from 'type-fest';
|
|
4
|
-
/**
|
|
5
|
-
* Checks a value as a separated list of tokens against a type definition.
|
|
6
|
-
*
|
|
7
|
-
* Parses the value into tokens based on the list separator configuration,
|
|
8
|
-
* then validates each individual token against the list's token type.
|
|
9
|
-
*
|
|
10
|
-
* @param value - The string value to check as a list
|
|
11
|
-
* @param type - The list type definition containing separator and token type
|
|
12
|
-
* @param defs - The type definitions registry for resolving nested types
|
|
13
|
-
* @param ref - Optional reference URL for the unmatched result
|
|
14
|
-
* @param cache - Whether to use cached results (defaults to `true`)
|
|
15
|
-
* @returns The validation result
|
|
16
|
-
*/
|
|
17
4
|
export declare function checkList(value: string, type: ReadonlyDeep<List>, defs: Defs, ref?: string, cache?: boolean): Result;
|
package/lib/list.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import { checkBase } from './check-base.js';
|
|
2
2
|
import { matched } from './match-result.js';
|
|
3
3
|
import { Token, TokenCollection } from './token/index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Checks a value as a separated list of tokens against a type definition.
|
|
6
|
-
*
|
|
7
|
-
* Parses the value into tokens based on the list separator configuration,
|
|
8
|
-
* then validates each individual token against the list's token type.
|
|
9
|
-
*
|
|
10
|
-
* @param value - The string value to check as a list
|
|
11
|
-
* @param type - The list type definition containing separator and token type
|
|
12
|
-
* @param defs - The type definitions registry for resolving nested types
|
|
13
|
-
* @param ref - Optional reference URL for the unmatched result
|
|
14
|
-
* @param cache - Whether to use cached results (defaults to `true`)
|
|
15
|
-
* @returns The validation result
|
|
16
|
-
*/
|
|
17
4
|
export function checkList(value, type, defs, ref, cache = true) {
|
|
18
5
|
const tokens = new TokenCollection(value, type);
|
|
19
6
|
const matches = tokens.check({ ref });
|
package/lib/match-result.d.ts
CHANGED
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
import type { FormattedPrimitiveTypeCheck, MatchedResult, UnmatchedResult, UnmatchedResultOptions, UnmatchedResultReason } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Creates a value checker function that wraps a primitive type check
|
|
4
|
-
* and returns a matched or unmatched result.
|
|
5
|
-
*
|
|
6
|
-
* @param checker - The primitive type check function to wrap
|
|
7
|
-
* @param options - Optional settings for the unmatched result including ref and reason
|
|
8
|
-
* @returns A function that takes a string value and returns a match result
|
|
9
|
-
*/
|
|
10
2
|
export declare function matches(checker: FormattedPrimitiveTypeCheck, options?: UnmatchedResultOptions & {
|
|
11
3
|
readonly ref?: string;
|
|
12
4
|
readonly reason?: UnmatchedResultReason;
|
|
13
5
|
}): (value: string) => MatchedResult | UnmatchedResult;
|
|
14
|
-
/**
|
|
15
|
-
* Creates a successful match result.
|
|
16
|
-
*
|
|
17
|
-
* @returns A matched result object
|
|
18
|
-
*/
|
|
19
6
|
export declare function matched(): MatchedResult;
|
|
20
|
-
/**
|
|
21
|
-
* Creates a failed match result with location and reason information.
|
|
22
|
-
*
|
|
23
|
-
* @param value - The raw string value that failed to match
|
|
24
|
-
* @param reason - The reason for the mismatch
|
|
25
|
-
* @param options - Optional settings including ref URL and expected values
|
|
26
|
-
* @returns An unmatched result object with position details
|
|
27
|
-
*/
|
|
28
7
|
export declare function unmatched(value: string, reason?: UnmatchedResultReason, options?: UnmatchedResultOptions & {
|
|
29
8
|
readonly ref?: string;
|
|
30
9
|
}): UnmatchedResult;
|