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