@flint.fyi/jsx 0.4.0 → 0.6.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 +9 -8
- 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,120 +0,0 @@
|
|
|
1
|
-
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { SyntaxKind } from "typescript";
|
|
3
|
-
import { getStaticStringValue, getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
4
|
-
import { nullThrows } from "@flint.fyi/utils";
|
|
5
|
-
//#region src/rules/autocomplete.ts
|
|
6
|
-
const validAutocompleteValues = /* @__PURE__ */ new Set([
|
|
7
|
-
"address-level1",
|
|
8
|
-
"address-level2",
|
|
9
|
-
"address-level3",
|
|
10
|
-
"address-level4",
|
|
11
|
-
"address-line1",
|
|
12
|
-
"address-line2",
|
|
13
|
-
"address-line3",
|
|
14
|
-
"bday",
|
|
15
|
-
"bday-day",
|
|
16
|
-
"bday-month",
|
|
17
|
-
"bday-year",
|
|
18
|
-
"cc-additional-name",
|
|
19
|
-
"cc-csc",
|
|
20
|
-
"cc-exp",
|
|
21
|
-
"cc-exp-month",
|
|
22
|
-
"cc-exp-year",
|
|
23
|
-
"cc-family-name",
|
|
24
|
-
"cc-given-name",
|
|
25
|
-
"cc-name",
|
|
26
|
-
"cc-number",
|
|
27
|
-
"cc-type",
|
|
28
|
-
"country",
|
|
29
|
-
"country-name",
|
|
30
|
-
"current-password",
|
|
31
|
-
"email",
|
|
32
|
-
"impp",
|
|
33
|
-
"language",
|
|
34
|
-
"name",
|
|
35
|
-
"new-password",
|
|
36
|
-
"off",
|
|
37
|
-
"on",
|
|
38
|
-
"one-time-code",
|
|
39
|
-
"organization",
|
|
40
|
-
"organization-title",
|
|
41
|
-
"photo",
|
|
42
|
-
"postal-code",
|
|
43
|
-
"sex",
|
|
44
|
-
"street-address",
|
|
45
|
-
"tel",
|
|
46
|
-
"tel-area-code",
|
|
47
|
-
"tel-country-code",
|
|
48
|
-
"tel-extension",
|
|
49
|
-
"tel-local",
|
|
50
|
-
"tel-national",
|
|
51
|
-
"transaction-amount",
|
|
52
|
-
"transaction-currency",
|
|
53
|
-
"url",
|
|
54
|
-
"username",
|
|
55
|
-
"webauthn"
|
|
56
|
-
]);
|
|
57
|
-
const billingAndShippingValues = /* @__PURE__ */ new Set([
|
|
58
|
-
"address-level1",
|
|
59
|
-
"address-level2",
|
|
60
|
-
"address-level3",
|
|
61
|
-
"address-level4",
|
|
62
|
-
"address-line1",
|
|
63
|
-
"address-line2",
|
|
64
|
-
"address-line3",
|
|
65
|
-
"country",
|
|
66
|
-
"country-name",
|
|
67
|
-
"postal-code",
|
|
68
|
-
"street-address"
|
|
69
|
-
]);
|
|
70
|
-
function isValidAutocompleteValue(value) {
|
|
71
|
-
const parts = value.trim().split(/\s+/);
|
|
72
|
-
if (parts.length === 1) return validAutocompleteValues.has(nullThrows(parts[0], "First part is expected to be present by prior length check"));
|
|
73
|
-
if (parts.length === 2) {
|
|
74
|
-
const [prefix, token] = parts;
|
|
75
|
-
if (prefix === "billing" || prefix === "shipping") return billingAndShippingValues.has(nullThrows(token, "Second part is expected to be present by prior length check"));
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
var autocomplete_default = ruleCreator.createRule(typescriptLanguage, {
|
|
80
|
-
about: {
|
|
81
|
-
description: "Ensure the autocomplete attribute is correct and suitable for the form field.",
|
|
82
|
-
id: "autocomplete",
|
|
83
|
-
presets: ["logical", "logicalStrict"]
|
|
84
|
-
},
|
|
85
|
-
messages: { invalid: {
|
|
86
|
-
primary: "`{{ value }}` is not a valid value for autocomplete.",
|
|
87
|
-
secondary: [
|
|
88
|
-
"The autocomplete attribute must use valid tokens from the HTML specification.",
|
|
89
|
-
"Valid values help browsers and assistive technologies provide better user experiences.",
|
|
90
|
-
"This is required for WCAG 1.3.5 compliance."
|
|
91
|
-
],
|
|
92
|
-
suggestions: [
|
|
93
|
-
"Use standard autocomplete tokens like 'email', 'name', 'off', or 'tel'",
|
|
94
|
-
"For address fields, use 'billing' or 'shipping' prefix with appropriate field tokens",
|
|
95
|
-
"Check the HTML specification for the complete list of valid autocomplete tokens"
|
|
96
|
-
]
|
|
97
|
-
} },
|
|
98
|
-
setup(context) {
|
|
99
|
-
function checkNode(node, { sourceFile }) {
|
|
100
|
-
const { attributes, tagName } = node;
|
|
101
|
-
if (tagName.kind !== SyntaxKind.Identifier) return;
|
|
102
|
-
if (tagName.text.toLowerCase() !== "input") return;
|
|
103
|
-
const autocomplete = attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text.toLowerCase() === "autocomplete");
|
|
104
|
-
if (autocomplete?.kind !== SyntaxKind.JsxAttribute || !autocomplete.initializer) return;
|
|
105
|
-
const value = getStaticStringValue(autocomplete.initializer);
|
|
106
|
-
if (value === void 0 || isValidAutocompleteValue(value)) return;
|
|
107
|
-
context.report({
|
|
108
|
-
data: { value },
|
|
109
|
-
message: "invalid",
|
|
110
|
-
range: getTSNodeRange(autocomplete.name, sourceFile)
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
return { visitors: {
|
|
114
|
-
JsxOpeningElement: checkNode,
|
|
115
|
-
JsxSelfClosingElement: checkNode
|
|
116
|
-
} };
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
//#endregion
|
|
120
|
-
export { autocomplete_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Prefer shorthand boolean attributes over explicit {true} values in JSX.";
|
|
3
|
-
readonly id: "booleanValues";
|
|
4
|
-
readonly presets: readonly ["stylistic"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "preferShorthand", undefined>;
|
|
9
|
-
export default _default;
|
|
@@ -1,31 +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/booleanValues.ts
|
|
5
|
-
var booleanValues_default = ruleCreator.createRule(typescriptLanguage, {
|
|
6
|
-
about: {
|
|
7
|
-
description: "Prefer shorthand boolean attributes over explicit {true} values in JSX.",
|
|
8
|
-
id: "booleanValues",
|
|
9
|
-
presets: ["stylistic"]
|
|
10
|
-
},
|
|
11
|
-
messages: { preferShorthand: {
|
|
12
|
-
primary: "Prefer shorthand boolean attribute `{{ name }}` over explicit `{{ name }}={true}`.",
|
|
13
|
-
secondary: ["Boolean attributes with explicit `{true}` values are redundant and verbose.", "The shorthand syntax is more concise and idiomatic in JSX."],
|
|
14
|
-
suggestions: ["Use the shorthand syntax: `{{ name }}`"]
|
|
15
|
-
} },
|
|
16
|
-
setup(context) {
|
|
17
|
-
function checkElement(node, { sourceFile }) {
|
|
18
|
-
for (const property of node.attributes.properties) if (property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.initializer?.kind === SyntaxKind.JsxExpression && property.initializer.expression?.kind === SyntaxKind.TrueKeyword) context.report({
|
|
19
|
-
data: { name: property.name.text },
|
|
20
|
-
message: "preferShorthand",
|
|
21
|
-
range: getTSNodeRange(property, sourceFile)
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
return { visitors: {
|
|
25
|
-
JsxOpeningElement: checkElement,
|
|
26
|
-
JsxSelfClosingElement: checkElement
|
|
27
|
-
} };
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
//#endregion
|
|
31
|
-
export { booleanValues_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Disallow unnecessary JSX curly braces around literals and JSX elements.";
|
|
3
|
-
readonly id: "bracedStatements";
|
|
4
|
-
readonly presets: readonly ["stylistic"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "unnecessaryBraces", undefined>;
|
|
9
|
-
export default _default;
|
|
@@ -1,31 +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/bracedStatements.ts
|
|
5
|
-
var bracedStatements_default = ruleCreator.createRule(typescriptLanguage, {
|
|
6
|
-
about: {
|
|
7
|
-
description: "Disallow unnecessary JSX curly braces around literals and JSX elements.",
|
|
8
|
-
id: "bracedStatements",
|
|
9
|
-
presets: ["stylistic"]
|
|
10
|
-
},
|
|
11
|
-
messages: { unnecessaryBraces: {
|
|
12
|
-
primary: "Curly braces are unnecessary around {{ type }}.",
|
|
13
|
-
secondary: ["Curly braces are unnecessary when they wrap simple literals or JSX elements.", "Removing them improves readability and reduces visual clutter."],
|
|
14
|
-
suggestions: ["Remove the curly braces and use the content directly."]
|
|
15
|
-
} },
|
|
16
|
-
setup(context) {
|
|
17
|
-
return { visitors: { JsxExpression(node, { sourceFile }) {
|
|
18
|
-
if (!node.expression || node.parent.kind !== SyntaxKind.JsxElement && node.parent.kind !== SyntaxKind.JsxFragment) return;
|
|
19
|
-
let unnecessaryType;
|
|
20
|
-
if (node.expression.kind === SyntaxKind.StringLiteral) unnecessaryType = "string literals";
|
|
21
|
-
else if (node.expression.kind === SyntaxKind.JsxElement || node.expression.kind === SyntaxKind.JsxSelfClosingElement || node.expression.kind === SyntaxKind.JsxFragment) unnecessaryType = "JSX elements";
|
|
22
|
-
if (unnecessaryType) context.report({
|
|
23
|
-
data: { type: unnecessaryType },
|
|
24
|
-
message: "unnecessaryBraces",
|
|
25
|
-
range: getTSNodeRange(node, sourceFile)
|
|
26
|
-
});
|
|
27
|
-
} } };
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
//#endregion
|
|
31
|
-
export { bracedStatements_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports button elements without an explicit type attribute.";
|
|
3
|
-
readonly id: "buttonTypes";
|
|
4
|
-
readonly presets: readonly ["logical", "logicalStrict"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "invalidType" | "missingType", undefined>;
|
|
9
|
-
export default _default;
|
package/lib/rules/buttonTypes.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { SyntaxKind } from "typescript";
|
|
3
|
-
import { getStaticStringValue, getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
4
|
-
//#region src/rules/buttonTypes.ts
|
|
5
|
-
const validButtonTypes = /* @__PURE__ */ new Set([
|
|
6
|
-
"button",
|
|
7
|
-
"reset",
|
|
8
|
-
"submit"
|
|
9
|
-
]);
|
|
10
|
-
var buttonTypes_default = ruleCreator.createRule(typescriptLanguage, {
|
|
11
|
-
about: {
|
|
12
|
-
description: "Reports button elements without an explicit type attribute.",
|
|
13
|
-
id: "buttonTypes",
|
|
14
|
-
presets: ["logical", "logicalStrict"]
|
|
15
|
-
},
|
|
16
|
-
messages: {
|
|
17
|
-
invalidType: {
|
|
18
|
-
primary: "Button type '{{ type }}' is invalid. Use 'button', 'submit', or 'reset'.",
|
|
19
|
-
secondary: ["The type attribute must be one of: 'button', 'submit', or 'reset'.", "Invalid types will not behave as expected in all browsers."],
|
|
20
|
-
suggestions: [
|
|
21
|
-
"Change to type='button' for general action buttons",
|
|
22
|
-
"Change to type='submit' for form submission buttons",
|
|
23
|
-
"Change to type='reset' for form reset buttons"
|
|
24
|
-
]
|
|
25
|
-
},
|
|
26
|
-
missingType: {
|
|
27
|
-
primary: "It is generally preferable to add an explicit `type` attribute to buttons.",
|
|
28
|
-
secondary: [
|
|
29
|
-
"Buttons without an explicit type default to 'submit', which can cause unintended form submissions.",
|
|
30
|
-
"Valid button types are 'button', 'submit', and 'reset'.",
|
|
31
|
-
"Specify an explicit type to make the button's behavior clear."
|
|
32
|
-
],
|
|
33
|
-
suggestions: [
|
|
34
|
-
"Add type='button' for general action buttons",
|
|
35
|
-
"Add type='submit' for form submission buttons",
|
|
36
|
-
"Add type='reset' for form reset buttons"
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
setup(context) {
|
|
41
|
-
function checkElement(node, { sourceFile }) {
|
|
42
|
-
if (node.tagName.kind !== SyntaxKind.Identifier) return;
|
|
43
|
-
if (node.tagName.text.toLowerCase() !== "button") return;
|
|
44
|
-
const typeAttribute = node.attributes.properties.find((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text.toLowerCase() === "type");
|
|
45
|
-
if (!typeAttribute) {
|
|
46
|
-
context.report({
|
|
47
|
-
message: "missingType",
|
|
48
|
-
range: getTSNodeRange(node.tagName, sourceFile)
|
|
49
|
-
});
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const typeInitializer = typeAttribute.initializer;
|
|
53
|
-
const typeExpression = typeInitializer?.kind === SyntaxKind.JsxExpression ? typeInitializer.expression : typeInitializer;
|
|
54
|
-
const typeValue = typeExpression && getStaticStringValue(typeExpression);
|
|
55
|
-
if (typeValue && !validButtonTypes.has(typeValue)) context.report({
|
|
56
|
-
data: { type: typeValue },
|
|
57
|
-
message: "invalidType",
|
|
58
|
-
range: getTSNodeRange(typeAttribute, sourceFile)
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return { visitors: {
|
|
62
|
-
JsxOpeningElement: checkElement,
|
|
63
|
-
JsxSelfClosingElement: checkElement
|
|
64
|
-
} };
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
//#endregion
|
|
68
|
-
export { buttonTypes_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports usage of the children prop.";
|
|
3
|
-
readonly id: "childrenProps";
|
|
4
|
-
readonly presets: readonly ["stylistic"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "noChildrenProp", 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/childrenProps.ts
|
|
5
|
-
var childrenProps_default = ruleCreator.createRule(typescriptLanguage, {
|
|
6
|
-
about: {
|
|
7
|
-
description: "Reports usage of the children prop.",
|
|
8
|
-
id: "childrenProps",
|
|
9
|
-
presets: ["stylistic"]
|
|
10
|
-
},
|
|
11
|
-
messages: { noChildrenProp: {
|
|
12
|
-
primary: "Prefer providing children as content between opening and closing tags, not as a `children` prop.",
|
|
13
|
-
secondary: [
|
|
14
|
-
"Using the `children` prop is not idiomatic in JSX.",
|
|
15
|
-
"Pass children as content between opening and closing tags instead.",
|
|
16
|
-
"This makes the JSX more readable and maintainable."
|
|
17
|
-
],
|
|
18
|
-
suggestions: ["Remove the children prop and pass children as content between tags."]
|
|
19
|
-
} },
|
|
20
|
-
setup(context) {
|
|
21
|
-
function checkElement(node, { sourceFile }) {
|
|
22
|
-
for (const property of node.attributes.properties) if (property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text === "children") {
|
|
23
|
-
context.report({
|
|
24
|
-
message: "noChildrenProp",
|
|
25
|
-
range: getTSNodeRange(property, sourceFile)
|
|
26
|
-
});
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return { visitors: {
|
|
31
|
-
JsxOpeningElement: checkElement,
|
|
32
|
-
JsxSelfClosingElement: checkElement
|
|
33
|
-
} };
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
//#endregion
|
|
37
|
-
export { childrenProps_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports onClick without keyboard event handlers.";
|
|
3
|
-
readonly id: "clickEventKeyEvents";
|
|
4
|
-
readonly presets: readonly ["logical", "logicalStrict"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "missingKeyEvent", undefined>;
|
|
9
|
-
export default _default;
|
|
@@ -1,54 +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/clickEventKeyEvents.ts
|
|
5
|
-
const interactiveElements = /* @__PURE__ */ new Set([
|
|
6
|
-
"a",
|
|
7
|
-
"button",
|
|
8
|
-
"input",
|
|
9
|
-
"select",
|
|
10
|
-
"textarea"
|
|
11
|
-
]);
|
|
12
|
-
var clickEventKeyEvents_default = ruleCreator.createRule(typescriptLanguage, {
|
|
13
|
-
about: {
|
|
14
|
-
description: "Reports onClick without keyboard event handlers.",
|
|
15
|
-
id: "clickEventKeyEvents",
|
|
16
|
-
presets: ["logical", "logicalStrict"]
|
|
17
|
-
},
|
|
18
|
-
messages: { missingKeyEvent: {
|
|
19
|
-
primary: "This `onClick` is missing accompanying `onKeyUp`, `onKeyDown`, and/or `onKeyPress` keyboard events.",
|
|
20
|
-
secondary: [
|
|
21
|
-
"Click events should have keyboard equivalents for users who cannot use a mouse.",
|
|
22
|
-
"This is important for users with physical disabilities and screen reader users.",
|
|
23
|
-
"Required for WCAG 2.1.1 compliance."
|
|
24
|
-
],
|
|
25
|
-
suggestions: ["Add onKeyDown, onKeyUp, and/or onKeyPress handlers", "Use a button element which is inherently keyboard accessible"]
|
|
26
|
-
} },
|
|
27
|
-
setup(context) {
|
|
28
|
-
function checkClickEvent(node, { sourceFile }) {
|
|
29
|
-
if (node.tagName.kind !== SyntaxKind.Identifier || node.tagName.text.toLowerCase() !== node.tagName.text) return;
|
|
30
|
-
const elementName = node.tagName.text.toLowerCase();
|
|
31
|
-
if (interactiveElements.has(elementName)) return;
|
|
32
|
-
let onClickName;
|
|
33
|
-
for (const property of node.attributes.properties) if (property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier) switch (property.name.text) {
|
|
34
|
-
case "aria-hidden":
|
|
35
|
-
case "onKeyDown":
|
|
36
|
-
case "onKeyPress":
|
|
37
|
-
case "onKeyUp": return;
|
|
38
|
-
case "onClick":
|
|
39
|
-
onClickName = property.name;
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
if (onClickName) context.report({
|
|
43
|
-
message: "missingKeyEvent",
|
|
44
|
-
range: getTSNodeRange(onClickName, sourceFile)
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
return { visitors: {
|
|
48
|
-
JsxOpeningElement: checkClickEvent,
|
|
49
|
-
JsxSelfClosingElement: checkClickEvent
|
|
50
|
-
} };
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
//#endregion
|
|
54
|
-
export { clickEventKeyEvents_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports JSX text nodes that contain comment syntax but are rendered as text.";
|
|
3
|
-
readonly id: "commentTextNodes";
|
|
4
|
-
readonly presets: readonly ["logical", "logicalStrict"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "commentAsText", undefined>;
|
|
9
|
-
export default _default;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
3
|
-
//#region src/rules/commentTextNodes.ts
|
|
4
|
-
var commentTextNodes_default = ruleCreator.createRule(typescriptLanguage, {
|
|
5
|
-
about: {
|
|
6
|
-
description: "Reports JSX text nodes that contain comment syntax but are rendered as text.",
|
|
7
|
-
id: "commentTextNodes",
|
|
8
|
-
presets: ["logical", "logicalStrict"]
|
|
9
|
-
},
|
|
10
|
-
messages: { commentAsText: {
|
|
11
|
-
primary: "This text looks like a comment but will be rendered as text in the JSX output.",
|
|
12
|
-
secondary: [
|
|
13
|
-
"In JSX, text that looks like comments (// or /* */) is rendered as literal text.",
|
|
14
|
-
"To add actual comments in JSX, wrap them in braces: {/* comment */}.",
|
|
15
|
-
"If this is intentional text, consider making it clearer that it's not a comment."
|
|
16
|
-
],
|
|
17
|
-
suggestions: ["Use {/* comment */} for actual comments", "Remove the comment-like syntax if it's unintended"]
|
|
18
|
-
} },
|
|
19
|
-
setup(context) {
|
|
20
|
-
return { visitors: { JsxText(node, { sourceFile }) {
|
|
21
|
-
const text = node.text;
|
|
22
|
-
if (/^\s*(?:\/\/|\/\*)/.test(text)) context.report({
|
|
23
|
-
message: "commentAsText",
|
|
24
|
-
range: getTSNodeRange(node, sourceFile)
|
|
25
|
-
});
|
|
26
|
-
} } };
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
//#endregion
|
|
30
|
-
export { commentTextNodes_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports distracting elements like <marquee> and <blink>.";
|
|
3
|
-
readonly id: "distractingElements";
|
|
4
|
-
readonly presets: readonly ["logical", "logicalStrict"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "noDistractingElement", undefined>;
|
|
9
|
-
export default _default;
|
|
@@ -1,38 +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/distractingElements.ts
|
|
5
|
-
const distractingElements = /* @__PURE__ */ new Set(["blink", "marquee"]);
|
|
6
|
-
var distractingElements_default = ruleCreator.createRule(typescriptLanguage, {
|
|
7
|
-
about: {
|
|
8
|
-
description: "Reports distracting elements like <marquee> and <blink>.",
|
|
9
|
-
id: "distractingElements",
|
|
10
|
-
presets: ["logical", "logicalStrict"]
|
|
11
|
-
},
|
|
12
|
-
messages: { noDistractingElement: {
|
|
13
|
-
primary: "The <{{ element }}> element is distracting and deprecated.",
|
|
14
|
-
secondary: [
|
|
15
|
-
"Visually distracting elements cause accessibility issues for users with visual impairments.",
|
|
16
|
-
"The <marquee> and <blink> elements are deprecated and should not be used.",
|
|
17
|
-
"Use modern CSS animations if movement is necessary for your design."
|
|
18
|
-
],
|
|
19
|
-
suggestions: ["Remove the distracting element", "Use CSS animations for visual effects"]
|
|
20
|
-
} },
|
|
21
|
-
setup(context) {
|
|
22
|
-
function checkElement({ tagName }, { sourceFile }) {
|
|
23
|
-
if (tagName.kind !== SyntaxKind.Identifier) return;
|
|
24
|
-
const elementName = tagName.text.toLowerCase();
|
|
25
|
-
if (distractingElements.has(elementName)) context.report({
|
|
26
|
-
data: { element: elementName },
|
|
27
|
-
message: "noDistractingElement",
|
|
28
|
-
range: getTSNodeRange(tagName, sourceFile)
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return { visitors: {
|
|
32
|
-
JsxOpeningElement: checkElement,
|
|
33
|
-
JsxSelfClosingElement: checkElement
|
|
34
|
-
} };
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
//#endregion
|
|
38
|
-
export { distractingElements_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports void DOM elements that have children, which is invalid HTML.";
|
|
3
|
-
readonly id: "elementChildrenValidity";
|
|
4
|
-
readonly presets: readonly ["logical", "logicalStrict"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "voidElementWithChildren", undefined>;
|
|
9
|
-
export default _default;
|
|
@@ -1,52 +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/elementChildrenValidity.ts
|
|
5
|
-
const voidElements = /* @__PURE__ */ new Set([
|
|
6
|
-
"area",
|
|
7
|
-
"base",
|
|
8
|
-
"br",
|
|
9
|
-
"col",
|
|
10
|
-
"embed",
|
|
11
|
-
"hr",
|
|
12
|
-
"img",
|
|
13
|
-
"input",
|
|
14
|
-
"link",
|
|
15
|
-
"meta",
|
|
16
|
-
"param",
|
|
17
|
-
"source",
|
|
18
|
-
"track",
|
|
19
|
-
"wbr"
|
|
20
|
-
]);
|
|
21
|
-
var elementChildrenValidity_default = ruleCreator.createRule(typescriptLanguage, {
|
|
22
|
-
about: {
|
|
23
|
-
description: "Reports void DOM elements that have children, which is invalid HTML.",
|
|
24
|
-
id: "elementChildrenValidity",
|
|
25
|
-
presets: ["logical", "logicalStrict"]
|
|
26
|
-
},
|
|
27
|
-
messages: { voidElementWithChildren: {
|
|
28
|
-
primary: "The `<{{ element }}>` element is a void element and cannot have children.",
|
|
29
|
-
secondary: [
|
|
30
|
-
"Void elements are self-closing and cannot contain any content or children.",
|
|
31
|
-
"Remove the children or use a different element type.",
|
|
32
|
-
"This violates HTML specification and may cause rendering issues."
|
|
33
|
-
],
|
|
34
|
-
suggestions: ["Remove the children from the element", "Use a self-closing syntax: `<{{ element }} />`"]
|
|
35
|
-
} },
|
|
36
|
-
setup(context) {
|
|
37
|
-
return { visitors: { JsxElement(node, { sourceFile }) {
|
|
38
|
-
if (!node.children.length) return;
|
|
39
|
-
const openingElement = node.openingElement;
|
|
40
|
-
if (openingElement.tagName.kind !== SyntaxKind.Identifier) return;
|
|
41
|
-
const elementName = openingElement.tagName.text.toLowerCase();
|
|
42
|
-
if (!voidElements.has(elementName)) return;
|
|
43
|
-
context.report({
|
|
44
|
-
data: { element: elementName },
|
|
45
|
-
message: "voidElementWithChildren",
|
|
46
|
-
range: getTSNodeRange(openingElement.tagName, sourceFile)
|
|
47
|
-
});
|
|
48
|
-
} } };
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
//#endregion
|
|
52
|
-
export { elementChildrenValidity_default as default };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports heading elements without accessible content.";
|
|
3
|
-
readonly id: "headingContents";
|
|
4
|
-
readonly presets: readonly ["logical", "logicalStrict"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "emptyHeading", undefined>;
|
|
9
|
-
export default _default;
|
|
@@ -1,52 +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/headingContents.ts
|
|
5
|
-
const headingElements = /* @__PURE__ */ new Set([
|
|
6
|
-
"h1",
|
|
7
|
-
"h2",
|
|
8
|
-
"h3",
|
|
9
|
-
"h4",
|
|
10
|
-
"h5",
|
|
11
|
-
"h6"
|
|
12
|
-
]);
|
|
13
|
-
var headingContents_default = ruleCreator.createRule(typescriptLanguage, {
|
|
14
|
-
about: {
|
|
15
|
-
description: "Reports heading elements without accessible content.",
|
|
16
|
-
id: "headingContents",
|
|
17
|
-
presets: ["logical", "logicalStrict"]
|
|
18
|
-
},
|
|
19
|
-
messages: { emptyHeading: {
|
|
20
|
-
primary: "This heading element is missing accessible content.",
|
|
21
|
-
secondary: [
|
|
22
|
-
"Headings without content are not accessible to screen readers.",
|
|
23
|
-
"Ensure the heading has text content or uses aria-label/aria-labelledby.",
|
|
24
|
-
"This is required for WCAG 2.4.6 compliance."
|
|
25
|
-
],
|
|
26
|
-
suggestions: ["Add text content to the heading", "Use aria-label or aria-labelledby to provide accessible text"]
|
|
27
|
-
} },
|
|
28
|
-
setup(context) {
|
|
29
|
-
function checkHeading(node, { sourceFile }) {
|
|
30
|
-
const tagName = node.kind === SyntaxKind.JsxElement ? node.openingElement.tagName : node.tagName;
|
|
31
|
-
if (tagName.kind !== SyntaxKind.Identifier || !headingElements.has(tagName.text.toLowerCase())) return;
|
|
32
|
-
if ((node.kind === SyntaxKind.JsxElement ? node.openingElement.attributes : node.attributes).properties.some((property) => {
|
|
33
|
-
if (property.kind !== SyntaxKind.JsxAttribute || property.name.kind !== SyntaxKind.Identifier) return false;
|
|
34
|
-
return (property.name.text === "aria-label" || property.name.text === "aria-labelledby") && !!property.initializer;
|
|
35
|
-
})) return;
|
|
36
|
-
if (node.kind === SyntaxKind.JsxElement && node.children.some((child) => {
|
|
37
|
-
if (child.kind === SyntaxKind.JsxText) return !!child.text.trim().length;
|
|
38
|
-
return child.kind === SyntaxKind.JsxElement || child.kind === SyntaxKind.JsxSelfClosingElement || child.kind === SyntaxKind.JsxExpression;
|
|
39
|
-
})) return;
|
|
40
|
-
context.report({
|
|
41
|
-
message: "emptyHeading",
|
|
42
|
-
range: getTSNodeRange(tagName, sourceFile)
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
return { visitors: {
|
|
46
|
-
JsxElement: checkHeading,
|
|
47
|
-
JsxSelfClosingElement: checkHeading
|
|
48
|
-
} };
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
//#endregion
|
|
52
|
-
export { headingContents_default as default };
|
package/lib/rules/htmlLangs.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports <html> elements without a lang prop.";
|
|
3
|
-
readonly id: "htmlLangs";
|
|
4
|
-
readonly presets: readonly ["logical", "logicalStrict"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "missingLang", undefined>;
|
|
9
|
-
export default _default;
|
package/lib/rules/htmlLangs.js
DELETED
|
@@ -1,30 +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/htmlLangs.ts
|
|
5
|
-
var htmlLangs_default = ruleCreator.createRule(typescriptLanguage, {
|
|
6
|
-
about: {
|
|
7
|
-
description: "Reports <html> elements without a lang prop.",
|
|
8
|
-
id: "htmlLangs",
|
|
9
|
-
presets: ["logical", "logicalStrict"]
|
|
10
|
-
},
|
|
11
|
-
messages: { missingLang: {
|
|
12
|
-
primary: "This <html> element is missing a `lang` prop.",
|
|
13
|
-
secondary: [
|
|
14
|
-
"The lang attribute identifies the language of the document for screen readers and assistive technologies.",
|
|
15
|
-
"Without it, screen readers may default to the user's system language, causing confusion.",
|
|
16
|
-
"This is required for WCAG 3.1.1 compliance."
|
|
17
|
-
],
|
|
18
|
-
suggestions: ["Add a lang prop with a valid language code (e.g., lang=\"en\")", "Use lang=\"en-US\" for more specific localization"]
|
|
19
|
-
} },
|
|
20
|
-
setup(context) {
|
|
21
|
-
return { visitors: { JsxOpeningElement(node, { sourceFile }) {
|
|
22
|
-
if (node.tagName.kind === SyntaxKind.Identifier && node.tagName.text === "html" && !node.attributes.properties.some((property) => property.kind === SyntaxKind.JsxAttribute && property.name.kind === SyntaxKind.Identifier && property.name.text.toLowerCase() === "lang")) context.report({
|
|
23
|
-
message: "missingLang",
|
|
24
|
-
range: getTSNodeRange(node.tagName, sourceFile)
|
|
25
|
-
});
|
|
26
|
-
} } };
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
//#endregion
|
|
30
|
-
export { htmlLangs_default as default };
|