@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.
Files changed (116) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +83 -75
  3. package/lib/check-multi-types.d.ts +2 -9
  4. package/lib/check-multi-types.js +2 -9
  5. package/lib/css-defs.d.ts +0 -8
  6. package/lib/css-defs.js +0 -8
  7. package/lib/css-overrides.d.ts +2 -5
  8. package/lib/css-overrides.js +2 -5
  9. package/lib/css-syntax.d.ts +0 -11
  10. package/lib/css-syntax.js +6 -18
  11. package/lib/css-tokenizers.d.ts +0 -6
  12. package/lib/css-tokenizers.js +0 -6
  13. package/lib/debug.d.ts +0 -3
  14. package/lib/debug.js +0 -3
  15. package/lib/defs.d.ts +0 -8
  16. package/lib/defs.js +308 -37
  17. package/lib/directive.d.ts +0 -14
  18. package/lib/directive.js +0 -14
  19. package/lib/enum.d.ts +0 -11
  20. package/lib/enum.js +0 -11
  21. package/lib/keyword-type.d.ts +0 -13
  22. package/lib/keyword-type.js +0 -13
  23. package/lib/list.d.ts +0 -13
  24. package/lib/list.js +0 -13
  25. package/lib/match-result.d.ts +0 -21
  26. package/lib/match-result.js +0 -21
  27. package/lib/number.d.ts +0 -12
  28. package/lib/number.js +0 -12
  29. package/lib/primitive/is-float.d.ts +3 -4
  30. package/lib/primitive/is-float.js +3 -4
  31. package/lib/primitive/is-int.d.ts +0 -5
  32. package/lib/primitive/is-int.js +0 -5
  33. package/lib/primitive/is-non-zero-uint.d.ts +0 -6
  34. package/lib/primitive/is-non-zero-uint.js +0 -6
  35. package/lib/primitive/is-quantity.d.ts +0 -8
  36. package/lib/primitive/is-quantity.js +0 -8
  37. package/lib/primitive/is-uint.d.ts +0 -6
  38. package/lib/primitive/is-uint.js +0 -6
  39. package/lib/primitive/range.d.ts +0 -8
  40. package/lib/primitive/range.js +0 -8
  41. package/lib/primitive/split-unit.d.ts +0 -6
  42. package/lib/primitive/split-unit.js +0 -6
  43. package/lib/rfc/is-bcp-47.d.ts +24 -1
  44. package/lib/rfc/is-bcp-47.js +88 -3
  45. package/lib/token/token-collection.d.ts +1 -111
  46. package/lib/token/token-collection.js +0 -108
  47. package/lib/token/token.d.ts +1 -73
  48. package/lib/token/token.js +0 -72
  49. package/lib/token/types.d.ts +0 -9
  50. package/lib/types.schema.d.ts +1 -1
  51. package/lib/w3c/check-content-security-policy.d.ts +18 -0
  52. package/lib/w3c/check-content-security-policy.js +363 -0
  53. package/lib/w3c/check-serialized-permissions-policy.d.ts +0 -2
  54. package/lib/w3c/check-serialized-permissions-policy.js +0 -2
  55. package/lib/whatwg/check-autocomplete.d.ts +26 -9
  56. package/lib/whatwg/check-autocomplete.js +42 -15
  57. package/lib/whatwg/check-datetime/date-string-with-optional-time.d.ts +11 -0
  58. package/lib/whatwg/check-datetime/date-string-with-optional-time.js +16 -0
  59. package/lib/whatwg/check-datetime/date-string.d.ts +0 -2
  60. package/lib/whatwg/check-datetime/date-string.js +0 -2
  61. package/lib/whatwg/check-datetime/datetime-tokens.d.ts +0 -13
  62. package/lib/whatwg/check-datetime/datetime-tokens.js +0 -16
  63. package/lib/whatwg/check-datetime/duration-string.d.ts +0 -4
  64. package/lib/whatwg/check-datetime/duration-string.js +0 -4
  65. package/lib/whatwg/check-datetime/global-date-and-time-string.d.ts +0 -2
  66. package/lib/whatwg/check-datetime/global-date-and-time-string.js +4 -4
  67. package/lib/whatwg/check-datetime/index.d.ts +0 -5
  68. package/lib/whatwg/check-datetime/index.js +0 -5
  69. package/lib/whatwg/check-datetime/local-date-and-time-string.d.ts +0 -4
  70. package/lib/whatwg/check-datetime/local-date-and-time-string.js +0 -4
  71. package/lib/whatwg/check-datetime/month-string.d.ts +0 -2
  72. package/lib/whatwg/check-datetime/month-string.js +0 -2
  73. package/lib/whatwg/check-datetime/time-string.d.ts +0 -2
  74. package/lib/whatwg/check-datetime/time-string.js +0 -2
  75. package/lib/whatwg/check-datetime/time-zone-offset-string.d.ts +0 -8
  76. package/lib/whatwg/check-datetime/time-zone-offset-string.js +0 -8
  77. package/lib/whatwg/check-datetime/week-string.d.ts +0 -2
  78. package/lib/whatwg/check-datetime/week-string.js +0 -2
  79. package/lib/whatwg/check-datetime/year-string.d.ts +0 -2
  80. package/lib/whatwg/check-datetime/year-string.js +0 -2
  81. package/lib/whatwg/check-datetime/yearless-date-string.d.ts +0 -2
  82. package/lib/whatwg/check-datetime/yearless-date-string.js +0 -2
  83. package/lib/whatwg/check-email.js +0 -2
  84. package/lib/whatwg/check-http-equiv-content-type.d.ts +12 -0
  85. package/lib/whatwg/check-http-equiv-content-type.js +32 -0
  86. package/lib/whatwg/check-http-equiv-refresh.d.ts +21 -0
  87. package/lib/whatwg/check-http-equiv-refresh.js +106 -0
  88. package/lib/whatwg/check-media-query-list.d.ts +61 -0
  89. package/lib/whatwg/check-media-query-list.js +484 -0
  90. package/lib/whatwg/check-mime-type.d.ts +0 -4
  91. package/lib/whatwg/check-mime-type.js +55 -3
  92. package/lib/whatwg/check-simple-color.d.ts +2 -4
  93. package/lib/whatwg/check-simple-color.js +2 -4
  94. package/lib/whatwg/check-url.d.ts +15 -7
  95. package/lib/whatwg/check-url.js +333 -25
  96. package/lib/whatwg/is-abs-url.d.ts +0 -2
  97. package/lib/whatwg/is-abs-url.js +0 -2
  98. package/lib/whatwg/is-browser-context-name.d.ts +0 -2
  99. package/lib/whatwg/is-browser-context-name.js +0 -2
  100. package/lib/whatwg/is-itemprop-name.d.ts +0 -2
  101. package/lib/whatwg/is-itemprop-name.js +0 -2
  102. package/lib/whatwg/is-navigable-target-name.d.ts +0 -2
  103. package/lib/whatwg/is-navigable-target-name.js +0 -2
  104. package/package.json +6 -5
  105. package/types.schema.json +9 -0
  106. package/ARCHITECTURE.ja.md +0 -256
  107. package/ARCHITECTURE.md +0 -256
  108. package/SKILL.md +0 -100
  109. package/docs/check-pipeline.ja.md +0 -494
  110. package/docs/check-pipeline.md +0 -494
  111. package/docs/token-system.ja.md +0 -584
  112. package/docs/token-system.md +0 -584
  113. package/docs/type-system.ja.md +0 -623
  114. package/docs/type-system.md +0 -623
  115. package/docs/validators.ja.md +0 -536
  116. 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
