@flint.fyi/jsx 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/index.mjs +3235 -0
  2. package/package.json +8 -7
  3. package/lib/index.d.ts +0 -2
  4. package/lib/index.js +0 -2
  5. package/lib/plugin.js +0 -104
  6. package/lib/rules/accessKeys.d.ts +0 -9
  7. package/lib/rules/accessKeys.js +0 -30
  8. package/lib/rules/altTexts.d.ts +0 -9
  9. package/lib/rules/altTexts.js +0 -76
  10. package/lib/rules/anchorAmbiguousText.d.ts +0 -9
  11. package/lib/rules/anchorAmbiguousText.js +0 -55
  12. package/lib/rules/anchorContent.d.ts +0 -9
  13. package/lib/rules/anchorContent.js +0 -55
  14. package/lib/rules/anchorValidity.d.ts +0 -9
  15. package/lib/rules/anchorValidity.js +0 -77
  16. package/lib/rules/ariaActiveDescendantTabIndex.d.ts +0 -9
  17. package/lib/rules/ariaActiveDescendantTabIndex.js +0 -51
  18. package/lib/rules/ariaHiddenFocusables.d.ts +0 -9
  19. package/lib/rules/ariaHiddenFocusables.js +0 -66
  20. package/lib/rules/ariaPropTypes.d.ts +0 -9
  21. package/lib/rules/ariaPropTypes.js +0 -222
  22. package/lib/rules/ariaProps.d.ts +0 -9
  23. package/lib/rules/ariaProps.js +0 -90
  24. package/lib/rules/ariaRoleValidity.d.ts +0 -9
  25. package/lib/rules/ariaRoleValidity.js +0 -111
  26. package/lib/rules/ariaUnsupportedElements.d.ts +0 -9
  27. package/lib/rules/ariaUnsupportedElements.js +0 -86
  28. package/lib/rules/autoFocusProps.d.ts +0 -9
  29. package/lib/rules/autoFocusProps.js +0 -42
  30. package/lib/rules/autocomplete.d.ts +0 -9
  31. package/lib/rules/autocomplete.js +0 -120
  32. package/lib/rules/booleanValues.d.ts +0 -9
  33. package/lib/rules/booleanValues.js +0 -31
  34. package/lib/rules/bracedStatements.d.ts +0 -9
  35. package/lib/rules/bracedStatements.js +0 -31
  36. package/lib/rules/buttonTypes.d.ts +0 -9
  37. package/lib/rules/buttonTypes.js +0 -68
  38. package/lib/rules/childrenProps.d.ts +0 -9
  39. package/lib/rules/childrenProps.js +0 -37
  40. package/lib/rules/clickEventKeyEvents.d.ts +0 -9
  41. package/lib/rules/clickEventKeyEvents.js +0 -54
  42. package/lib/rules/commentTextNodes.d.ts +0 -9
  43. package/lib/rules/commentTextNodes.js +0 -30
  44. package/lib/rules/distractingElements.d.ts +0 -9
  45. package/lib/rules/distractingElements.js +0 -38
  46. package/lib/rules/elementChildrenValidity.d.ts +0 -9
  47. package/lib/rules/elementChildrenValidity.js +0 -52
  48. package/lib/rules/headingContents.d.ts +0 -9
  49. package/lib/rules/headingContents.js +0 -52
  50. package/lib/rules/htmlLangs.d.ts +0 -9
  51. package/lib/rules/htmlLangs.js +0 -30
  52. package/lib/rules/iframeTitles.d.ts +0 -9
  53. package/lib/rules/iframeTitles.js +0 -61
  54. package/lib/rules/interactiveElementRoles.d.ts +0 -9
  55. package/lib/rules/interactiveElementRoles.js +0 -91
  56. package/lib/rules/interactiveElementsFocusable.d.ts +0 -9
  57. package/lib/rules/interactiveElementsFocusable.js +0 -170
  58. package/lib/rules/labelAssociatedControls.d.ts +0 -9
  59. package/lib/rules/labelAssociatedControls.js +0 -68
  60. package/lib/rules/langValidity.d.ts +0 -9
  61. package/lib/rules/langValidity.js +0 -41
  62. package/lib/rules/mediaCaptions.d.ts +0 -9
  63. package/lib/rules/mediaCaptions.js +0 -50
  64. package/lib/rules/mouseEventKeyEvents.d.ts +0 -9
  65. package/lib/rules/mouseEventKeyEvents.js +0 -47
  66. package/lib/rules/nonInteractiveElementInteractions.d.ts +0 -9
  67. package/lib/rules/nonInteractiveElementInteractions.js +0 -93
  68. package/lib/rules/nonInteractiveElementRoles.d.ts +0 -9
  69. package/lib/rules/nonInteractiveElementRoles.js +0 -116
  70. package/lib/rules/nonInteractiveElementTabIndexes.d.ts +0 -9
  71. package/lib/rules/nonInteractiveElementTabIndexes.js +0 -107
  72. package/lib/rules/propDuplicates.d.ts +0 -9
  73. package/lib/rules/propDuplicates.js +0 -37
  74. package/lib/rules/roleRedundancies.d.ts +0 -9
  75. package/lib/rules/roleRedundancies.js +0 -74
  76. package/lib/rules/roleRequiredAriaProps.d.ts +0 -9
  77. package/lib/rules/roleRequiredAriaProps.js +0 -76
  78. package/lib/rules/roleSupportedAriaProps.d.ts +0 -9
  79. package/lib/rules/roleSupportedAriaProps.js +0 -417
  80. package/lib/rules/roleTags.d.ts +0 -9
  81. package/lib/rules/roleTags.js +0 -70
  82. package/lib/rules/ruleCreator.d.ts +0 -2
  83. package/lib/rules/ruleCreator.js +0 -13
  84. package/lib/rules/scopeProps.d.ts +0 -9
  85. package/lib/rules/scopeProps.js +0 -39
  86. package/lib/rules/staticElementInteractions.d.ts +0 -9
  87. package/lib/rules/staticElementInteractions.js +0 -58
  88. package/lib/rules/svgTitles.d.ts +0 -9
  89. package/lib/rules/svgTitles.js +0 -57
  90. package/lib/rules/tabIndexPositiveValues.d.ts +0 -9
  91. package/lib/rules/tabIndexPositiveValues.js +0 -43
  92. package/lib/rules/unescapedEntities.d.ts +0 -9
  93. package/lib/rules/unescapedEntities.js +0 -68
  94. package/lib/rules/unnecessaryFragments.d.ts +0 -9
  95. package/lib/rules/unnecessaryFragments.js +0 -49
  96. /package/{lib/plugin.d.ts → dist/index.d.mts} +0 -0
