@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,420 @@
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/roleSupportedAriaProps.ts
6
+ const globalAriaProperties = new Set([
7
+ "aria-atomic",
8
+ "aria-busy",
9
+ "aria-controls",
10
+ "aria-current",
11
+ "aria-describedby",
12
+ "aria-details",
13
+ "aria-disabled",
14
+ "aria-dropeffect",
15
+ "aria-errormessage",
16
+ "aria-flowto",
17
+ "aria-grabbed",
18
+ "aria-haspopup",
19
+ "aria-hidden",
20
+ "aria-invalid",
21
+ "aria-keyshortcuts",
22
+ "aria-label",
23
+ "aria-labelledby",
24
+ "aria-live",
25
+ "aria-owns",
26
+ "aria-relevant",
27
+ "aria-roledescription"
28
+ ]);
29
+ const roleToSupportedProps = {
30
+ alert: new Set(["aria-expanded"]),
31
+ alertdialog: new Set(["aria-expanded", "aria-modal"]),
32
+ application: new Set(["aria-activedescendant", "aria-expanded"]),
33
+ article: new Set([
34
+ "aria-expanded",
35
+ "aria-posinset",
36
+ "aria-setsize"
37
+ ]),
38
+ banner: new Set(["aria-expanded"]),
39
+ button: new Set(["aria-expanded", "aria-pressed"]),
40
+ cell: new Set([
41
+ "aria-colindex",
42
+ "aria-colspan",
43
+ "aria-expanded",
44
+ "aria-rowindex",
45
+ "aria-rowspan"
46
+ ]),
47
+ checkbox: new Set([
48
+ "aria-checked",
49
+ "aria-expanded",
50
+ "aria-readonly",
51
+ "aria-required"
52
+ ]),
53
+ columnheader: new Set([
54
+ "aria-colindex",
55
+ "aria-colspan",
56
+ "aria-expanded",
57
+ "aria-readonly",
58
+ "aria-required",
59
+ "aria-rowindex",
60
+ "aria-rowspan",
61
+ "aria-selected",
62
+ "aria-sort"
63
+ ]),
64
+ combobox: new Set([
65
+ "aria-activedescendant",
66
+ "aria-autocomplete",
67
+ "aria-expanded",
68
+ "aria-readonly",
69
+ "aria-required"
70
+ ]),
71
+ command: new Set(["aria-expanded"]),
72
+ complementary: new Set(["aria-expanded"]),
73
+ composite: new Set(["aria-activedescendant", "aria-expanded"]),
74
+ contentinfo: new Set(["aria-expanded"]),
75
+ definition: new Set(["aria-expanded"]),
76
+ dialog: new Set(["aria-expanded", "aria-modal"]),
77
+ directory: new Set(["aria-expanded"]),
78
+ document: new Set(["aria-expanded"]),
79
+ feed: new Set(["aria-expanded"]),
80
+ figure: new Set(["aria-expanded"]),
81
+ form: new Set(["aria-expanded"]),
82
+ grid: new Set([
83
+ "aria-activedescendant",
84
+ "aria-colcount",
85
+ "aria-expanded",
86
+ "aria-level",
87
+ "aria-multiselectable",
88
+ "aria-readonly",
89
+ "aria-rowcount"
90
+ ]),
91
+ gridcell: new Set([
92
+ "aria-colindex",
93
+ "aria-colspan",
94
+ "aria-expanded",
95
+ "aria-readonly",
96
+ "aria-required",
97
+ "aria-rowindex",
98
+ "aria-rowspan",
99
+ "aria-selected"
100
+ ]),
101
+ group: new Set(["aria-activedescendant", "aria-expanded"]),
102
+ heading: new Set(["aria-expanded", "aria-level"]),
103
+ img: new Set(["aria-expanded"]),
104
+ input: new Set(["aria-expanded"]),
105
+ landmark: new Set(["aria-expanded"]),
106
+ link: new Set(["aria-expanded"]),
107
+ list: new Set(["aria-expanded"]),
108
+ listbox: new Set([
109
+ "aria-activedescendant",
110
+ "aria-expanded",
111
+ "aria-multiselectable",
112
+ "aria-orientation",
113
+ "aria-readonly",
114
+ "aria-required"
115
+ ]),
116
+ listitem: new Set([
117
+ "aria-expanded",
118
+ "aria-level",
119
+ "aria-posinset",
120
+ "aria-setsize"
121
+ ]),
122
+ log: new Set(["aria-expanded"]),
123
+ main: new Set(["aria-expanded"]),
124
+ marquee: new Set(["aria-expanded"]),
125
+ math: new Set(["aria-expanded"]),
126
+ menu: new Set([
127
+ "aria-activedescendant",
128
+ "aria-expanded",
129
+ "aria-orientation"
130
+ ]),
131
+ menubar: new Set([
132
+ "aria-activedescendant",
133
+ "aria-expanded",
134
+ "aria-orientation"
135
+ ]),
136
+ menuitem: new Set([
137
+ "aria-expanded",
138
+ "aria-posinset",
139
+ "aria-setsize"
140
+ ]),
141
+ menuitemcheckbox: new Set([
142
+ "aria-checked",
143
+ "aria-expanded",
144
+ "aria-posinset",
145
+ "aria-readonly",
146
+ "aria-setsize"
147
+ ]),
148
+ menuitemradio: new Set([
149
+ "aria-checked",
150
+ "aria-expanded",
151
+ "aria-posinset",
152
+ "aria-readonly",
153
+ "aria-setsize"
154
+ ]),
155
+ navigation: new Set(["aria-expanded"]),
156
+ none: /* @__PURE__ */ new Set([]),
157
+ note: new Set(["aria-expanded"]),
158
+ option: new Set([
159
+ "aria-checked",
160
+ "aria-expanded",
161
+ "aria-posinset",
162
+ "aria-selected",
163
+ "aria-setsize"
164
+ ]),
165
+ presentation: /* @__PURE__ */ new Set([]),
166
+ progressbar: new Set([
167
+ "aria-expanded",
168
+ "aria-valuemax",
169
+ "aria-valuemin",
170
+ "aria-valuenow",
171
+ "aria-valuetext"
172
+ ]),
173
+ radio: new Set([
174
+ "aria-checked",
175
+ "aria-expanded",
176
+ "aria-posinset",
177
+ "aria-readonly",
178
+ "aria-setsize"
179
+ ]),
180
+ radiogroup: new Set([
181
+ "aria-activedescendant",
182
+ "aria-expanded",
183
+ "aria-orientation",
184
+ "aria-readonly",
185
+ "aria-required"
186
+ ]),
187
+ range: new Set([
188
+ "aria-expanded",
189
+ "aria-valuemax",
190
+ "aria-valuemin",
191
+ "aria-valuenow",
192
+ "aria-valuetext"
193
+ ]),
194
+ region: new Set(["aria-expanded"]),
195
+ roletype: new Set(["aria-expanded"]),
196
+ row: new Set([
197
+ "aria-activedescendant",
198
+ "aria-colindex",
199
+ "aria-expanded",
200
+ "aria-level",
201
+ "aria-rowindex",
202
+ "aria-selected"
203
+ ]),
204
+ rowgroup: new Set(["aria-activedescendant", "aria-expanded"]),
205
+ rowheader: new Set([
206
+ "aria-colindex",
207
+ "aria-colspan",
208
+ "aria-expanded",
209
+ "aria-readonly",
210
+ "aria-required",
211
+ "aria-rowindex",
212
+ "aria-rowspan",
213
+ "aria-selected",
214
+ "aria-sort"
215
+ ]),
216
+ scrollbar: new Set([
217
+ "aria-controls",
218
+ "aria-expanded",
219
+ "aria-orientation",
220
+ "aria-valuemax",
221
+ "aria-valuemin",
222
+ "aria-valuenow",
223
+ "aria-valuetext"
224
+ ]),
225
+ search: new Set(["aria-expanded"]),
226
+ searchbox: new Set([
227
+ "aria-activedescendant",
228
+ "aria-autocomplete",
229
+ "aria-expanded",
230
+ "aria-multiline",
231
+ "aria-placeholder",
232
+ "aria-readonly",
233
+ "aria-required"
234
+ ]),
235
+ section: new Set(["aria-expanded"]),
236
+ sectionhead: new Set(["aria-expanded"]),
237
+ select: new Set([
238
+ "aria-activedescendant",
239
+ "aria-expanded",
240
+ "aria-orientation"
241
+ ]),
242
+ separator: new Set([
243
+ "aria-expanded",
244
+ "aria-orientation",
245
+ "aria-valuemax",
246
+ "aria-valuemin",
247
+ "aria-valuenow",
248
+ "aria-valuetext"
249
+ ]),
250
+ slider: new Set([
251
+ "aria-expanded",
252
+ "aria-orientation",
253
+ "aria-readonly",
254
+ "aria-valuemax",
255
+ "aria-valuemin",
256
+ "aria-valuenow",
257
+ "aria-valuetext"
258
+ ]),
259
+ spinbutton: new Set([
260
+ "aria-activedescendant",
261
+ "aria-expanded",
262
+ "aria-readonly",
263
+ "aria-required",
264
+ "aria-valuemax",
265
+ "aria-valuemin",
266
+ "aria-valuenow",
267
+ "aria-valuetext"
268
+ ]),
269
+ status: new Set(["aria-expanded"]),
270
+ structure: new Set(["aria-expanded"]),
271
+ switch: new Set([
272
+ "aria-checked",
273
+ "aria-expanded",
274
+ "aria-readonly",
275
+ "aria-required"
276
+ ]),
277
+ tab: new Set([
278
+ "aria-expanded",
279
+ "aria-posinset",
280
+ "aria-selected",
281
+ "aria-setsize"
282
+ ]),
283
+ table: new Set([
284
+ "aria-colcount",
285
+ "aria-expanded",
286
+ "aria-level",
287
+ "aria-multiselectable",
288
+ "aria-rowcount"
289
+ ]),
290
+ tablist: new Set([
291
+ "aria-activedescendant",
292
+ "aria-expanded",
293
+ "aria-level",
294
+ "aria-multiselectable",
295
+ "aria-orientation"
296
+ ]),
297
+ tabpanel: new Set(["aria-expanded"]),
298
+ term: new Set(["aria-expanded"]),
299
+ textbox: new Set([
300
+ "aria-activedescendant",
301
+ "aria-autocomplete",
302
+ "aria-expanded",
303
+ "aria-multiline",
304
+ "aria-placeholder",
305
+ "aria-readonly",
306
+ "aria-required"
307
+ ]),
308
+ timer: new Set(["aria-expanded"]),
309
+ toolbar: new Set([
310
+ "aria-activedescendant",
311
+ "aria-expanded",
312
+ "aria-orientation"
313
+ ]),
314
+ tooltip: new Set(["aria-expanded"]),
315
+ tree: new Set([
316
+ "aria-activedescendant",
317
+ "aria-expanded",
318
+ "aria-multiselectable",
319
+ "aria-orientation",
320
+ "aria-required"
321
+ ]),
322
+ treegrid: new Set([
323
+ "aria-activedescendant",
324
+ "aria-colcount",
325
+ "aria-expanded",
326
+ "aria-level",
327
+ "aria-multiselectable",
328
+ "aria-orientation",
329
+ "aria-readonly",
330
+ "aria-required",
331
+ "aria-rowcount"
332
+ ]),
333
+ treeitem: new Set([
334
+ "aria-checked",
335
+ "aria-expanded",
336
+ "aria-level",
337
+ "aria-posinset",
338
+ "aria-selected",
339
+ "aria-setsize"
340
+ ]),
341
+ widget: new Set(["aria-expanded"]),
342
+ window: new Set(["aria-expanded", "aria-modal"])
343
+ };
344
+ const implicitRoles = {
345
+ a: "link",
346
+ article: "article",
347
+ aside: "complementary",
348
+ button: "button",
349
+ dialog: "dialog",
350
+ footer: "contentinfo",
351
+ form: "form",
352
+ h1: "heading",
353
+ h2: "heading",
354
+ h3: "heading",
355
+ h4: "heading",
356
+ h5: "heading",
357
+ h6: "heading",
358
+ header: "banner",
359
+ hr: "separator",
360
+ img: "img",
361
+ input: "textbox",
362
+ li: "listitem",
363
+ main: "main",
364
+ nav: "navigation",
365
+ ol: "list",
366
+ section: "region",
367
+ select: "listbox",
368
+ textarea: "textbox",
369
+ ul: "list"
370
+ };
371
+ function getSupportedPropsForRole(role) {
372
+ return new Set([...roleToSupportedProps[role] ?? [], ...globalAriaProperties]);
373
+ }
374
+ var roleSupportedAriaProps_default = ruleCreator.createRule(typescriptLanguage, {
375
+ about: {
376
+ description: "Reports ARIA properties that are not supported by an element's role.",
377
+ id: "roleSupportedAriaProps",
378
+ presets: ["logical"]
379
+ },
380
+ messages: { unsupportedProp: {
381
+ primary: "The `{{ prop }}` ARIA property is not supported by the `{{ role }}` role.",
382
+ secondary: [
383
+ "Elements with ARIA roles should only use ARIA properties that are supported by that role.",
384
+ "Using unsupported ARIA properties can confuse assistive technologies.",
385
+ "This is required for WCAG 4.1.2 compliance."
386
+ ],
387
+ suggestions: ["Remove the unsupported ARIA property or change the element's role."]
388
+ } },
389
+ setup(context) {
390
+ function checkElement(node, { sourceFile }) {
391
+ if (node.tagName.kind !== SyntaxKind.Identifier) return;
392
+ const elementName = node.tagName.text.toLowerCase();
393
+ const roleProperty = node.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text === "role");
394
+ const role = roleProperty && roleProperty.kind === SyntaxKind.JsxAttribute && roleProperty.initializer && roleProperty.initializer.kind === SyntaxKind.StringLiteral ? roleProperty.initializer.text.toLowerCase() : implicitRoles[elementName];
395
+ if (!role) return;
396
+ const supportedProps = getSupportedPropsForRole(role);
397
+ for (const property of node.attributes.properties) {
398
+ if (property.kind !== SyntaxKind.JsxAttribute || property.name.kind !== SyntaxKind.Identifier) continue;
399
+ const propertyName = property.name.text.toLowerCase();
400
+ if (!propertyName.startsWith("aria-") || supportedProps.has(propertyName)) continue;
401
+ context.report({
402
+ data: {
403
+ prop: propertyName,
404
+ role
405
+ },
406
+ message: "unsupportedProp",
407
+ range: getTSNodeRange(property.name, sourceFile)
408
+ });
409
+ }
410
+ }
411
+ return { visitors: {
412
+ JsxOpeningElement: checkElement,
413
+ JsxSelfClosingElement: checkElement
414
+ } };
415
+ }
416
+ });
417
+
418
+ //#endregion
419
+ export { roleSupportedAriaProps_default as default };
420
+ //# sourceMappingURL=roleSupportedAriaProps.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, "preferSemanticElement", import("@flint.fyi/core").AnyOptionalSchema>;
2
+ export default _default;
3
+ //# sourceMappingURL=roleTags.d.ts.map
@@ -0,0 +1,73 @@
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/roleTags.ts
6
+ const roleToElement = {
7
+ article: "article",
8
+ banner: "header",
9
+ button: "button",
10
+ checkbox: "input[type='checkbox']",
11
+ complementary: "aside",
12
+ contentinfo: "footer",
13
+ definition: "dd",
14
+ dialog: "dialog",
15
+ figure: "figure",
16
+ form: "form",
17
+ heading: "h1-h6",
18
+ img: "img",
19
+ link: "a",
20
+ list: "ul/ol",
21
+ listitem: "li",
22
+ main: "main",
23
+ navigation: "nav",
24
+ radio: "input[type='radio']",
25
+ region: "section",
26
+ row: "tr",
27
+ rowgroup: "tbody/tfoot/thead",
28
+ table: "table",
29
+ term: "dt",
30
+ textbox: "input[type='text']/textarea"
31
+ };
32
+ var roleTags_default = ruleCreator.createRule(typescriptLanguage, {
33
+ about: {
34
+ description: "Reports ARIA roles that have semantic HTML element equivalents.",
35
+ id: "roleTags",
36
+ presets: ["logical", "logicalStrict"]
37
+ },
38
+ messages: { preferSemanticElement: {
39
+ primary: "<{{ currentElement }}> with role='{{ role }}' is a less-accessible equivalent to <{{ semanticElement }}>.",
40
+ secondary: [
41
+ "Semantic HTML elements have built-in accessibility features.",
42
+ "Using native elements is more maintainable than ARIA roles.",
43
+ "Browsers provide better default behavior for semantic elements."
44
+ ],
45
+ suggestions: ["Replace with the semantic HTML element"]
46
+ } },
47
+ setup(context) {
48
+ function checkRole(node, { sourceFile }) {
49
+ if (node.tagName.kind !== SyntaxKind.Identifier || node.tagName.text.toLowerCase() !== node.tagName.text) return;
50
+ const roleProperty = node.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text === "role");
51
+ if (!roleProperty || roleProperty.kind !== SyntaxKind.JsxAttribute || !roleProperty.initializer || roleProperty.initializer.kind !== SyntaxKind.StringLiteral) return;
52
+ const role = roleProperty.initializer.text;
53
+ const semanticElement = roleToElement[role];
54
+ if (semanticElement) context.report({
55
+ data: {
56
+ currentElement: node.tagName.text,
57
+ role,
58
+ semanticElement
59
+ },
60
+ message: "preferSemanticElement",
61
+ range: getTSNodeRange(roleProperty, sourceFile)
62
+ });
63
+ }
64
+ return { visitors: {
65
+ JsxOpeningElement: checkRole,
66
+ JsxSelfClosingElement: checkRole
67
+ } };
68
+ }
69
+ });
70
+
71
+ //#endregion
72
+ export { roleTags_default as default };
73
+ //# sourceMappingURL=roleTags.js.map
@@ -0,0 +1,3 @@
1
+ import { RuleCreator } from "@flint.fyi/core";
2
+ export declare const ruleCreator: RuleCreator<"logical" | "logicalStrict" | "stylistic">;
3
+ //# sourceMappingURL=ruleCreator.d.ts.map
@@ -0,0 +1,16 @@
1
+ import { RuleCreator } from "@flint.fyi/core";
2
+
3
+ //#region src/rules/ruleCreator.ts
4
+ const ruleCreator = new RuleCreator({
5
+ docs: (ruleId) => `https://flint.fyi/rules/jsx/${ruleId.toLowerCase()}`,
6
+ pluginId: "jsx",
7
+ presets: [
8
+ "logical",
9
+ "logicalStrict",
10
+ "stylistic"
11
+ ]
12
+ });
13
+
14
+ //#endregion
15
+ export { ruleCreator };
16
+ //# sourceMappingURL=ruleCreator.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, "invalidScope", import("@flint.fyi/core").AnyOptionalSchema>;
2
+ export default _default;
3
+ //# sourceMappingURL=scopeProps.d.ts.map
@@ -0,0 +1,42 @@
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/scopeProps.ts
6
+ var scopeProps_default = ruleCreator.createRule(typescriptLanguage, {
7
+ about: {
8
+ description: "Reports scope props on non-th elements.",
9
+ id: "scopeProps",
10
+ presets: ["logical"]
11
+ },
12
+ messages: { invalidScope: {
13
+ primary: "The `scope` prop only has an effect on <th> elements.",
14
+ secondary: [
15
+ "The scope attribute defines whether a table header is a column header or row header.",
16
+ "Using it on non-<th> elements has no semantic meaning and may confuse assistive technologies.",
17
+ "This is required for WCAG 1.3.1 and 4.1.1 compliance."
18
+ ],
19
+ suggestions: ["Remove the scope prop from this element", "Change this element to a <th> element if it's a table header"]
20
+ } },
21
+ setup(context) {
22
+ function checkElement(node, { sourceFile }) {
23
+ if (node.tagName.kind !== SyntaxKind.Identifier || node.tagName.text.toLowerCase() === "th") return;
24
+ const scopeProperty = node.attributes.properties.find((property) => {
25
+ return property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text.toLowerCase() === "scope";
26
+ });
27
+ if (!scopeProperty) return;
28
+ context.report({
29
+ message: "invalidScope",
30
+ range: getTSNodeRange(scopeProperty, sourceFile)
31
+ });
32
+ }
33
+ return { visitors: {
34
+ JsxOpeningElement: checkElement,
35
+ JsxSelfClosingElement: checkElement
36
+ } };
37
+ }
38
+ });
39
+
40
+ //#endregion
41
+ export { scopeProps_default as default };
42
+ //# sourceMappingURL=scopeProps.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, "missingRole", import("@flint.fyi/core").AnyOptionalSchema>;
2
+ export default _default;
3
+ //# sourceMappingURL=staticElementInteractions.d.ts.map
@@ -0,0 +1,61 @@
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/staticElementInteractions.ts
6
+ const interactiveHandlers = [
7
+ "onClick",
8
+ "onKeyDown",
9
+ "onKeyPress",
10
+ "onKeyUp",
11
+ "onMouseDown",
12
+ "onMouseUp"
13
+ ];
14
+ const interactiveElements = new Set([
15
+ "a",
16
+ "button",
17
+ "input",
18
+ "select",
19
+ "textarea"
20
+ ]);
21
+ var staticElementInteractions_default = ruleCreator.createRule(typescriptLanguage, {
22
+ about: {
23
+ description: "Reports static elements with event handlers that lack ARIA roles.",
24
+ id: "staticElementInteractions",
25
+ presets: ["logical"]
26
+ },
27
+ messages: { missingRole: {
28
+ primary: "This static element that handles events is missing a role attribute.",
29
+ secondary: [
30
+ "Static HTML elements like <div> and <span> have no semantic meaning.",
31
+ "Interactive handlers require a role to convey purpose to assistive technology and search engine crawlers.",
32
+ "Required for WCAG 4.1.2 compliance."
33
+ ],
34
+ suggestions: ["Add a role attribute (e.g. `role='button'`)", "Use a semantic HTML element instead (e.g. `<button>`)"]
35
+ } },
36
+ setup(context) {
37
+ function checkElement(node, { sourceFile }) {
38
+ if (node.tagName.kind !== SyntaxKind.Identifier || node.tagName.text.toLowerCase() !== node.tagName.text) return;
39
+ const elementName = node.tagName.text.toLowerCase();
40
+ if (interactiveElements.has(elementName)) return;
41
+ let hadInteractiveHandler = false;
42
+ for (const property of node.attributes.properties) if (property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier) {
43
+ if (property.name.text === "role") return;
44
+ if (interactiveHandlers.includes(property.name.text)) hadInteractiveHandler = true;
45
+ }
46
+ if (!hadInteractiveHandler) return;
47
+ context.report({
48
+ message: "missingRole",
49
+ range: getTSNodeRange(node.tagName, sourceFile)
50
+ });
51
+ }
52
+ return { visitors: {
53
+ JsxOpeningElement: checkElement,
54
+ JsxSelfClosingElement: checkElement
55
+ } };
56
+ }
57
+ });
58
+
59
+ //#endregion
60
+ export { staticElementInteractions_default as default };
61
+ //# sourceMappingURL=staticElementInteractions.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, "missingTitle", import("@flint.fyi/core").AnyOptionalSchema>;
2
+ export default _default;
3
+ //# sourceMappingURL=svgTitles.d.ts.map