@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.
- package/dist/index.mjs +3235 -0
- package/package.json +8 -7
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/plugin.js +0 -104
- package/lib/rules/accessKeys.d.ts +0 -9
- package/lib/rules/accessKeys.js +0 -30
- package/lib/rules/altTexts.d.ts +0 -9
- package/lib/rules/altTexts.js +0 -76
- package/lib/rules/anchorAmbiguousText.d.ts +0 -9
- package/lib/rules/anchorAmbiguousText.js +0 -55
- package/lib/rules/anchorContent.d.ts +0 -9
- package/lib/rules/anchorContent.js +0 -55
- package/lib/rules/anchorValidity.d.ts +0 -9
- package/lib/rules/anchorValidity.js +0 -77
- package/lib/rules/ariaActiveDescendantTabIndex.d.ts +0 -9
- package/lib/rules/ariaActiveDescendantTabIndex.js +0 -51
- package/lib/rules/ariaHiddenFocusables.d.ts +0 -9
- package/lib/rules/ariaHiddenFocusables.js +0 -66
- package/lib/rules/ariaPropTypes.d.ts +0 -9
- package/lib/rules/ariaPropTypes.js +0 -222
- package/lib/rules/ariaProps.d.ts +0 -9
- package/lib/rules/ariaProps.js +0 -90
- package/lib/rules/ariaRoleValidity.d.ts +0 -9
- package/lib/rules/ariaRoleValidity.js +0 -111
- package/lib/rules/ariaUnsupportedElements.d.ts +0 -9
- package/lib/rules/ariaUnsupportedElements.js +0 -86
- package/lib/rules/autoFocusProps.d.ts +0 -9
- package/lib/rules/autoFocusProps.js +0 -42
- package/lib/rules/autocomplete.d.ts +0 -9
- package/lib/rules/autocomplete.js +0 -120
- package/lib/rules/booleanValues.d.ts +0 -9
- package/lib/rules/booleanValues.js +0 -31
- package/lib/rules/bracedStatements.d.ts +0 -9
- package/lib/rules/bracedStatements.js +0 -31
- package/lib/rules/buttonTypes.d.ts +0 -9
- package/lib/rules/buttonTypes.js +0 -68
- package/lib/rules/childrenProps.d.ts +0 -9
- package/lib/rules/childrenProps.js +0 -37
- package/lib/rules/clickEventKeyEvents.d.ts +0 -9
- package/lib/rules/clickEventKeyEvents.js +0 -54
- package/lib/rules/commentTextNodes.d.ts +0 -9
- package/lib/rules/commentTextNodes.js +0 -30
- package/lib/rules/distractingElements.d.ts +0 -9
- package/lib/rules/distractingElements.js +0 -38
- package/lib/rules/elementChildrenValidity.d.ts +0 -9
- package/lib/rules/elementChildrenValidity.js +0 -52
- package/lib/rules/headingContents.d.ts +0 -9
- package/lib/rules/headingContents.js +0 -52
- package/lib/rules/htmlLangs.d.ts +0 -9
- package/lib/rules/htmlLangs.js +0 -30
- package/lib/rules/iframeTitles.d.ts +0 -9
- package/lib/rules/iframeTitles.js +0 -61
- package/lib/rules/interactiveElementRoles.d.ts +0 -9
- package/lib/rules/interactiveElementRoles.js +0 -91
- package/lib/rules/interactiveElementsFocusable.d.ts +0 -9
- package/lib/rules/interactiveElementsFocusable.js +0 -170
- package/lib/rules/labelAssociatedControls.d.ts +0 -9
- package/lib/rules/labelAssociatedControls.js +0 -68
- package/lib/rules/langValidity.d.ts +0 -9
- package/lib/rules/langValidity.js +0 -41
- package/lib/rules/mediaCaptions.d.ts +0 -9
- package/lib/rules/mediaCaptions.js +0 -50
- package/lib/rules/mouseEventKeyEvents.d.ts +0 -9
- package/lib/rules/mouseEventKeyEvents.js +0 -47
- package/lib/rules/nonInteractiveElementInteractions.d.ts +0 -9
- package/lib/rules/nonInteractiveElementInteractions.js +0 -93
- package/lib/rules/nonInteractiveElementRoles.d.ts +0 -9
- package/lib/rules/nonInteractiveElementRoles.js +0 -116
- package/lib/rules/nonInteractiveElementTabIndexes.d.ts +0 -9
- package/lib/rules/nonInteractiveElementTabIndexes.js +0 -107
- package/lib/rules/propDuplicates.d.ts +0 -9
- package/lib/rules/propDuplicates.js +0 -37
- package/lib/rules/roleRedundancies.d.ts +0 -9
- package/lib/rules/roleRedundancies.js +0 -74
- package/lib/rules/roleRequiredAriaProps.d.ts +0 -9
- package/lib/rules/roleRequiredAriaProps.js +0 -76
- package/lib/rules/roleSupportedAriaProps.d.ts +0 -9
- package/lib/rules/roleSupportedAriaProps.js +0 -417
- package/lib/rules/roleTags.d.ts +0 -9
- package/lib/rules/roleTags.js +0 -70
- package/lib/rules/ruleCreator.d.ts +0 -2
- package/lib/rules/ruleCreator.js +0 -13
- package/lib/rules/scopeProps.d.ts +0 -9
- package/lib/rules/scopeProps.js +0 -39
- package/lib/rules/staticElementInteractions.d.ts +0 -9
- package/lib/rules/staticElementInteractions.js +0 -58
- package/lib/rules/svgTitles.d.ts +0 -9
- package/lib/rules/svgTitles.js +0 -57
- package/lib/rules/tabIndexPositiveValues.d.ts +0 -9
- package/lib/rules/tabIndexPositiveValues.js +0 -43
- package/lib/rules/unescapedEntities.d.ts +0 -9
- package/lib/rules/unescapedEntities.js +0 -68
- package/lib/rules/unnecessaryFragments.d.ts +0 -9
- package/lib/rules/unnecessaryFragments.js +0 -49
- /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;
|