@@ -1,93 +0,0 @@
1
- import { ruleCreator } from "./ruleCreator.js";
2
- import { SyntaxKind } from "typescript";
3
- import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
4
- //#region src/rules/nonInteractiveElementInteractions.ts
5
- const interactiveHandlers = [
6
- "onClick",
7
- "onKeyDown",
8
- "onKeyPress",
9
- "onKeyUp",
10
- "onMouseDown",
11
- "onMouseUp"
12
- ];
13
- const nonInteractiveElements = /* @__PURE__ */ new Set([
14
- "area",
15
- "article",
16
- "aside",
17
- "body",
18
- "br",
19
- "details",
20
- "footer",
21
- "h1",
22
- "h2",
23
- "h3",
24
- "h4",
25
- "h5",
26
- "h6",
27
- "header",
28
- "hr",
29
- "img",
30
- "li",
31
- "main",
32
- "nav",
33
- "ol",
34
- "p",
35
- "section",
36
- "table",
37
- "td",
38
- "ul"
39
- ]);
40
- const nonInteractiveRoles = /* @__PURE__ */ new Set([
41
- "article",
42
- "banner",
43
- "complementary",
44
- "img",
45
- "listitem",
46
- "main",
47
- "navigation",
48
- "region",
49
- "tooltip"
50
- ]);
51
- var nonInteractiveElementInteractions_default = ruleCreator.createRule(typescriptLanguage, {
52
- about: {
53
- description: "Reports non-interactive elements with interactive event handlers.",
54
- id: "nonInteractiveElementInteractions",
55
- presets: ["logical", "logicalStrict"]
56
- },
57
- messages: { invalidHandler: {
58
- primary: "`<{{ element }}>` elements are non-interactive and so should not have interactive event handlers.",
59
- secondary: [
60
- "Non-interactive elements indicate content and containers in the user interface.",
61
- "Use native interactive elements like <button> or <a> instead, or add an interactive role.",
62
- "This is required for WCAG 4.1.2 compliance."
63
- ],
64
- suggestions: [
65
- "Use a native interactive element instead (e.g. <button>)",
66
- "Add an interactive role attribute (e.g. role='button')",
67
- "Move the handler to an inner element with an interactive role"
68
- ]
69
- } },
70
- setup(context) {
71
- function checkElement(element, { sourceFile }) {
72
- if (element.tagName.kind !== SyntaxKind.Identifier) return;
73
- const elementName = element.tagName.text.toLowerCase();
74
- if (elementName !== element.tagName.text || !nonInteractiveElements.has(elementName)) return;
75
- let hasInteractiveHandler = false;
76
- for (const property of element.attributes.properties) if (property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier) {
77
- if (property.name.text === "role" && property.initializer?.kind === SyntaxKind.StringLiteral && !nonInteractiveRoles.has(property.initializer.text)) return;
78
- if (interactiveHandlers.includes(property.name.text)) hasInteractiveHandler = true;
79
- }
80
- if (hasInteractiveHandler) context.report({
81
- data: { element: elementName },
82
- message: "invalidHandler",
83
- range: getTSNodeRange(element.tagName, sourceFile)
84
- });
85
- }
86
- return { visitors: {
87
- JsxOpeningElement: checkElement,
88
- JsxSelfClosingElement: checkElement
89
- } };
90
- }
91
- });
92
- //#endregion
93
- export { nonInteractiveElementInteractions_default as default };
@@ -1,9 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports non-interactive elements with interactive ARIA roles.";
3
- readonly id: "nonInteractiveElementRoles";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "invalidRole", undefined>;
9
- export default _default;
@@ -1,116 +0,0 @@
1
- import { ruleCreator } from "./ruleCreator.js";
2
- import { SyntaxKind } from "typescript";
3
- import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
4
- //#region src/rules/nonInteractiveElementRoles.ts
5
- const nonInteractiveElements = /* @__PURE__ */ new Set([
6
- "article",
7
- "aside",
8
- "body",
9
- "br",
10
- "details",
11
- "div",
12
- "footer",
13
- "h1",
14
- "h2",
15
- "h3",
16
- "h4",
17
- "h5",
18
- "h6",
19
- "header",
20
- "hr",
21
- "img",
22
- "li",
23
- "main",
24
- "nav",
25
- "ol",
26
- "p",
27
- "section",
28
- "table",
29
- "td",
30
- "ul"
31
- ]);
32
- const interactiveRoles = /* @__PURE__ */ new Set([
33
- "button",
34
- "checkbox",
35
- "link",
36
- "menuitem",
37
- "menuitemcheckbox",
38
- "menuitemradio",
39
- "option",
40
- "radio",
41
- "searchbox",
42
- "slider",
43
- "spinbutton",
44
- "switch",
45
- "tab",
46
- "textbox"
47
- ]);
48
- const allowedExceptions = {
49
- li: /* @__PURE__ */ new Set([
50
- "menuitem",
51
- "option",
52
- "row",
53
- "tab",
54
- "treeitem"
55
- ]),
56
- ol: /* @__PURE__ */ new Set([
57
- "listbox",
58
- "menu",
59
- "menubar",
60
- "radiogroup",
61
- "tablist",
62
- "tree",
63
- "treegrid"
64
- ]),
65
- table: /* @__PURE__ */ new Set(["grid"]),
66
- td: /* @__PURE__ */ new Set(["gridcell"]),
67
- ul: /* @__PURE__ */ new Set([
68
- "listbox",
69
- "menu",
70
- "menubar",
71
- "radiogroup",
72
- "tablist",
73
- "tree",
74
- "treegrid"
75
- ])
76
- };
77
- var nonInteractiveElementRoles_default = ruleCreator.createRule(typescriptLanguage, {
78
- about: {
79
- description: "Reports non-interactive elements with interactive ARIA roles.",
80
- id: "nonInteractiveElementRoles",
81
- presets: ["logical", "logicalStrict"]
82
- },
83
- messages: { invalidRole: {
84
- primary: "Non-interactive element <{{ element }}> should not have the interactive role `'{{ role }}'`.",
85
- secondary: [
86
- "Non-interactive elements should not be converted to interactive controls using ARIA roles.",
87
- "Use native interactive elements like <button> or <a> instead.",
88
- "This is required for WCAG 4.1.2 compliance."
89
- ],
90
- suggestions: ["Use a native interactive element instead", "Remove the role attribute"]
91
- } },
92
- setup(context) {
93
- function checkElement(element, { sourceFile }) {
94
- if (element.tagName.kind !== SyntaxKind.Identifier) return;
95
- const elementName = element.tagName.text.toLowerCase();
96
- if (!nonInteractiveElements.has(elementName)) return;
97
- const roleProperty = element.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text === "role");
98
- if (roleProperty?.kind !== SyntaxKind.JsxAttribute || roleProperty.initializer?.kind !== SyntaxKind.StringLiteral) return;
99
- const role = roleProperty.initializer.text;
100
- if (interactiveRoles.has(role) && !allowedExceptions[elementName]?.has(role)) context.report({
101
- data: {
102
- element: elementName,
103
- role
104
- },
105
- message: "invalidRole",
106
- range: getTSNodeRange(roleProperty, sourceFile)
107
- });
108
- }
109
- return { visitors: {
110
- JsxOpeningElement: checkElement,
111
- JsxSelfClosingElement: checkElement
112
- } };
113
- }
114
- });
115
- //#endregion
116
- export { nonInteractiveElementRoles_default as default };
@@ -1,9 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports non-interactive elements with positive or zero tabIndex values.";
3
- readonly id: "nonInteractiveElementTabIndexes";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "nonInteractiveTabIndex", undefined>;
9
- export default _default;
@@ -1,107 +0,0 @@
1
- import { ruleCreator } from "./ruleCreator.js";
2
- import { SyntaxKind } from "typescript";
3
- import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
4
- //#region src/rules/nonInteractiveElementTabIndexes.ts
5
- const nonInteractiveElements = /* @__PURE__ */ new Set([
6
- "article",
7
- "aside",
8
- "body",
9
- "br",
10
- "details",
11
- "div",
12
- "footer",
13
- "h1",
14
- "h2",
15
- "h3",
16
- "h4",
17
- "h5",
18
- "h6",
19
- "header",
20
- "hr",
21
- "img",
22
- "li",
23
- "main",
24
- "nav",
25
- "ol",
26
- "p",
27
- "section",
28
- "span",
29
- "ul"
30
- ]);
31
- const interactiveRoles = /* @__PURE__ */ new Set([
32
- "button",
33
- "checkbox",
34
- "link",
35
- "menuitem",
36
- "menuitemcheckbox",
37
- "menuitemradio",
38
- "option",
39
- "radio",
40
- "searchbox",
41
- "slider",
42
- "spinbutton",
43
- "switch",
44
- "tab",
45
- "textbox"
46
- ]);
47
- var nonInteractiveElementTabIndexes_default = ruleCreator.createRule(typescriptLanguage, {
48
- about: {
49
- description: "Reports non-interactive elements with positive or zero tabIndex values.",
50
- id: "nonInteractiveElementTabIndexes",
51
- presets: ["logical", "logicalStrict"]
52
- },
53
- messages: { nonInteractiveTabIndex: {
54
- primary: "Non-interactive element `<{{ element }}>` should not have an explicit, non-negative `tabIndex`.",
55
- secondary: [
56
- "Tab navigation should be limited to interactive elements.",
57
- "Non-interactive elements with tabIndex add unnecessary stops in the tab order.",
58
- "Use tabIndex='-1' if you need to programmatically focus the element."
59
- ],
60
- suggestions: [
61
- "Remove the tabIndex attribute",
62
- "Use tabIndex='-1' instead",
63
- "Add an interactive role to the element"
64
- ]
65
- } },
66
- setup(context) {
67
- function getTabIndexValue(attr) {
68
- if (!attr.initializer) return;
69
- if (attr.initializer.kind === SyntaxKind.StringLiteral) {
70
- const value = parseInt(attr.initializer.text, 10);
71
- return Number.isNaN(value) ? void 0 : value;
72
- }
73
- if (attr.initializer.kind === SyntaxKind.JsxExpression) {
74
- const expr = attr.initializer.expression;
75
- if (expr?.kind === SyntaxKind.NumericLiteral) {
76
- const value = parseInt(expr.text, 10);
77
- return Number.isNaN(value) ? void 0 : value;
78
- }
79
- }
80
- }
81
- function getRoleValue(attributes) {
82
- const roleProperty = attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text === "role");
83
- if (roleProperty?.kind === SyntaxKind.JsxAttribute && roleProperty.initializer?.kind === SyntaxKind.StringLiteral) return roleProperty.initializer.text;
84
- }
85
- function checkTabIndex(node, { sourceFile }) {
86
- if (node.tagName.kind !== SyntaxKind.Identifier) return;
87
- const elementName = node.tagName.text.toLowerCase();
88
- if (!nonInteractiveElements.has(elementName)) return;
89
- const role = getRoleValue(node.attributes);
90
- if (role && interactiveRoles.has(role)) return;
91
- const tabIndexProperty = node.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text === "tabIndex");
92
- if (tabIndexProperty?.kind !== SyntaxKind.JsxAttribute) return;
93
- const tabIndexValue = getTabIndexValue(tabIndexProperty);
94
- if (tabIndexValue !== void 0 && tabIndexValue >= 0) context.report({
95
- data: { element: elementName },
96
- message: "nonInteractiveTabIndex",
97
- range: getTSNodeRange(tabIndexProperty, sourceFile)
98
- });
99
- }
100
- return { visitors: {
101
- JsxOpeningElement: checkTabIndex,
102
- JsxSelfClosingElement: checkTabIndex
103
- } };
104
- }
105
- });
106
- //#endregion
107
- export { nonInteractiveElementTabIndexes_default as default };
@@ -1,9 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Disallow duplicate props in JSX elements.";
3
- readonly id: "propDuplicates";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "duplicateProp", undefined>;
9
- export default _default;
@@ -1,37 +0,0 @@
1
- import { ruleCreator } from "./ruleCreator.js";
2
- import { SyntaxKind } from "typescript";
3
- import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
4
- //#region src/rules/propDuplicates.ts
5
- var propDuplicates_default = ruleCreator.createRule(typescriptLanguage, {
6
- about: {
7
- description: "Disallow duplicate props in JSX elements.",
8
- id: "propDuplicates",
9
- presets: ["logical", "logicalStrict"]
10
- },
11
- messages: { duplicateProp: {
12
- primary: "Duplicate prop `{{ propName }}` found in JSX element. The last occurrence will override earlier ones.",
13
- secondary: ["Having duplicate props can lead to unexpected behavior since only the last value is used.", "This makes the code harder to understand and maintain."],
14
- suggestions: ["Remove the duplicate prop and keep only one definition."]
15
- } },
16
- setup(context) {
17
- function checkElement(node, { sourceFile }) {
18
- const seenProps = /* @__PURE__ */ new Set();
19
- for (const property of node.attributes.properties) {
20
- if (property.kind !== SyntaxKind.JsxAttribute || property.name.kind !== SyntaxKind.Identifier) continue;
21
- const propName = property.name.text;
22
- if (seenProps.has(propName)) context.report({
23
- data: { propName },
24
- message: "duplicateProp",
25
- range: getTSNodeRange(property.name, sourceFile)
26
- });
27
- else seenProps.add(propName);
28
- }
29
- }
30
- return { visitors: {
31
- JsxOpeningElement: checkElement,
32
- JsxSelfClosingElement: checkElement
33
- } };
34
- }
35
- });
36
- //#endregion
37
- export { propDuplicates_default as default };
@@ -1,9 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports redundant ARIA roles on elements with implicit roles.";
3
- readonly id: "roleRedundancies";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "redundantRole", undefined>;
9
- export default _default;
@@ -1,74 +0,0 @@
1
- import { ruleCreator } from "./ruleCreator.js";
2
- import { SyntaxKind } from "typescript";
3
- import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
4
- //#region src/rules/roleRedundancies.ts
5
- const implicitRoles = {
6
- a: "link",
7
- article: "article",
8
- aside: "complementary",
9
- button: "button",
10
- dialog: "dialog",
11
- footer: "contentinfo",
12
- form: "form",
13
- h1: "heading",
14
- h2: "heading",
15
- h3: "heading",
16
- h4: "heading",
17
- h5: "heading",
18
- h6: "heading",
19
- header: "banner",
20
- hr: "separator",
21
- img: "img",
22
- input: "textbox",
23
- li: "listitem",
24
- main: "main",
25
- nav: "navigation",
26
- ol: "list",
27
- section: "region",
28
- select: "listbox",
29
- textarea: "textbox",
30
- ul: "list"
31
- };
32
- var roleRedundancies_default = ruleCreator.createRule(typescriptLanguage, {
33
- about: {
34
- description: "Reports redundant ARIA roles on elements with implicit roles.",
35
- id: "roleRedundancies",
36
- presets: ["logical", "logicalStrict"]
37
- },
38
- messages: { redundantRole: {
39
- primary: "`<{{ element }}>` elements already implicitly have a role of `{{ role }}`. This explicit role is unnecessary.",
40
- secondary: ["HTML elements have default semantics implemented by the browser.", "Setting an explicit role that matches the implicit role is redundant and doesn't provide any benefit."],
41
- suggestions: ["Remove the redundant role attribute"]
42
- } },
43
- setup(context) {
44
- function checkRedundantRole(node, { sourceFile }) {
45
- if (node.tagName.kind !== SyntaxKind.Identifier) return;
46
- const element = node.tagName.text.toLowerCase();
47
- const implicitRole = implicitRoles[element];
48
- if (!implicitRole) return;
49
- const roleProperty = node.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text === "role");
50
- if (roleProperty?.kind === SyntaxKind.JsxAttribute && roleProperty.initializer?.kind === SyntaxKind.StringLiteral && roleProperty.initializer.text === implicitRole) {
51
- const range = getTSNodeRange(roleProperty, sourceFile);
52
- context.report({
53
- data: {
54
- element,
55
- role: roleProperty.initializer.text
56
- },
57
- message: "redundantRole",
58
- range,
59
- suggestions: [{
60
- id: "removeRole",
61
- range,
62
- text: ""
63
- }]
64
- });
65
- }
66
- }
67
- return { visitors: {
68
- JsxOpeningElement: checkRedundantRole,
69
- JsxSelfClosingElement: checkRedundantRole
70
- } };
71
- }
72
- });
73
- //#endregion
74
- export { roleRedundancies_default as default };
@@ -1,9 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports ARIA roles missing their required ARIA properties.";
3
- readonly id: "roleRequiredAriaProps";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "missingRequiredProps", undefined>;
9
- export default _default;
@@ -1,76 +0,0 @@
1
- import { ruleCreator } from "./ruleCreator.js";
2
- import { SyntaxKind } from "typescript";
3
- import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
4
- //#region src/rules/roleRequiredAriaProps.ts
5
- const requiredAriaPropsForRole = {
6
- checkbox: ["aria-checked"],
7
- combobox: ["aria-controls", "aria-expanded"],
8
- heading: ["aria-level"],
9
- menuitemcheckbox: ["aria-checked"],
10
- menuitemradio: ["aria-checked"],
11
- option: ["aria-selected"],
12
- radio: ["aria-checked"],
13
- scrollbar: [
14
- "aria-controls",
15
- "aria-valuenow",
16
- "aria-valuemax",
17
- "aria-valuemin"
18
- ],
19
- slider: [
20
- "aria-valuenow",
21
- "aria-valuemax",
22
- "aria-valuemin"
23
- ],
24
- spinbutton: [
25
- "aria-valuenow",
26
- "aria-valuemax",
27
- "aria-valuemin"
28
- ],
29
- switch: ["aria-checked"]
30
- };
31
- var roleRequiredAriaProps_default = ruleCreator.createRule(typescriptLanguage, {
32
- about: {
33
- description: "Reports ARIA roles missing their required ARIA properties.",
34
- id: "roleRequiredAriaProps",
35
- presets: ["logical", "logicalStrict"]
36
- },
37
- messages: { missingRequiredProps: {
38
- primary: "Elements with ARIA role `{{ role }}` must have the following ARIA property(s) defined: {{ props }}.",
39
- secondary: [
40
- "ARIA roles define how elements are exposed to assistive technologies.",
41
- "Some roles require specific ARIA properties to function correctly.",
42
- "This is required for WCAG 4.1.2 compliance."
43
- ],
44
- suggestions: ["Add the required ARIA properties to the element."]
45
- } },
46
- setup(context) {
47
- function checkElement(node, { sourceFile }) {
48
- const roleAttribute = node.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text === "role");
49
- if (roleAttribute?.kind !== SyntaxKind.JsxAttribute || roleAttribute.initializer?.kind !== SyntaxKind.StringLiteral) return;
50
- const role = roleAttribute.initializer.text.toLowerCase();
51
- const requiredProps = requiredAriaPropsForRole[role];
52
- if (!requiredProps) return;
53
- const existingProps = /* @__PURE__ */ new Set();
54
- for (const property of node.attributes.properties) {
55
- if (property.kind !== SyntaxKind.JsxAttribute || property.name.kind !== SyntaxKind.Identifier) continue;
56
- const propertyName = property.name.text.toLowerCase();
57
- if (propertyName.startsWith("aria-")) existingProps.add(propertyName);
58
- }
59
- const missingProps = requiredProps.filter((prop) => !existingProps.has(prop));
60
- if (missingProps.length) context.report({
61
- data: {
62
- props: missingProps.join(", "),
63
- role
64
- },
65
- message: "missingRequiredProps",
66
- range: getTSNodeRange(roleAttribute, sourceFile)
67
- });
68
- }
69
- return { visitors: {
70
- JsxOpeningElement: checkElement,
71
- JsxSelfClosingElement: checkElement
72
- } };
73
- }
74
- });
75
- //#endregion
76
- export { roleRequiredAriaProps_default as default };
@@ -1,9 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports ARIA properties that are not supported by an element's role.";
3
- readonly id: "roleSupportedAriaProps";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "unsupportedProp", undefined>;
9
- export default _default;