- * Built-in type definitions registry for HTML attribute value validation.
27
- *
28
- * Maps type identifiers to their validation logic, reference URLs,
29
- * and expected value descriptions. Includes definitions for common types
30
- * (Any, Number, URL, etc.), WHATWG-specified types (DateTime, MIMEType,
31
- * CustomElementName, etc.), and format-specific validators (BCP47, Pattern, etc.).
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: checkURL(),
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
- value = value.toLowerCase().trim();
335
- if (value.startsWith('data:') || value.startsWith('javascript:')) {
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 matched();
388
+ return checkURLOnce(value);
339
389
  },
340
390
  },
341
391
  AbsoluteURL: {
342
392
  ref: 'https://url.spec.whatwg.org/#syntax-url-absolute',
343
- is: matches(isAbsURL()),
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
- * Subresource Integrity metadata: one or more space-separated
347
- * `hash-algo-base64` tokens where algo is sha256, sha384, or sha512.
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
- is: value => (value[0] === '#' ? matched() : unmatched(value, 'unexpected-token')),
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
- // No descriptor implies 1x (density descriptor)
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
- syntax: {
653
- apply: '<source-size-list>',
654
- def: {
655
- 'source-size-list': '[ <source-size># , ]? <source-size-value>',
656
- 'source-size': '<media-condition> <source-size-value> | auto',
657
- /**
658
- * > Percentages are not allowed in a `<source-size-value>`,
659
- * > to avoid confusion about what it would be relative to.
660
- * > The 'vw' unit can be used for sizes relative to the viewport width.
661
- *
662
- * `<length>` doesn't allow percentages.
663
- * @see https://csstree.github.io/docs/syntax/#Type:length
664
- */
665
- 'source-size-value': '<length> | auto',
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: [
@@ -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;
@@ -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;
@@ -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 });
@@ -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;