@flint.fyi/jsx 0.3.3 → 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/{lib/plugin.d.ts → dist/index.d.mts} +45 -48
  2. package/dist/index.mjs +3235 -0
  3. package/package.json +10 -10
  4. package/lib/index.d.ts +0 -2
  5. package/lib/index.js +0 -2
  6. package/lib/plugin.js +0 -106
  7. package/lib/rules/accessKeys.d.ts +0 -10
  8. package/lib/rules/accessKeys.js +0 -32
  9. package/lib/rules/altTexts.d.ts +0 -10
  10. package/lib/rules/altTexts.js +0 -78
  11. package/lib/rules/anchorAmbiguousText.d.ts +0 -10
  12. package/lib/rules/anchorAmbiguousText.js +0 -57
  13. package/lib/rules/anchorContent.d.ts +0 -10
  14. package/lib/rules/anchorContent.js +0 -57
  15. package/lib/rules/anchorValidity.d.ts +0 -10
  16. package/lib/rules/anchorValidity.js +0 -79
  17. package/lib/rules/ariaActiveDescendantTabIndex.d.ts +0 -10
  18. package/lib/rules/ariaActiveDescendantTabIndex.js +0 -53
  19. package/lib/rules/ariaHiddenFocusables.d.ts +0 -10
  20. package/lib/rules/ariaHiddenFocusables.js +0 -68
  21. package/lib/rules/ariaPropTypes.d.ts +0 -10
  22. package/lib/rules/ariaPropTypes.js +0 -224
  23. package/lib/rules/ariaProps.d.ts +0 -10
  24. package/lib/rules/ariaProps.js +0 -92
  25. package/lib/rules/ariaRoleValidity.d.ts +0 -10
  26. package/lib/rules/ariaRoleValidity.js +0 -113
  27. package/lib/rules/ariaUnsupportedElements.d.ts +0 -10
  28. package/lib/rules/ariaUnsupportedElements.js +0 -88
  29. package/lib/rules/autoFocusProps.d.ts +0 -10
  30. package/lib/rules/autoFocusProps.js +0 -44
  31. package/lib/rules/autocomplete.d.ts +0 -10
  32. package/lib/rules/autocomplete.js +0 -126
  33. package/lib/rules/booleanValues.d.ts +0 -10
  34. package/lib/rules/booleanValues.js +0 -33
  35. package/lib/rules/bracedStatements.d.ts +0 -10
  36. package/lib/rules/bracedStatements.js +0 -33
  37. package/lib/rules/buttonTypes.d.ts +0 -10
  38. package/lib/rules/buttonTypes.js +0 -76
  39. package/lib/rules/childrenProps.d.ts +0 -10
  40. package/lib/rules/childrenProps.js +0 -39
  41. package/lib/rules/clickEventKeyEvents.d.ts +0 -10
  42. package/lib/rules/clickEventKeyEvents.js +0 -56
  43. package/lib/rules/commentTextNodes.d.ts +0 -10
  44. package/lib/rules/commentTextNodes.js +0 -32
  45. package/lib/rules/distractingElements.d.ts +0 -10
  46. package/lib/rules/distractingElements.js +0 -40
  47. package/lib/rules/elementChildrenValidity.d.ts +0 -10
  48. package/lib/rules/elementChildrenValidity.js +0 -54
  49. package/lib/rules/headingContents.d.ts +0 -10
  50. package/lib/rules/headingContents.js +0 -54
  51. package/lib/rules/htmlLangs.d.ts +0 -10
  52. package/lib/rules/htmlLangs.js +0 -32
  53. package/lib/rules/iframeTitles.d.ts +0 -10
  54. package/lib/rules/iframeTitles.js +0 -63
  55. package/lib/rules/interactiveElementRoles.d.ts +0 -10
  56. package/lib/rules/interactiveElementRoles.js +0 -93
  57. package/lib/rules/interactiveElementsFocusable.d.ts +0 -10
  58. package/lib/rules/interactiveElementsFocusable.js +0 -172
  59. package/lib/rules/labelAssociatedControls.d.ts +0 -10
  60. package/lib/rules/labelAssociatedControls.js +0 -70
  61. package/lib/rules/langValidity.d.ts +0 -10
  62. package/lib/rules/langValidity.js +0 -43
  63. package/lib/rules/mediaCaptions.d.ts +0 -10
  64. package/lib/rules/mediaCaptions.js +0 -52
  65. package/lib/rules/mouseEventKeyEvents.d.ts +0 -10
  66. package/lib/rules/mouseEventKeyEvents.js +0 -49
  67. package/lib/rules/nonInteractiveElementInteractions.d.ts +0 -10
  68. package/lib/rules/nonInteractiveElementInteractions.js +0 -95
  69. package/lib/rules/nonInteractiveElementRoles.d.ts +0 -10
  70. package/lib/rules/nonInteractiveElementRoles.js +0 -118
  71. package/lib/rules/nonInteractiveElementTabIndexes.d.ts +0 -10
  72. package/lib/rules/nonInteractiveElementTabIndexes.js +0 -109
  73. package/lib/rules/propDuplicates.d.ts +0 -10
  74. package/lib/rules/propDuplicates.js +0 -39
  75. package/lib/rules/roleRedundancies.d.ts +0 -10
  76. package/lib/rules/roleRedundancies.js +0 -76
  77. package/lib/rules/roleRequiredAriaProps.d.ts +0 -10
  78. package/lib/rules/roleRequiredAriaProps.js +0 -78
  79. package/lib/rules/roleSupportedAriaProps.d.ts +0 -10
  80. package/lib/rules/roleSupportedAriaProps.js +0 -419
  81. package/lib/rules/roleTags.d.ts +0 -10
  82. package/lib/rules/roleTags.js +0 -72
  83. package/lib/rules/ruleCreator.d.ts +0 -3
  84. package/lib/rules/ruleCreator.js +0 -15
  85. package/lib/rules/scopeProps.d.ts +0 -10
  86. package/lib/rules/scopeProps.js +0 -41
  87. package/lib/rules/staticElementInteractions.d.ts +0 -10
  88. package/lib/rules/staticElementInteractions.js +0 -60
  89. package/lib/rules/svgTitles.d.ts +0 -10
  90. package/lib/rules/svgTitles.js +0 -59
  91. package/lib/rules/tabIndexPositiveValues.d.ts +0 -10
  92. package/lib/rules/tabIndexPositiveValues.js +0 -45
  93. package/lib/rules/unescapedEntities.d.ts +0 -10
  94. package/lib/rules/unescapedEntities.js +0 -70
  95. package/lib/rules/unnecessaryFragments.d.ts +0 -10
  96. package/lib/rules/unnecessaryFragments.js +0 -51
