@markuplint/rules 4.11.2 → 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/CHANGELOG.md +63 -2
- package/SKILL.md +76 -0
- package/lib/attr-check.d.ts +46 -7
- package/lib/attr-check.js +42 -8
- package/lib/attr-duplication/index.d.ts +7 -0
- package/lib/attr-duplication/index.js +7 -0
- package/lib/attr-duplication/meta.d.ts +1 -0
- package/lib/attr-duplication/meta.js +1 -0
- package/lib/attr-value-quotes/index.d.ts +10 -0
- package/lib/attr-value-quotes/index.js +10 -0
- package/lib/attr-value-quotes/meta.d.ts +1 -0
- package/lib/attr-value-quotes/meta.js +1 -0
- package/lib/case-sensitive-attr-name/index.d.ts +9 -0
- package/lib/case-sensitive-attr-name/index.js +8 -0
- package/lib/case-sensitive-attr-name/meta.d.ts +1 -0
- package/lib/case-sensitive-attr-name/meta.js +1 -0
- package/lib/case-sensitive-tag-name/index.d.ts +9 -0
- package/lib/case-sensitive-tag-name/index.js +8 -0
- package/lib/case-sensitive-tag-name/meta.d.ts +1 -0
- package/lib/case-sensitive-tag-name/meta.js +1 -0
- package/lib/character-reference/index.d.ts +9 -0
- package/lib/character-reference/index.js +20 -0
- package/lib/character-reference/meta.d.ts +1 -0
- package/lib/character-reference/meta.js +1 -0
- package/lib/class-naming/index.d.ts +9 -0
- package/lib/class-naming/index.js +8 -0
- package/lib/class-naming/meta.d.ts +1 -0
- package/lib/class-naming/meta.js +1 -0
- package/lib/correct-aspect-ratio/index.d.ts +5 -0
- package/lib/correct-aspect-ratio/index.js +80 -0
- package/lib/correct-aspect-ratio/meta.d.ts +4 -0
- package/lib/correct-aspect-ratio/meta.js +3 -0
- package/lib/correct-aspect-ratio/resolve-image-size.d.ts +24 -0
- package/lib/correct-aspect-ratio/resolve-image-size.js +165 -0
- package/lib/create-message.d.ts +29 -2
- package/lib/create-message.js +60 -2
- package/lib/debug.d.ts +9 -0
- package/lib/debug.js +5 -0
- package/lib/deprecated-attr/index.d.ts +6 -0
- package/lib/deprecated-attr/index.js +6 -0
- package/lib/deprecated-attr/meta.d.ts +1 -0
- package/lib/deprecated-attr/meta.js +1 -0
- package/lib/deprecated-element/index.d.ts +6 -0
- package/lib/deprecated-element/index.js +8 -2
- package/lib/deprecated-element/meta.d.ts +1 -0
- package/lib/deprecated-element/meta.js +1 -0
- package/lib/disallowed-element/index.d.ts +7 -0
- package/lib/disallowed-element/index.js +7 -0
- package/lib/disallowed-element/meta.d.ts +1 -0
- package/lib/disallowed-element/meta.js +1 -0
- package/lib/doctype/index.d.ts +10 -0
- package/lib/doctype/index.js +8 -0
- package/lib/doctype/meta.d.ts +1 -0
- package/lib/doctype/meta.js +1 -0
- package/lib/end-tag/index.d.ts +7 -0
- package/lib/end-tag/index.js +8 -1
- package/lib/end-tag/meta.d.ts +1 -0
- package/lib/end-tag/meta.js +1 -0
- package/lib/heading-levels/index.d.ts +6 -0
- package/lib/heading-levels/index.js +6 -1
- package/lib/heading-levels/meta.d.ts +1 -0
- package/lib/heading-levels/meta.js +1 -0
- package/lib/helpers.d.ts +105 -0
- package/lib/helpers.js +112 -16
- package/lib/id-duplication/index.d.ts +7 -0
- package/lib/id-duplication/index.js +7 -0
- package/lib/id-duplication/meta.d.ts +1 -0
- package/lib/id-duplication/meta.js +1 -0
- package/lib/index.d.ts +43 -41
- package/lib/index.js +24 -0
- package/lib/ineffective-attr/index.d.ts +7 -0
- package/lib/ineffective-attr/index.js +7 -0
- package/lib/ineffective-attr/meta.d.ts +1 -0
- package/lib/ineffective-attr/meta.js +1 -0
- package/lib/invalid-attr/index.d.ts +29 -21
- package/lib/invalid-attr/index.js +60 -109
- package/lib/invalid-attr/meta.d.ts +1 -0
- package/lib/invalid-attr/meta.js +1 -0
- package/lib/label-has-control/index.d.ts +7 -0
- package/lib/label-has-control/index.js +11 -0
- package/lib/label-has-control/meta.d.ts +1 -0
- package/lib/label-has-control/meta.js +1 -0
- package/lib/landmark-roles/index.d.ts +14 -0
- package/lib/landmark-roles/index.js +21 -0
- package/lib/landmark-roles/meta.d.ts +1 -0
- package/lib/landmark-roles/meta.js +1 -0
- package/lib/link-types/index.d.ts +10 -0
- package/lib/link-types/index.js +180 -0
- package/lib/link-types/meta.d.ts +5 -0
- package/lib/link-types/meta.js +4 -0
- package/lib/neighbor-popovers/index.d.ts +8 -0
- package/lib/neighbor-popovers/index.js +28 -6
- package/lib/neighbor-popovers/meta.d.ts +1 -0
- package/lib/neighbor-popovers/meta.js +1 -0
- package/lib/no-ambiguous-navigable-target-names/index.d.ts +8 -0
- package/lib/no-ambiguous-navigable-target-names/index.js +8 -0
- package/lib/no-ambiguous-navigable-target-names/meta.d.ts +1 -0
- package/lib/no-ambiguous-navigable-target-names/meta.js +1 -0
- package/lib/no-boolean-attr-value/index.d.ts +7 -0
- package/lib/no-boolean-attr-value/index.js +7 -0
- package/lib/no-boolean-attr-value/meta.d.ts +1 -0
- package/lib/no-boolean-attr-value/meta.js +1 -0
- package/lib/no-consecutive-br/index.d.ts +8 -0
- package/lib/no-consecutive-br/index.js +8 -0
- package/lib/no-consecutive-br/meta.d.ts +1 -0
- package/lib/no-consecutive-br/meta.js +1 -0
- package/lib/no-default-value/index.d.ts +7 -0
- package/lib/no-default-value/index.js +7 -0
- package/lib/no-default-value/meta.d.ts +1 -0
- package/lib/no-default-value/meta.js +1 -0
- package/lib/no-duplicate-dt/index.d.ts +6 -0
- package/lib/no-duplicate-dt/index.js +6 -0
- package/lib/no-duplicate-dt/meta.d.ts +1 -0
- package/lib/no-duplicate-dt/meta.js +1 -0
- package/lib/no-empty-palpable-content/index.d.ts +13 -0
- package/lib/no-empty-palpable-content/index.js +12 -0
- package/lib/no-empty-palpable-content/meta.d.ts +1 -0
- package/lib/no-empty-palpable-content/meta.js +1 -0
- package/lib/no-hard-code-id/index.d.ts +8 -0
- package/lib/no-hard-code-id/index.js +8 -0
- package/lib/no-hard-code-id/meta.d.ts +1 -0
- package/lib/no-hard-code-id/meta.js +1 -0
- package/lib/no-orphaned-end-tag/index.d.ts +7 -0
- package/lib/no-orphaned-end-tag/index.js +8 -2
- package/lib/no-orphaned-end-tag/meta.d.ts +1 -0
- package/lib/no-orphaned-end-tag/meta.js +1 -0
- package/lib/no-refer-to-non-existent-id/index.d.ts +9 -1
- package/lib/no-refer-to-non-existent-id/index.js +11 -2
- package/lib/no-refer-to-non-existent-id/meta.d.ts +1 -0
- package/lib/no-refer-to-non-existent-id/meta.js +1 -0
- package/lib/no-unsupported-features/compat-data.d.ts +71 -0
- package/lib/no-unsupported-features/compat-data.js +204 -0
- package/lib/no-unsupported-features/index.d.ts +24 -0
- package/lib/no-unsupported-features/index.js +139 -0
- package/lib/no-unsupported-features/meta.d.ts +5 -0
- package/lib/no-unsupported-features/meta.js +4 -0
- package/lib/no-unsupported-features/resolve-browsers.d.ts +30 -0
- package/lib/no-unsupported-features/resolve-browsers.js +125 -0
- package/lib/no-use-event-handler-attr/index.d.ts +10 -0
- package/lib/no-use-event-handler-attr/index.js +8 -0
- package/lib/no-use-event-handler-attr/meta.d.ts +1 -0
- package/lib/no-use-event-handler-attr/meta.js +1 -0
- package/lib/permitted-contents/choice.d.ts +17 -0
- package/lib/permitted-contents/choice.js +31 -1
- package/lib/permitted-contents/complex-branch.d.ts +11 -7
- package/lib/permitted-contents/complex-branch.js +11 -7
- package/lib/permitted-contents/content-model.d.ts +11 -0
- package/lib/permitted-contents/content-model.js +32 -0
- package/lib/permitted-contents/count-pattern.d.ts +13 -7
- package/lib/permitted-contents/count-pattern.js +24 -8
- package/lib/permitted-contents/debug.browser.d.ts +12 -0
- package/lib/permitted-contents/debug.browser.js +12 -0
- package/lib/permitted-contents/debug.d.ts +12 -0
- package/lib/permitted-contents/debug.js +12 -0
- package/lib/permitted-contents/index.d.ts +12 -0
- package/lib/permitted-contents/index.js +21 -0
- package/lib/permitted-contents/matches-selector.d.ts +19 -0
- package/lib/permitted-contents/matches-selector.js +33 -0
- package/lib/permitted-contents/meta.d.ts +1 -0
- package/lib/permitted-contents/meta.js +1 -0
- package/lib/permitted-contents/order.d.ts +14 -7
- package/lib/permitted-contents/order.js +15 -8
- package/lib/permitted-contents/recursive-branch.d.ts +17 -0
- package/lib/permitted-contents/recursive-branch.js +17 -0
- package/lib/permitted-contents/represent-transparent-nodes.d.ts +54 -0
- package/lib/permitted-contents/represent-transparent-nodes.js +119 -12
- package/lib/permitted-contents/start.d.ts +12 -6
- package/lib/permitted-contents/start.js +12 -6
- package/lib/permitted-contents/transparent.d.ts +12 -0
- package/lib/permitted-contents/transparent.js +12 -0
- package/lib/permitted-contents/types.d.ts +67 -0
- package/lib/permitted-contents/utils.d.ts +160 -0
- package/lib/permitted-contents/utils.js +216 -35
- package/lib/placeholder-label-option/index.d.ts +7 -0
- package/lib/placeholder-label-option/index.js +19 -15
- package/lib/placeholder-label-option/meta.d.ts +1 -0
- package/lib/placeholder-label-option/meta.js +1 -0
- package/lib/redundant-accessible-name/index.d.ts +10 -0
- package/lib/redundant-accessible-name/index.js +123 -0
- package/lib/redundant-accessible-name/meta.d.ts +5 -0
- package/lib/redundant-accessible-name/meta.js +4 -0
- package/lib/require-accessible-name/index.d.ts +10 -4
- package/lib/require-accessible-name/index.js +13 -5
- package/lib/require-accessible-name/meta.d.ts +1 -0
- package/lib/require-accessible-name/meta.js +1 -0
- package/lib/require-datetime/index.d.ts +12 -0
- package/lib/require-datetime/index.js +8 -0
- package/lib/require-datetime/meta.d.ts +1 -0
- package/lib/require-datetime/meta.js +1 -0
- package/lib/require-datetime/types.d.ts +7 -0
- package/lib/require-datetime/utils.d.ts +18 -5
- package/lib/require-datetime/utils.js +53 -9
- package/lib/require-dialog-autofocus/index.d.ts +22 -0
- package/lib/require-dialog-autofocus/index.js +62 -0
- package/lib/require-dialog-autofocus/meta.d.ts +5 -0
- package/lib/require-dialog-autofocus/meta.js +4 -0
- package/lib/required-attr/index.d.ts +21 -1
- package/lib/required-attr/index.js +34 -4
- package/lib/required-attr/meta.d.ts +1 -0
- package/lib/required-attr/meta.js +1 -0
- package/lib/required-element/index.d.ts +14 -0
- package/lib/required-element/index.js +14 -2
- package/lib/required-element/meta.d.ts +1 -0
- package/lib/required-element/meta.js +1 -0
- package/lib/required-h1/index.d.ts +12 -0
- package/lib/required-h1/index.js +7 -0
- package/lib/required-h1/meta.d.ts +1 -0
- package/lib/required-h1/meta.js +1 -0
- package/lib/srcset-sizes-constraint/index.d.ts +6 -0
- package/lib/srcset-sizes-constraint/index.js +108 -0
- package/lib/srcset-sizes-constraint/meta.d.ts +5 -0
- package/lib/srcset-sizes-constraint/meta.js +4 -0
- package/lib/srcset-sizes-constraint/parse-srcset.d.ts +40 -0
- package/lib/srcset-sizes-constraint/parse-srcset.js +56 -0
- package/lib/table-row-column-alignment/find-children.d.ts +10 -0
- package/lib/table-row-column-alignment/find-children.js +11 -0
- package/lib/table-row-column-alignment/grid.d.ts +60 -0
- package/lib/table-row-column-alignment/grid.js +104 -26
- package/lib/table-row-column-alignment/index.d.ts +8 -0
- package/lib/table-row-column-alignment/index.js +8 -0
- package/lib/table-row-column-alignment/meta.d.ts +1 -0
- package/lib/table-row-column-alignment/meta.js +1 -0
- package/lib/table-row-column-alignment/types.d.ts +10 -0
- package/lib/use-list/index.d.ts +16 -0
- package/lib/use-list/index.js +20 -0
- package/lib/use-list/meta.d.ts +1 -0
- package/lib/use-list/meta.js +1 -0
- package/lib/wai-aria/checkings/abstract-role.d.ts +9 -0
- package/lib/wai-aria/checkings/abstract-role.js +14 -2
- package/lib/wai-aria/checkings/default-value.d.ts +11 -0
- package/lib/wai-aria/checkings/default-value.js +11 -0
- package/lib/wai-aria/checkings/deprecated-props.d.ts +11 -0
- package/lib/wai-aria/checkings/deprecated-props.js +11 -0
- package/lib/wai-aria/checkings/deprecated-role.d.ts +10 -0
- package/lib/wai-aria/checkings/deprecated-role.js +10 -0
- package/lib/wai-aria/checkings/disallowed-prop.d.ts +14 -0
- package/lib/wai-aria/checkings/disallowed-prop.js +19 -2
- package/lib/wai-aria/checkings/implicit-props.d.ts +13 -0
- package/lib/wai-aria/checkings/implicit-props.js +13 -0
- package/lib/wai-aria/checkings/implicit-role.d.ts +9 -0
- package/lib/wai-aria/checkings/implicit-role.js +14 -2
- package/lib/wai-aria/checkings/interaction-in-hidden.d.ts +9 -0
- package/lib/wai-aria/checkings/interaction-in-hidden.js +15 -4
- package/lib/wai-aria/checkings/no-global-prop.d.ts +10 -0
- package/lib/wai-aria/checkings/no-global-prop.js +10 -0
- package/lib/wai-aria/checkings/non-existent-role.d.ts +11 -0
- package/lib/wai-aria/checkings/non-existent-role.js +19 -2
- package/lib/wai-aria/checkings/permitted-roles.d.ts +10 -0
- package/lib/wai-aria/checkings/permitted-roles.js +15 -2
- package/lib/wai-aria/checkings/presentational-children.d.ts +7 -1
- package/lib/wai-aria/checkings/presentational-children.js +17 -3
- package/lib/wai-aria/checkings/required-accessibility-parent-role.d.ts +25 -0
- package/lib/wai-aria/checkings/required-accessibility-parent-role.js +56 -0
- package/lib/wai-aria/checkings/required-owned-elements.d.ts +10 -1
- package/lib/wai-aria/checkings/required-owned-elements.js +69 -25
- package/lib/wai-aria/checkings/required-prop.d.ts +13 -0
- package/lib/wai-aria/checkings/required-prop.js +18 -2
- package/lib/wai-aria/checkings/value.d.ts +23 -0
- package/lib/wai-aria/checkings/value.js +32 -0
- package/lib/wai-aria/index.d.ts +10 -0
- package/lib/wai-aria/index.js +22 -4
- package/lib/wai-aria/meta.d.ts +1 -0
- package/lib/wai-aria/meta.js +1 -0
- package/lib/wai-aria/types.d.ts +25 -1
- package/package.json +16 -11
- package/schema.json +18 -0
|
@@ -2,28 +2,36 @@ import { createRule, getRoleSpec, getComputedRole } from '@markuplint/ml-core';
|
|
|
2
2
|
import { ARIA_RECOMMENDED_VERSION, isExposed } from '@markuplint/ml-spec';
|
|
3
3
|
import { accnameMayBeMutable } from '../helpers.js';
|
|
4
4
|
import meta from './meta.js';
|
|
5
|
+
/**
|
|
6
|
+
* Rule that requires elements with roles that need accessible names to have one.
|
|
7
|
+
*
|
|
8
|
+
* For each element exposed to the accessibility tree whose computed ARIA role
|
|
9
|
+
* has `accessibleNameRequired` set to `true`, this rule verifies that the element
|
|
10
|
+
* provides a non-empty accessible name.
|
|
11
|
+
*/
|
|
5
12
|
export default createRule({
|
|
6
13
|
meta: meta,
|
|
7
14
|
defaultOptions: {
|
|
8
|
-
ariaVersion:
|
|
15
|
+
ariaVersion: undefined,
|
|
9
16
|
},
|
|
10
17
|
async verify({ document, report, t }) {
|
|
11
18
|
await document.walkOn('Element', el => {
|
|
19
|
+
const ariaVersion = el.rule.options?.ariaVersion ?? document.ruleCommonSettings?.ariaVersion ?? ARIA_RECOMMENDED_VERSION;
|
|
12
20
|
if (accnameMayBeMutable(el, document)) {
|
|
13
21
|
return;
|
|
14
22
|
}
|
|
15
|
-
if (!isExposed(el, document.specs,
|
|
23
|
+
if (!isExposed(el, document.specs, ariaVersion)) {
|
|
16
24
|
return;
|
|
17
25
|
}
|
|
18
|
-
const computed = getComputedRole(document.specs, el,
|
|
26
|
+
const computed = getComputedRole(document.specs, el, ariaVersion);
|
|
19
27
|
if (!computed.role) {
|
|
20
28
|
return;
|
|
21
29
|
}
|
|
22
|
-
const roleSpec = getRoleSpec(document.specs, computed.role.name, el.namespaceURI,
|
|
30
|
+
const roleSpec = getRoleSpec(document.specs, computed.role.name, el.namespaceURI, ariaVersion);
|
|
23
31
|
if (!roleSpec || !roleSpec.accessibleNameRequired) {
|
|
24
32
|
return;
|
|
25
33
|
}
|
|
26
|
-
const hasAccessibleName = !!el.getAccessibleName(
|
|
34
|
+
const hasAccessibleName = !!el.getAccessibleName(ariaVersion).trim();
|
|
27
35
|
if (!hasAccessibleName) {
|
|
28
36
|
report({ scope: el, message: t('Require {0}', 'accessible name') });
|
|
29
37
|
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { Lang } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for the require-datetime rule.
|
|
4
|
+
*/
|
|
2
5
|
type Options = {
|
|
6
|
+
/** Languages to use when parsing natural language date/time text content. */
|
|
3
7
|
langs?: Lang[];
|
|
4
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* Rule that validates `<time>` elements without a `datetime` attribute have
|
|
11
|
+
* machine-readable date/time content.
|
|
12
|
+
*
|
|
13
|
+
* When a `<time>` element lacks the `datetime` attribute, this rule checks
|
|
14
|
+
* whether the text content is a valid datetime string. If not, it attempts
|
|
15
|
+
* to parse the content as natural language and suggests a `datetime` value.
|
|
16
|
+
*/
|
|
5
17
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Options>>;
|
|
6
18
|
export default _default;
|
|
@@ -2,6 +2,14 @@ import { createRule } from '@markuplint/ml-core';
|
|
|
2
2
|
import { check } from '@markuplint/types';
|
|
3
3
|
import meta from './meta.js';
|
|
4
4
|
import { getCandidateDatetimeString } from './utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Rule that validates `<time>` elements without a `datetime` attribute have
|
|
7
|
+
* machine-readable date/time content.
|
|
8
|
+
*
|
|
9
|
+
* When a `<time>` element lacks the `datetime` attribute, this rule checks
|
|
10
|
+
* whether the text content is a valid datetime string. If not, it attempts
|
|
11
|
+
* to parse the content as natural language and suggests a `datetime` value.
|
|
12
|
+
*/
|
|
5
13
|
export default createRule({
|
|
6
14
|
meta: meta,
|
|
7
15
|
defaultOptions: {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
/** Union of possible date/time component keys used in parsed datetime data. */
|
|
1
2
|
export type DateTimeKey = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'ms';
|
|
3
|
+
/** A partial record mapping date/time component keys to their numeric values. */
|
|
2
4
|
export type DateTimeData = Partial<Record<DateTimeKey, number>>;
|
|
5
|
+
/** Represents a parsed date/time value with optional timezone offset. */
|
|
3
6
|
export type DateTime = {
|
|
7
|
+
/** The parsed date/time component values. */
|
|
4
8
|
datetime: DateTimeData;
|
|
9
|
+
/** Timezone offset in minutes from UTC (e.g., -300 for UTC-5). */
|
|
5
10
|
zone?: number;
|
|
6
11
|
};
|
|
7
12
|
/**
|
|
13
|
+
* Supported locale codes for natural language date/time parsing.
|
|
14
|
+
*
|
|
8
15
|
* @see https://github.com/wanasit/chrono#locales
|
|
9
16
|
*/
|
|
10
17
|
export type Lang = 'en' | 'ja' | 'fr' | 'nl' | 'ru' | 'de' | 'pt' | 'zh';
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import type { DateTime, Lang } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Parses natural language datetime text into structured datetime data.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @
|
|
5
|
+
* Tries multiple locale parsers and returns the first successful parse result
|
|
6
|
+
* with only the certain (non-implied) date/time components included.
|
|
7
|
+
*
|
|
8
|
+
* @param content - The text content to parse as a datetime.
|
|
9
|
+
* @param langs - Locale codes to attempt parsing with.
|
|
10
|
+
* @param base - Optional reference date for relative date parsing (e.g., "tomorrow").
|
|
11
|
+
* @returns Parsed datetime data with timezone, or `null` if parsing fails.
|
|
9
12
|
*/
|
|
10
13
|
export declare function parseADatetime(content: string, langs: readonly Lang[], base?: Readonly<Date>): DateTime | null;
|
|
14
|
+
/**
|
|
15
|
+
* Generates an HTML `datetime` attribute value from natural language text.
|
|
16
|
+
*
|
|
17
|
+
* Parses the text content using chrono-node and formats the result as an
|
|
18
|
+
* ISO 8601-like datetime string suitable for the `datetime` attribute.
|
|
19
|
+
*
|
|
20
|
+
* @param content - The text content to parse.
|
|
21
|
+
* @param langs - Locale codes to use for parsing. Defaults to all supported languages.
|
|
22
|
+
* @returns A formatted datetime string, or `null` if the content cannot be parsed.
|
|
23
|
+
*/
|
|
11
24
|
export declare function getCandidateDatetimeString(content: string, langs?: Lang[]): string | null;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import * as chrono from 'chrono-node';
|
|
2
|
+
/** Default set of languages to try when parsing natural language datetime text. */
|
|
2
3
|
const defaultLangs = ['en', 'ja', 'fr', 'nl', 'ru', 'de', 'pt', 'zh'];
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Parses natural language datetime text into structured datetime data.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @
|
|
7
|
+
* Tries multiple locale parsers and returns the first successful parse result
|
|
8
|
+
* with only the certain (non-implied) date/time components included.
|
|
9
|
+
*
|
|
10
|
+
* @param content - The text content to parse as a datetime.
|
|
11
|
+
* @param langs - Locale codes to attempt parsing with.
|
|
12
|
+
* @param base - Optional reference date for relative date parsing (e.g., "tomorrow").
|
|
13
|
+
* @returns Parsed datetime data with timezone, or `null` if parsing fails.
|
|
10
14
|
*/
|
|
11
15
|
export function parseADatetime(content, langs, base) {
|
|
12
16
|
const date = parseTryMultipleLangs(content, langs, base);
|
|
@@ -43,6 +47,16 @@ export function parseADatetime(content, langs, base) {
|
|
|
43
47
|
}
|
|
44
48
|
return datetime;
|
|
45
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Generates an HTML `datetime` attribute value from natural language text.
|
|
52
|
+
*
|
|
53
|
+
* Parses the text content using chrono-node and formats the result as an
|
|
54
|
+
* ISO 8601-like datetime string suitable for the `datetime` attribute.
|
|
55
|
+
*
|
|
56
|
+
* @param content - The text content to parse.
|
|
57
|
+
* @param langs - Locale codes to use for parsing. Defaults to all supported languages.
|
|
58
|
+
* @returns A formatted datetime string, or `null` if the content cannot be parsed.
|
|
59
|
+
*/
|
|
46
60
|
export function getCandidateDatetimeString(content, langs = defaultLangs) {
|
|
47
61
|
const date = parseADatetime(content, langs);
|
|
48
62
|
if (!date) {
|
|
@@ -60,6 +74,15 @@ export function getCandidateDatetimeString(content, langs = defaultLangs) {
|
|
|
60
74
|
}
|
|
61
75
|
return datetimeStr;
|
|
62
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Converts parsed datetime data into an ISO 8601-like string.
|
|
79
|
+
*
|
|
80
|
+
* Produces different formats depending on which components are present
|
|
81
|
+
* (e.g., date-only, time-only, or combined date-time).
|
|
82
|
+
*
|
|
83
|
+
* @param date - The parsed date/time component data.
|
|
84
|
+
* @returns A formatted datetime string, or `null` if the components do not match any known format.
|
|
85
|
+
*/
|
|
63
86
|
function toDatetimeString(date) {
|
|
64
87
|
if (only(date, ['year', 'month'])) {
|
|
65
88
|
return `${f(date.year, 4)}-${f(date.month, 2)}`;
|
|
@@ -90,6 +113,17 @@ function toDatetimeString(date) {
|
|
|
90
113
|
}
|
|
91
114
|
return null;
|
|
92
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Attempts to parse datetime text using multiple locale parsers sequentially.
|
|
118
|
+
*
|
|
119
|
+
* Returns the parsed start component from the first locale that produces a
|
|
120
|
+
* valid result (non-range, single datetime).
|
|
121
|
+
*
|
|
122
|
+
* @param content - The text content to parse.
|
|
123
|
+
* @param langs - Locale codes to try in order.
|
|
124
|
+
* @param base - Optional reference date for relative date parsing.
|
|
125
|
+
* @returns The parsed start component, or `null` if no locale succeeds.
|
|
126
|
+
*/
|
|
93
127
|
function parseTryMultipleLangs(content, langs, base) {
|
|
94
128
|
for (const lang of langs) {
|
|
95
129
|
const results =
|
|
@@ -111,6 +145,16 @@ function parseTryMultipleLangs(content, langs, base) {
|
|
|
111
145
|
}
|
|
112
146
|
return null;
|
|
113
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Type guard that checks if the given datetime data contains only the specified keys.
|
|
150
|
+
*
|
|
151
|
+
* @template K - The array of datetime keys to check for.
|
|
152
|
+
* @template U - Union of the key types.
|
|
153
|
+
* @template R - The resulting narrowed type with required properties.
|
|
154
|
+
* @param date - The datetime data to check.
|
|
155
|
+
* @param keys - The keys that should be the only ones present.
|
|
156
|
+
* @returns `true` if the date contains only the specified keys (and narrows the type).
|
|
157
|
+
*/
|
|
114
158
|
function only(date, keys) {
|
|
115
159
|
const list = Object.keys(date);
|
|
116
160
|
for (const exists of list) {
|
|
@@ -121,11 +165,11 @@ function only(date, keys) {
|
|
|
121
165
|
return true;
|
|
122
166
|
}
|
|
123
167
|
/**
|
|
124
|
-
*
|
|
168
|
+
* Formats a number with zero-padding to the specified width.
|
|
125
169
|
*
|
|
126
|
-
* @param n
|
|
127
|
-
* @param pad
|
|
128
|
-
* @returns
|
|
170
|
+
* @param n - The number to format.
|
|
171
|
+
* @param pad - The minimum number of digits in the output string.
|
|
172
|
+
* @returns The zero-padded string representation.
|
|
129
173
|
*/
|
|
130
174
|
function f(n, pad) {
|
|
131
175
|
return n.toString(10).padStart(pad, '0');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule that requires a `<dialog>` element referenced by a `show-modal` command
|
|
3
|
+
* to contain a descendant (or itself) with the `autofocus` attribute.
|
|
4
|
+
*
|
|
5
|
+
* Without `autofocus`, the browser's dialog focusing steps fall back to the
|
|
6
|
+
* `<dialog>` element itself, which is not ideal for accessibility — screen
|
|
7
|
+
* reader users may miss the dialog content, and keyboard users may need
|
|
8
|
+
* extra tab presses to reach interactive elements.
|
|
9
|
+
*
|
|
10
|
+
* Detection algorithm:
|
|
11
|
+
* 1. Find all `<button>` elements with both `command` and `commandfor` attributes
|
|
12
|
+
* 2. Filter to triggers whose `command` value is `show-modal` (case-insensitive)
|
|
13
|
+
* 3. Resolve the referenced `<dialog>` via `document.getElementById(commandfor)`
|
|
14
|
+
* 4. Skip non-dialog targets and already-reported dialogs (deduplication)
|
|
15
|
+
* 5. Report if neither the dialog nor any descendant has the `autofocus` attribute
|
|
16
|
+
*
|
|
17
|
+
* @see https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-focusing-steps
|
|
18
|
+
* @see https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element
|
|
19
|
+
* @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
|
|
20
|
+
*/
|
|
21
|
+
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
22
|
+
export default _default;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createRule } from '@markuplint/ml-core';
|
|
2
|
+
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule that requires a `<dialog>` element referenced by a `show-modal` command
|
|
5
|
+
* to contain a descendant (or itself) with the `autofocus` attribute.
|
|
6
|
+
*
|
|
7
|
+
* Without `autofocus`, the browser's dialog focusing steps fall back to the
|
|
8
|
+
* `<dialog>` element itself, which is not ideal for accessibility — screen
|
|
9
|
+
* reader users may miss the dialog content, and keyboard users may need
|
|
10
|
+
* extra tab presses to reach interactive elements.
|
|
11
|
+
*
|
|
12
|
+
* Detection algorithm:
|
|
13
|
+
* 1. Find all `<button>` elements with both `command` and `commandfor` attributes
|
|
14
|
+
* 2. Filter to triggers whose `command` value is `show-modal` (case-insensitive)
|
|
15
|
+
* 3. Resolve the referenced `<dialog>` via `document.getElementById(commandfor)`
|
|
16
|
+
* 4. Skip non-dialog targets and already-reported dialogs (deduplication)
|
|
17
|
+
* 5. Report if neither the dialog nor any descendant has the `autofocus` attribute
|
|
18
|
+
*
|
|
19
|
+
* @see https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-focusing-steps
|
|
20
|
+
* @see https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element
|
|
21
|
+
* @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
|
|
22
|
+
*/
|
|
23
|
+
export default createRule({
|
|
24
|
+
defaultSeverity: 'warning',
|
|
25
|
+
meta: meta,
|
|
26
|
+
verify({ document, report, t }) {
|
|
27
|
+
const triggers = document.querySelectorAll('button[command][commandfor]');
|
|
28
|
+
const reportedDialogIds = new Set();
|
|
29
|
+
for (const trigger of triggers) {
|
|
30
|
+
const command = trigger.getAttribute('command');
|
|
31
|
+
if (!command || command.toLowerCase() !== 'show-modal') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const targetId = trigger.getAttribute('commandfor');
|
|
35
|
+
if (!targetId) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (reportedDialogIds.has(targetId)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const target = document.getElementById(targetId);
|
|
42
|
+
if (!target) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (target.localName !== 'dialog') {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (target.hasAttribute('autofocus')) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const autofocusDescendants = target.querySelectorAll('[autofocus]');
|
|
52
|
+
if (autofocusDescendants.length > 0) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
reportedDialogIds.add(targetId);
|
|
56
|
+
report({
|
|
57
|
+
scope: target,
|
|
58
|
+
message: t('The "{0*}" element referenced by a "{1*}" command requires an element with the "{2*}" attribute', 'dialog', 'show-modal', 'autofocus'),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -1,7 +1,27 @@
|
|
|
1
|
+
/** The rule value: either a single attribute name or an array of required attribute descriptors. */
|
|
1
2
|
type RequiredAttributes = string | (string | Attr)[];
|
|
3
|
+
/**
|
|
4
|
+
* Descriptor for a required attribute with an optional set of allowed values.
|
|
5
|
+
*/
|
|
2
6
|
type Attr = {
|
|
7
|
+
/** The attribute name that is required. */
|
|
3
8
|
name: string;
|
|
9
|
+
/** One or more allowed values for the attribute, if constrained. */
|
|
4
10
|
value?: string | string[];
|
|
5
11
|
};
|
|
6
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for the `required-attr` rule.
|
|
14
|
+
*/
|
|
15
|
+
type Options = {
|
|
16
|
+
/** Attribute names to exclude from required-attribute checks. */
|
|
17
|
+
readonly ignoreAttrs?: readonly string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Rule that validates elements have all required attributes.
|
|
21
|
+
*
|
|
22
|
+
* Checks both HTML-spec-defined required attributes and custom required attributes
|
|
23
|
+
* specified in the rule configuration. Also validates that required attribute values
|
|
24
|
+
* match expected patterns when value constraints are provided.
|
|
25
|
+
*/
|
|
26
|
+
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<RequiredAttributes, Options>>;
|
|
7
27
|
export default _default;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
2
|
import { attrMatches, match } from '../helpers.js';
|
|
3
3
|
import meta from './meta.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rule that validates elements have all required attributes.
|
|
6
|
+
*
|
|
7
|
+
* Checks both HTML-spec-defined required attributes and custom required attributes
|
|
8
|
+
* specified in the rule configuration. Also validates that required attribute values
|
|
9
|
+
* match expected patterns when value constraints are provided.
|
|
10
|
+
*/
|
|
4
11
|
export default createRule({
|
|
5
12
|
meta: meta,
|
|
6
13
|
defaultValue: [],
|
|
14
|
+
defaultOptions: {},
|
|
7
15
|
async verify({ document, report, t }) {
|
|
8
16
|
await document.walkOn('Element', el => {
|
|
9
17
|
if (el.hasSpreadAttr) {
|
|
@@ -11,6 +19,7 @@ export default createRule({
|
|
|
11
19
|
}
|
|
12
20
|
const customRequiredAttrs = typeof el.rule.value === 'string' ? [el.rule.value] : el.rule.value;
|
|
13
21
|
const attrSpec = getAttrSpecs(el, document.specs);
|
|
22
|
+
const ignoreAttrs = new Set(el.rule.options.ignoreAttrs);
|
|
14
23
|
const attributeSpecs = {};
|
|
15
24
|
if (attrSpec && el.elementType === 'html') {
|
|
16
25
|
for (const spec of attrSpec) {
|
|
@@ -44,12 +53,33 @@ export default createRule({
|
|
|
44
53
|
};
|
|
45
54
|
}
|
|
46
55
|
for (const spec of Object.values(attributeSpecs)) {
|
|
56
|
+
if (ignoreAttrs.has(spec.name)) {
|
|
57
|
+
if (spec.requiredEither) {
|
|
58
|
+
const activeCandidates = spec.requiredEither.filter(n => !ignoreAttrs.has(n));
|
|
59
|
+
if (activeCandidates.length > 0) {
|
|
60
|
+
const invalid = !activeCandidates.some(attrName => el.hasAttribute(attrName));
|
|
61
|
+
if (invalid) {
|
|
62
|
+
const sortedCandidate = activeCandidates.toSorted();
|
|
63
|
+
const expects = sortedCandidate.length === 1
|
|
64
|
+
? t('the "{0*}" {1}', sortedCandidate[0], 'attribute')
|
|
65
|
+
: t('{0} {1}', sortedCandidate
|
|
66
|
+
.map(attrName => t('the "{0*}"', attrName))
|
|
67
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
68
|
+
.reduce((a, b) => t('{0} or {1}', a, b)), t('attribute'));
|
|
69
|
+
const message = t('{0} expects {1}', t('the "{0*}" {1}', el.localName, 'element'), expects);
|
|
70
|
+
report({ scope: el, message });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
47
76
|
const didntHave = !el.hasAttribute(spec.name);
|
|
48
77
|
const candidate = [spec.name];
|
|
49
78
|
let invalid = false;
|
|
50
79
|
if (spec.requiredEither) {
|
|
51
80
|
candidate.push(...spec.requiredEither);
|
|
52
|
-
|
|
81
|
+
const activeCandidates = candidate.filter(n => !ignoreAttrs.has(n));
|
|
82
|
+
invalid = !activeCandidates.some(attrName => el.hasAttribute(attrName));
|
|
53
83
|
}
|
|
54
84
|
else if (spec.required === true) {
|
|
55
85
|
invalid = attrMatches(el, spec.condition) && didntHave;
|
|
@@ -58,11 +88,11 @@ export default createRule({
|
|
|
58
88
|
const selector = typeof spec.required === 'string' ? spec.required : spec.required.join(',');
|
|
59
89
|
invalid = el.matches(selector) && didntHave;
|
|
60
90
|
}
|
|
61
|
-
candidate.
|
|
91
|
+
const sortedCandidate = candidate.filter(n => !ignoreAttrs.has(n)).toSorted();
|
|
62
92
|
if (invalid) {
|
|
63
|
-
const expects =
|
|
93
|
+
const expects = sortedCandidate.length === 1
|
|
64
94
|
? t('the "{0*}" {1}', spec.name, 'attribute')
|
|
65
|
-
: t('{0} {1}',
|
|
95
|
+
: t('{0} {1}', sortedCandidate
|
|
66
96
|
.map(attrName => t('the "{0*}"', attrName))
|
|
67
97
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
68
98
|
.reduce((a, b) => t('{0} or {1}', a, b)), t('attribute'));
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the required-element rule.
|
|
3
|
+
*/
|
|
1
4
|
type Options = {
|
|
5
|
+
/** Whether to skip validation for elements that contain mutable (dynamic) content. */
|
|
2
6
|
ignoreHasMutableContents: boolean;
|
|
7
|
+
/** Whether to ignore omitted (ghost) elements that are implicitly created by the HTML parser. */
|
|
8
|
+
ignoreOmittedElements: boolean;
|
|
3
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Rule that validates required child elements are present.
|
|
12
|
+
*
|
|
13
|
+
* Accepts an array of CSS selectors as the rule value. For the document level,
|
|
14
|
+
* reports if any matching element is missing from the entire document. For
|
|
15
|
+
* element-level overrides, checks that the specified child elements exist
|
|
16
|
+
* among the element's direct children.
|
|
17
|
+
*/
|
|
4
18
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<string[], Options>>;
|
|
5
19
|
export default _default;
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule that validates required child elements are present.
|
|
5
|
+
*
|
|
6
|
+
* Accepts an array of CSS selectors as the rule value. For the document level,
|
|
7
|
+
* reports if any matching element is missing from the entire document. For
|
|
8
|
+
* element-level overrides, checks that the specified child elements exist
|
|
9
|
+
* among the element's direct children.
|
|
10
|
+
*/
|
|
3
11
|
export default createRule({
|
|
4
12
|
meta: meta,
|
|
5
13
|
defaultValue: [],
|
|
6
14
|
defaultOptions: {
|
|
7
15
|
ignoreHasMutableContents: true,
|
|
16
|
+
ignoreOmittedElements: true,
|
|
8
17
|
},
|
|
9
18
|
async verify({ document, report, t }) {
|
|
10
19
|
const hasMutableContent = document.nodeList.some(n => n.is(n.ELEMENT_NODE) && n.hasMutableChildren());
|
|
11
20
|
if (!hasMutableContent) {
|
|
12
21
|
for (const query of document.rule.value) {
|
|
13
|
-
const
|
|
22
|
+
const matched = document.querySelectorAll(query);
|
|
23
|
+
const exists = document.rule.options.ignoreOmittedElements
|
|
24
|
+
? [...matched].filter(el => !el.isOmitted)
|
|
25
|
+
: [...matched];
|
|
14
26
|
if (exists.length === 0) {
|
|
15
27
|
const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
|
|
16
28
|
report({
|
|
@@ -30,7 +42,7 @@ export default createRule({
|
|
|
30
42
|
return;
|
|
31
43
|
}
|
|
32
44
|
for (const query of el.rule.value) {
|
|
33
|
-
const exists = [...el.children].find(child => child.matches(query));
|
|
45
|
+
const exists = [...el.children].find(child => !(el.rule.options.ignoreOmittedElements && child.isOmitted) && child.matches(query));
|
|
34
46
|
if (!exists) {
|
|
35
47
|
const message = t('Require {0}', t('the "{0*}" {1}', query, 'element'));
|
|
36
48
|
report({
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the required-h1 rule.
|
|
3
|
+
*/
|
|
1
4
|
export interface Options {
|
|
5
|
+
/** Whether to report a violation when more than one `<h1>` element is found. */
|
|
2
6
|
'expected-once': boolean;
|
|
7
|
+
/** Whether to apply this rule in document fragments (components, partials). */
|
|
3
8
|
'in-document-fragment': boolean;
|
|
4
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Rule that requires exactly one `<h1>` element in the document.
|
|
12
|
+
*
|
|
13
|
+
* Reports a violation when no `<h1>` is present. Optionally reports when
|
|
14
|
+
* multiple `<h1>` elements exist (controlled by `expected-once`). Can be
|
|
15
|
+
* configured to skip document fragments via the `in-document-fragment` option.
|
|
16
|
+
*/
|
|
5
17
|
declare const _default: Readonly<import("@markuplint/ml-core").RuleSeed<boolean, Options>>;
|
|
6
18
|
export default _default;
|
package/lib/required-h1/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { createRule } from '@markuplint/ml-core';
|
|
2
2
|
import meta from './meta.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rule that requires exactly one `<h1>` element in the document.
|
|
5
|
+
*
|
|
6
|
+
* Reports a violation when no `<h1>` is present. Optionally reports when
|
|
7
|
+
* multiple `<h1>` elements exist (controlled by `expected-once`). Can be
|
|
8
|
+
* configured to skip document fragments via the `in-document-fragment` option.
|
|
9
|
+
*/
|
|
3
10
|
export default createRule({
|
|
4
11
|
meta: meta,
|
|
5
12
|
defaultOptions: {
|
package/lib/required-h1/meta.js
CHANGED