@@ -1,68 +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/ariaHiddenFocusables.ts
5
- const focusableElements = new Set([
6
- "a",
7
- "audio",
8
- "button",
9
- "input",
10
- "select",
11
- "textarea",
12
- "video"
13
- ]);
14
- var ariaHiddenFocusables_default = ruleCreator.createRule(typescriptLanguage, {
15
- about: {
16
- description: "Reports elements with aria-hidden='true' that are focusable.",
17
- id: "ariaHiddenFocusables",
18
- presets: ["logical", "logicalStrict"]
19
- },
20
- messages: { ariaHiddenFocusable: {
21
- primary: "This element has `aria-hidden=\"true\"` but is focusable, which is misleading to users navigating with keyboards.",
22
- secondary: ["Elements with aria-hidden='true' should not be reachable via keyboard navigation.", "This creates confusion when users can focus elements they cannot perceive with a screen reader."],
23
- suggestions: [
24
- "Remove aria-hidden=\"true\"",
25
- "Add tabIndex=\"-1\" to remove from focus order",
26
- "Use a non-focusable element"
27
- ]
28
- } },
29
- setup(context) {
30
- function checkElement(node, { sourceFile }) {
31
- const { attributes, tagName } = node;
32
- if (tagName.kind !== SyntaxKind.Identifier) return;
33
- const ariaHiddenProperty = attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text.toLowerCase() === "aria-hidden");
34
- if (ariaHiddenProperty?.kind !== SyntaxKind.JsxAttribute || !isAriaHiddenTrue(ariaHiddenProperty)) return;
35
- const tabIndexValue = findTabIndexValue(node);
36
- if (tabIndexValue === -1) return;
37
- if (focusableElements.has(tagName.text.toLowerCase()) || tabIndexValue !== void 0 && tabIndexValue >= 0) context.report({
38
- message: "ariaHiddenFocusable",
39
- range: getTSNodeRange(ariaHiddenProperty, sourceFile)
40
- });
41
- }
42
- return { visitors: {
43
- JsxOpeningElement: checkElement,
44
- JsxSelfClosingElement: checkElement
45
- } };
46
- }
47
- });
48
- function findTabIndexValue(node) {
49
- const tabIndexProperty = node.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text.toLowerCase() === "tabindex");
50
- if (!tabIndexProperty?.initializer) return;
51
- if (tabIndexProperty.initializer.kind === SyntaxKind.JsxExpression) {
52
- const expression = tabIndexProperty.initializer.expression;
53
- if (expression?.kind === SyntaxKind.NumericLiteral) return Number(expression.text);
54
- }
55
- if (tabIndexProperty.initializer.kind === SyntaxKind.StringLiteral) return Number(tabIndexProperty.initializer.text);
56
- }
57
- function isAriaHiddenTrue(ariaHiddenProperty) {
58
- if (!ariaHiddenProperty.initializer) return false;
59
- if (ariaHiddenProperty.initializer.kind === SyntaxKind.StringLiteral) return ariaHiddenProperty.initializer.text === "true";
60
- if (ariaHiddenProperty.initializer.kind === SyntaxKind.JsxExpression) {
61
- if (ariaHiddenProperty.initializer.expression?.kind === SyntaxKind.TrueKeyword) return true;
62
- }
63
- return false;
64
- }
65
- //#endregion
66
- export { ariaHiddenFocusables_default as default };
67
-
68
- //# sourceMappingURL=ariaHiddenFocusables.js.map
@@ -1,10 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports ARIA properties with invalid value types.";
3
- readonly id: "ariaPropTypes";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "invalidPropType", undefined>;
9
- export default _default;
10
- //# sourceMappingURL=ariaPropTypes.d.ts.map
@@ -1,224 +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/ariaPropTypes.ts
5
- const ariaPropertyTypes = {
6
- "aria-activedescendant": "id",
7
- "aria-atomic": "boolean",
8
- "aria-autocomplete": "token",
9
- "aria-busy": "boolean",
10
- "aria-checked": "tristate",
11
- "aria-colcount": "integer",
12
- "aria-colindex": "integer",
13
- "aria-colspan": "integer",
14
- "aria-controls": "idlist",
15
- "aria-current": "token",
16
- "aria-describedby": "idlist",
17
- "aria-details": "id",
18
- "aria-disabled": "boolean",
19
- "aria-dropeffect": "token",
20
- "aria-errormessage": "id",
21
- "aria-expanded": "tristate",
22
- "aria-flowto": "idlist",
23
- "aria-grabbed": "tristate",
24
- "aria-haspopup": "token",
25
- "aria-hidden": "tristate",
26
- "aria-invalid": "token",
27
- "aria-keyshortcuts": "string",
28
- "aria-label": "string",
29
- "aria-labelledby": "idlist",
30
- "aria-level": "integer",
31
- "aria-live": "token",
32
- "aria-modal": "boolean",
33
- "aria-multiline": "boolean",
34
- "aria-multiselectable": "boolean",
35
- "aria-orientation": "token",
36
- "aria-owns": "idlist",
37
- "aria-placeholder": "string",
38
- "aria-posinset": "integer",
39
- "aria-pressed": "tristate",
40
- "aria-readonly": "boolean",
41
- "aria-relevant": "token",
42
- "aria-required": "boolean",
43
- "aria-roledescription": "string",
44
- "aria-rowcount": "integer",
45
- "aria-rowindex": "integer",
46
- "aria-rowspan": "integer",
47
- "aria-selected": "tristate",
48
- "aria-setsize": "integer",
49
- "aria-sort": "token",
50
- "aria-valuemax": "number",
51
- "aria-valuemin": "number",
52
- "aria-valuenow": "number",
53
- "aria-valuetext": "string"
54
- };
55
- const tokenValues = {
56
- "aria-autocomplete": new Set([
57
- "both",
58
- "inline",
59
- "list",
60
- "none"
61
- ]),
62
- "aria-current": new Set([
63
- "date",
64
- "false",
65
- "location",
66
- "page",
67
- "step",
68
- "time",
69
- "true"
70
- ]),
71
- "aria-dropeffect": new Set([
72
- "copy",
73
- "execute",
74
- "link",
75
- "move",
76
- "none",
77
- "popup"
78
- ]),
79
- "aria-haspopup": new Set([
80
- "dialog",
81
- "false",
82
- "grid",
83
- "listbox",
84
- "menu",
85
- "tree",
86
- "true"
87
- ]),
88
- "aria-invalid": new Set([
89
- "false",
90
- "grammar",
91
- "spelling",
92
- "true"
93
- ]),
94
- "aria-live": new Set([
95
- "assertive",
96
- "off",
97
- "polite"
98
- ]),
99
- "aria-orientation": new Set([
100
- "horizontal",
101
- "undefined",
102
- "vertical"
103
- ]),
104
- "aria-relevant": new Set([
105
- "additions",
106
- "additions text",
107
- "all",
108
- "removals",
109
- "text"
110
- ]),
111
- "aria-sort": new Set([
112
- "ascending",
113
- "descending",
114
- "none",
115
- "other"
116
- ])
117
- };
118
- function getAttributeValue(property) {
119
- if (!property.initializer) return true;
120
- if (property.initializer.kind === SyntaxKind.StringLiteral) return property.initializer.text;
121
- if (property.initializer.kind === SyntaxKind.JsxExpression) {
122
- const expression = property.initializer.expression;
123
- if (!expression) return;
124
- if (expression.kind === SyntaxKind.TrueKeyword) return true;
125
- if (expression.kind === SyntaxKind.FalseKeyword) return false;
126
- if (expression.kind === SyntaxKind.NumericLiteral) return Number(expression.text);
127
- if (expression.kind === SyntaxKind.StringLiteral) return expression.text;
128
- }
129
- }
130
- function getExpectedValueDescription(propertyName, expectedType) {
131
- switch (expectedType) {
132
- case "boolean": return "true or false";
133
- case "id": return "an ID reference";
134
- case "idlist": return "a space-separated list of IDs";
135
- case "integer": return "an integer";
136
- case "number": return "a number";
137
- case "string": return "a string";
138
- case "token": {
139
- const validTokens = tokenValues[propertyName];
140
- if (validTokens !== void 0) {
141
- const tokens = Array.from(validTokens).sort();
142
- return tokens.length > 4 ? `one of: ${tokens.slice(0, 4).join(", ")}, ...` : `one of: ${tokens.join(", ")}`;
143
- }
144
- return "a valid token value";
145
- }
146
- case "tristate": return "true, false, or mixed";
147
- default: return "a valid value";
148
- }
149
- }
150
- function isValidPropertyValue(propertyName, value, expectedType) {
151
- if (value === void 0) return true;
152
- switch (expectedType) {
153
- case "boolean": return value === "true" || value === "false" || typeof value === "boolean";
154
- case "id": return typeof value === "string" && !!value.length;
155
- case "idlist": return typeof value === "string" && !!value.trim().length;
156
- case "integer":
157
- if (typeof value === "number") return Number.isInteger(value);
158
- if (typeof value === "string") {
159
- const num = Number(value);
160
- return !Number.isNaN(num) && Number.isInteger(num);
161
- }
162
- return false;
163
- case "number":
164
- if (typeof value === "number") return !Number.isNaN(value);
165
- if (typeof value === "string") {
166
- const num = Number(value);
167
- return !Number.isNaN(num);
168
- }
169
- return false;
170
- case "string": return typeof value === "string";
171
- case "token":
172
- if (typeof value !== "string") return false;
173
- return tokenValues[propertyName]?.has(value.toLowerCase()) ?? true;
174
- case "tristate": return value === "true" || value === "false" || value === "mixed" || typeof value === "boolean";
175
- default: return true;
176
- }
177
- }
178
- var ariaPropTypes_default = ruleCreator.createRule(typescriptLanguage, {
179
- about: {
180
- description: "Reports ARIA properties with invalid value types.",
181
- id: "ariaPropTypes",
182
- presets: ["logical", "logicalStrict"]
183
- },
184
- messages: { invalidPropType: {
185
- primary: "`{{ prop }}` should have a value of {{ expected }}, but received `{{ actual }}`.",
186
- secondary: [
187
- "ARIA properties must have values that match their expected type according to the WAI-ARIA specification.",
188
- "Invalid property values can confuse assistive technologies and lead to accessibility issues.",
189
- "This is required for WCAG 4.1.2 compliance."
190
- ],
191
- suggestions: ["Provide a value that matches the expected type for this ARIA property."]
192
- } },
193
- setup(context) {
194
- function checkElement(node, { sourceFile }) {
195
- for (const property of node.attributes.properties) {
196
- if (property.kind !== SyntaxKind.JsxAttribute || property.name.kind !== SyntaxKind.Identifier) continue;
197
- const propertyName = property.name.text.toLowerCase();
198
- if (!propertyName.startsWith("aria-")) continue;
199
- const expectedType = ariaPropertyTypes[propertyName];
200
- if (expectedType === void 0) continue;
201
- const value = getAttributeValue(property);
202
- if (isValidPropertyValue(propertyName, value, expectedType)) return;
203
- const expected = getExpectedValueDescription(propertyName, expectedType);
204
- context.report({
205
- data: {
206
- actual: String(value),
207
- expected,
208
- prop: propertyName
209
- },
210
- message: "invalidPropType",
211
- range: property.initializer ? getTSNodeRange(property.initializer, sourceFile) : getTSNodeRange(property.name, sourceFile)
212
- });
213
- }
214
- }
215
- return { visitors: {
216
- JsxOpeningElement: checkElement,
217
- JsxSelfClosingElement: checkElement
218
- } };
219
- }
220
- });
221
- //#endregion
222
- export { ariaPropTypes_default as default };
223
-
224
- //# sourceMappingURL=ariaPropTypes.js.map
@@ -1,10 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports invalid ARIA properties.";
3
- readonly id: "ariaProps";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "invalidAriaProp", undefined>;
9
- export default _default;
10
- //# sourceMappingURL=ariaProps.d.ts.map
@@ -1,92 +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/ariaProps.ts
5
- const validAriaProps = new Set([
6
- "aria-activedescendant",
7
- "aria-atomic",
8
- "aria-autocomplete",
9
- "aria-busy",
10
- "aria-checked",
11
- "aria-colcount",
12
- "aria-colindex",
13
- "aria-colspan",
14
- "aria-controls",
15
- "aria-current",
16
- "aria-describedby",
17
- "aria-details",
18
- "aria-disabled",
19
- "aria-dropeffect",
20
- "aria-errormessage",
21
- "aria-expanded",
22
- "aria-flowto",
23
- "aria-grabbed",
24
- "aria-haspopup",
25
- "aria-hidden",
26
- "aria-invalid",
27
- "aria-keyshortcuts",
28
- "aria-label",
29
- "aria-labelledby",
30
- "aria-level",
31
- "aria-live",
32
- "aria-modal",
33
- "aria-multiline",
34
- "aria-multiselectable",
35
- "aria-orientation",
36
- "aria-owns",
37
- "aria-placeholder",
38
- "aria-posinset",
39
- "aria-pressed",
40
- "aria-readonly",
41
- "aria-relevant",
42
- "aria-required",
43
- "aria-roledescription",
44
- "aria-rowcount",
45
- "aria-rowindex",
46
- "aria-rowspan",
47
- "aria-selected",
48
- "aria-setsize",
49
- "aria-sort",
50
- "aria-valuemax",
51
- "aria-valuemin",
52
- "aria-valuenow",
53
- "aria-valuetext"
54
- ]);
55
- var ariaProps_default = ruleCreator.createRule(typescriptLanguage, {
56
- about: {
57
- description: "Reports invalid ARIA properties.",
58
- id: "ariaProps",
59
- presets: ["logical", "logicalStrict"]
60
- },
61
- messages: { invalidAriaProp: {
62
- primary: "`{{ prop }}` is not a valid ARIA property and so has no effect on the browser.",
63
- secondary: [
64
- "This aria-* attribute is not a valid ARIA property according to the WAI-ARIA spec.",
65
- "Check the spelling of the property name.",
66
- "This is required for WCAG 4.1.2 compliance."
67
- ],
68
- suggestions: ["Use a valid property from the WAI-ARIA specification."]
69
- } },
70
- setup(context) {
71
- function checkElement(node, { sourceFile }) {
72
- for (const property of node.attributes.properties) {
73
- if (property.kind !== SyntaxKind.JsxAttribute || property.name.kind !== SyntaxKind.Identifier) continue;
74
- const propertyName = property.name.text;
75
- if (!propertyName.startsWith("aria-")) continue;
76
- if (!validAriaProps.has(propertyName.toLowerCase())) context.report({
77
- data: { prop: propertyName },
78
- message: "invalidAriaProp",
79
- range: getTSNodeRange(property.name, sourceFile)
80
- });
81
- }
82
- }
83
- return { visitors: {
84
- JsxOpeningElement: checkElement,
85
- JsxSelfClosingElement: checkElement
86
- } };
87
- }
88
- });
89
- //#endregion
90
- export { ariaProps_default as default };
91
-
92
- //# sourceMappingURL=ariaProps.js.map
@@ -1,10 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports invalid or abstract ARIA roles.";
3
- readonly id: "ariaRoleValidity";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "invalidRole", undefined>;
9
- export default _default;
10
- //# sourceMappingURL=ariaRoleValidity.d.ts.map
@@ -1,113 +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/ariaRoleValidity.ts
5
- const validAriaRoles = new Set([
6
- "alert",
7
- "alertdialog",
8
- "application",
9
- "article",
10
- "banner",
11
- "button",
12
- "cell",
13
- "checkbox",
14
- "columnheader",
15
- "combobox",
16
- "complementary",
17
- "contentinfo",
18
- "definition",
19
- "dialog",
20
- "directory",
21
- "document",
22
- "feed",
23
- "figure",
24
- "form",
25
- "grid",
26
- "gridcell",
27
- "group",
28
- "heading",
29
- "img",
30
- "link",
31
- "list",
32
- "listbox",
33
- "listitem",
34
- "log",
35
- "main",
36
- "marquee",
37
- "math",
38
- "menu",
39
- "menubar",
40
- "menuitem",
41
- "menuitemcheckbox",
42
- "menuitemradio",
43
- "meter",
44
- "navigation",
45
- "none",
46
- "note",
47
- "option",
48
- "presentation",
49
- "progressbar",
50
- "radio",
51
- "radiogroup",
52
- "region",
53
- "row",
54
- "rowgroup",
55
- "rowheader",
56
- "scrollbar",
57
- "search",
58
- "searchbox",
59
- "separator",
60
- "slider",
61
- "spinbutton",
62
- "status",
63
- "switch",
64
- "tab",
65
- "table",
66
- "tablist",
67
- "tabpanel",
68
- "term",
69
- "textbox",
70
- "timer",
71
- "toolbar",
72
- "tooltip",
73
- "tree",
74
- "treegrid",
75
- "treeitem"
76
- ]);
77
- var ariaRoleValidity_default = ruleCreator.createRule(typescriptLanguage, {
78
- about: {
79
- description: "Reports invalid or abstract ARIA roles.",
80
- id: "ariaRoleValidity",
81
- presets: ["logical", "logicalStrict"]
82
- },
83
- messages: { invalidRole: {
84
- primary: "Invalid ARIA role '{{ role }}'. Use a valid, non-abstract role.",
85
- secondary: [
86
- "ARIA roles must be valid according to the WAI-ARIA specification.",
87
- "Abstract roles cannot be used directly in HTML.",
88
- "Required for WCAG 4.1.2 compliance."
89
- ],
90
- suggestions: ["Use a valid ARIA role from the WAI-ARIA specification", "Remove the role attribute if not needed"]
91
- } },
92
- setup(context) {
93
- function checkRole(node, { sourceFile }) {
94
- if (node.tagName.kind !== SyntaxKind.Identifier || node.tagName.text.toLowerCase() !== node.tagName.text) return;
95
- const roleProperty = node.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier);
96
- if (roleProperty?.initializer?.kind !== SyntaxKind.StringLiteral) return;
97
- const role = roleProperty.initializer.text.trim();
98
- if (!role || !validAriaRoles.has(role)) context.report({
99
- data: { role: role || "(empty)" },
100
- message: "invalidRole",
101
- range: getTSNodeRange(roleProperty, sourceFile)
102
- });
103
- }
104
- return { visitors: {
105
- JsxOpeningElement: checkRole,
106
- JsxSelfClosingElement: checkRole
107
- } };
108
- }
109
- });
110
- //#endregion
111
- export { ariaRoleValidity_default as default };
112
-
113
- //# sourceMappingURL=ariaRoleValidity.js.map
@@ -1,10 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports ARIA attributes on elements that don't support them.";
3
- readonly id: "ariaUnsupportedElements";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "unsupportedElement", undefined>;
9
- export default _default;
10
- //# sourceMappingURL=ariaUnsupportedElements.d.ts.map
@@ -1,88 +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/ariaUnsupportedElements.ts
5
- const unsupportedElements = new Set([
6
- "base",
7
- "body",
8
- "caption",
9
- "col",
10
- "colgroup",
11
- "datalist",
12
- "dd",
13
- "details",
14
- "dialog",
15
- "dt",
16
- "fieldset",
17
- "figcaption",
18
- "figure",
19
- "footer",
20
- "form",
21
- "frame",
22
- "frameset",
23
- "head",
24
- "header",
25
- "hgroup",
26
- "html",
27
- "iframe",
28
- "legend",
29
- "link",
30
- "main",
31
- "meta",
32
- "meter",
33
- "nav",
34
- "noscript",
35
- "optgroup",
36
- "option",
37
- "output",
38
- "param",
39
- "picture",
40
- "progress",
41
- "script",
42
- "slot",
43
- "style",
44
- "template",
45
- "textarea",
46
- "title",
47
- "track"
48
- ]);
49
- var ariaUnsupportedElements_default = ruleCreator.createRule(typescriptLanguage, {
50
- about: {
51
- description: "Reports ARIA attributes on elements that don't support them.",
52
- id: "ariaUnsupportedElements",
53
- presets: ["logical", "logicalStrict"]
54
- },
55
- messages: { unsupportedElement: {
56
- primary: "The `{{ element }}` element does not support ARIA roles, states, or properties.",
57
- secondary: [
58
- "Certain reserved DOM elements do not support ARIA attributes.",
59
- "These elements are often not visible or have specific semantic meaning.",
60
- "This is required for WCAG 4.1.2 compliance."
61
- ],
62
- suggestions: ["Remove ARIA attributes from this element"]
63
- } },
64
- setup(context) {
65
- function checkElement(node, { sourceFile }) {
66
- if (node.tagName.kind !== SyntaxKind.Identifier) return;
67
- const elementName = node.tagName.text.toLowerCase();
68
- if (!unsupportedElements.has(elementName)) return;
69
- if (node.attributes.properties.some((property) => {
70
- if (property.kind !== SyntaxKind.JsxAttribute || property.name.kind !== SyntaxKind.Identifier) return false;
71
- const attributeName = property.name.text.toLowerCase();
72
- return attributeName === "role" || attributeName.startsWith("aria-");
73
- })) context.report({
74
- data: { element: elementName },
75
- message: "unsupportedElement",
76
- range: getTSNodeRange(node.tagName, sourceFile)
77
- });
78
- }
79
- return { visitors: {
80
- JsxOpeningElement: checkElement,
81
- JsxSelfClosingElement: checkElement
82
- } };
83
- }
84
- });
85
- //#endregion
86
- export { ariaUnsupportedElements_default as default };
87
-
88
- //# sourceMappingURL=ariaUnsupportedElements.js.map
@@ -1,10 +0,0 @@
1
- declare const _default: import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports autoFocus props that are not set to false.";
3
- readonly id: "autoFocusProps";
4
- readonly presets: readonly ["logical", "logicalStrict"];
5
- } & {
6
- readonly pluginId: string;
7
- readonly url: string;
8
- }, "noAutoFocus", undefined>;
9
- export default _default;
10
- //# sourceMappingURL=autoFocusProps.d.ts.map
@@ -1,44 +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/autoFocusProps.ts
5
- var autoFocusProps_default = ruleCreator.createRule(typescriptLanguage, {
6
- about: {
7
- description: "Reports autoFocus props that are not set to false.",
8
- id: "autoFocusProps",
9
- presets: ["logical", "logicalStrict"]
10
- },
11
- messages: { noAutoFocus: {
12
- primary: "The `autoFocus` prop disruptively forces unintuitive focus behavior.",
13
- secondary: [
14
- "Auto-focusing elements can cause usability issues for sighted and non-sighted users.",
15
- "It can be disruptive to users who rely on screen readers or keyboard navigation.",
16
- "Consider letting users focus elements manually instead."
17
- ],
18
- suggestions: ["Remove the autoFocus prop", "Set autoFocus to false: autoFocus={false}"]
19
- } },
20
- setup(context) {
21
- function isSetToFalse(property) {
22
- if (!property.initializer) return false;
23
- if (property.initializer.kind === SyntaxKind.StringLiteral) return true;
24
- if (property.initializer.kind === SyntaxKind.JsxExpression) {
25
- if (property.initializer.expression?.kind === SyntaxKind.FalseKeyword) return true;
26
- }
27
- return false;
28
- }
29
- function checkElement(node, { sourceFile }) {
30
- for (const property of node.attributes.properties) if (property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text.toLowerCase() === "autofocus" && !isSetToFalse(property)) context.report({
31
- message: "noAutoFocus",
32
- range: getTSNodeRange(property, sourceFile)
33
- });
34
- }
35
- return { visitors: {
36
- JsxOpeningElement: checkElement,
37
- JsxSelfClosingElement: checkElement
38
- } };
39
- }
40
- });
41
- //#endregion
42
- export { autoFocusProps_default as default };
43
-
44
- //# sourceMappingURL=autoFocusProps.js.map