@flint.fyi/jsx 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{lib/plugin.d.ts → dist/index.d.mts} +45 -48
- package/dist/index.mjs +3235 -0
- package/package.json +10 -10
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/plugin.js +0 -106
- package/lib/rules/accessKeys.d.ts +0 -10
- package/lib/rules/accessKeys.js +0 -32
- package/lib/rules/altTexts.d.ts +0 -10
- package/lib/rules/altTexts.js +0 -78
- package/lib/rules/anchorAmbiguousText.d.ts +0 -10
- package/lib/rules/anchorAmbiguousText.js +0 -57
- package/lib/rules/anchorContent.d.ts +0 -10
- package/lib/rules/anchorContent.js +0 -57
- package/lib/rules/anchorValidity.d.ts +0 -10
- package/lib/rules/anchorValidity.js +0 -79
- package/lib/rules/ariaActiveDescendantTabIndex.d.ts +0 -10
- package/lib/rules/ariaActiveDescendantTabIndex.js +0 -53
- package/lib/rules/ariaHiddenFocusables.d.ts +0 -10
- package/lib/rules/ariaHiddenFocusables.js +0 -68
- package/lib/rules/ariaPropTypes.d.ts +0 -10
- package/lib/rules/ariaPropTypes.js +0 -224
- package/lib/rules/ariaProps.d.ts +0 -10
- package/lib/rules/ariaProps.js +0 -92
- package/lib/rules/ariaRoleValidity.d.ts +0 -10
- package/lib/rules/ariaRoleValidity.js +0 -113
- package/lib/rules/ariaUnsupportedElements.d.ts +0 -10
- package/lib/rules/ariaUnsupportedElements.js +0 -88
- package/lib/rules/autoFocusProps.d.ts +0 -10
- package/lib/rules/autoFocusProps.js +0 -44
- package/lib/rules/autocomplete.d.ts +0 -10
- package/lib/rules/autocomplete.js +0 -126
- package/lib/rules/booleanValues.d.ts +0 -10
- package/lib/rules/booleanValues.js +0 -33
- package/lib/rules/bracedStatements.d.ts +0 -10
- package/lib/rules/bracedStatements.js +0 -33
- package/lib/rules/buttonTypes.d.ts +0 -10
- package/lib/rules/buttonTypes.js +0 -76
- package/lib/rules/childrenProps.d.ts +0 -10
- package/lib/rules/childrenProps.js +0 -39
- package/lib/rules/clickEventKeyEvents.d.ts +0 -10
- package/lib/rules/clickEventKeyEvents.js +0 -56
- package/lib/rules/commentTextNodes.d.ts +0 -10
- package/lib/rules/commentTextNodes.js +0 -32
- package/lib/rules/distractingElements.d.ts +0 -10
- package/lib/rules/distractingElements.js +0 -40
- package/lib/rules/elementChildrenValidity.d.ts +0 -10
- package/lib/rules/elementChildrenValidity.js +0 -54
- package/lib/rules/headingContents.d.ts +0 -10
- package/lib/rules/headingContents.js +0 -54
- package/lib/rules/htmlLangs.d.ts +0 -10
- package/lib/rules/htmlLangs.js +0 -32
- package/lib/rules/iframeTitles.d.ts +0 -10
- package/lib/rules/iframeTitles.js +0 -63
- package/lib/rules/interactiveElementRoles.d.ts +0 -10
- package/lib/rules/interactiveElementRoles.js +0 -93
- package/lib/rules/interactiveElementsFocusable.d.ts +0 -10
- package/lib/rules/interactiveElementsFocusable.js +0 -172
- package/lib/rules/labelAssociatedControls.d.ts +0 -10
- package/lib/rules/labelAssociatedControls.js +0 -70
- package/lib/rules/langValidity.d.ts +0 -10
- package/lib/rules/langValidity.js +0 -43
- package/lib/rules/mediaCaptions.d.ts +0 -10
- package/lib/rules/mediaCaptions.js +0 -52
- package/lib/rules/mouseEventKeyEvents.d.ts +0 -10
- package/lib/rules/mouseEventKeyEvents.js +0 -49
- package/lib/rules/nonInteractiveElementInteractions.d.ts +0 -10
- package/lib/rules/nonInteractiveElementInteractions.js +0 -95
- package/lib/rules/nonInteractiveElementRoles.d.ts +0 -10
- package/lib/rules/nonInteractiveElementRoles.js +0 -118
- package/lib/rules/nonInteractiveElementTabIndexes.d.ts +0 -10
- package/lib/rules/nonInteractiveElementTabIndexes.js +0 -109
- package/lib/rules/propDuplicates.d.ts +0 -10
- package/lib/rules/propDuplicates.js +0 -39
- package/lib/rules/roleRedundancies.d.ts +0 -10
- package/lib/rules/roleRedundancies.js +0 -76
- package/lib/rules/roleRequiredAriaProps.d.ts +0 -10
- package/lib/rules/roleRequiredAriaProps.js +0 -78
- package/lib/rules/roleSupportedAriaProps.d.ts +0 -10
- package/lib/rules/roleSupportedAriaProps.js +0 -419
- package/lib/rules/roleTags.d.ts +0 -10
- package/lib/rules/roleTags.js +0 -72
- package/lib/rules/ruleCreator.d.ts +0 -3
- package/lib/rules/ruleCreator.js +0 -15
- package/lib/rules/scopeProps.d.ts +0 -10
- package/lib/rules/scopeProps.js +0 -41
- package/lib/rules/staticElementInteractions.d.ts +0 -10
- package/lib/rules/staticElementInteractions.js +0 -60
- package/lib/rules/svgTitles.d.ts +0 -10
- package/lib/rules/svgTitles.js +0 -59
- package/lib/rules/tabIndexPositiveValues.d.ts +0 -10
- package/lib/rules/tabIndexPositiveValues.js +0 -45
- package/lib/rules/unescapedEntities.d.ts +0 -10
- package/lib/rules/unescapedEntities.js +0 -70
- package/lib/rules/unnecessaryFragments.d.ts +0 -10
- package/lib/rules/unnecessaryFragments.js +0 -51
package/lib/rules/svgTitles.js
DELETED
|
@@ -1,59 +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/svgTitles.ts
|
|
5
|
-
var svgTitles_default = ruleCreator.createRule(typescriptLanguage, {
|
|
6
|
-
about: {
|
|
7
|
-
description: "Reports <svg> elements without a <title> child element.",
|
|
8
|
-
id: "svgTitles",
|
|
9
|
-
presets: ["logical", "logicalStrict"]
|
|
10
|
-
},
|
|
11
|
-
messages: { missingTitle: {
|
|
12
|
-
primary: "This <svg> element is missing a <title> child element.",
|
|
13
|
-
secondary: [
|
|
14
|
-
"SVG elements without a title are not accessible to screen readers.",
|
|
15
|
-
"The <title> element provides a text description of the SVG's content.",
|
|
16
|
-
"This is required for WCAG 1.1.1 compliance."
|
|
17
|
-
],
|
|
18
|
-
suggestions: ["Add a <title> child element with descriptive text", "Use aria-label or aria-labelledby as an alternative"]
|
|
19
|
-
} },
|
|
20
|
-
setup(context) {
|
|
21
|
-
function hasValidAriaLabel(attributes) {
|
|
22
|
-
return attributes.properties.some((property) => {
|
|
23
|
-
if (property.kind !== SyntaxKind.JsxAttribute || property.name.kind !== SyntaxKind.Identifier || property.name.text !== "aria-label" && property.name.text !== "aria-labelledby") return false;
|
|
24
|
-
if (!property.initializer) return false;
|
|
25
|
-
if (property.initializer.kind === SyntaxKind.JsxExpression) {
|
|
26
|
-
const { expression } = property.initializer;
|
|
27
|
-
if (!expression) return false;
|
|
28
|
-
if (expression.kind === SyntaxKind.StringLiteral || expression.kind === SyntaxKind.NoSubstitutionTemplateLiteral) return expression.text !== "";
|
|
29
|
-
if (expression.kind === SyntaxKind.Identifier) return expression.text !== "undefined";
|
|
30
|
-
}
|
|
31
|
-
if (property.initializer.kind === SyntaxKind.StringLiteral) return property.initializer.text !== "";
|
|
32
|
-
return false;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function checkElement(node, { sourceFile }) {
|
|
36
|
-
const tagName = node.kind === SyntaxKind.JsxElement ? node.openingElement.tagName : node.tagName;
|
|
37
|
-
if (tagName.kind !== SyntaxKind.Identifier || tagName.text.toLowerCase() !== "svg") return;
|
|
38
|
-
if (hasValidAriaLabel(node.kind === SyntaxKind.JsxElement ? node.openingElement.attributes : node.attributes)) return;
|
|
39
|
-
if (node.kind === SyntaxKind.JsxElement && node.children.some(isTitleElement)) return;
|
|
40
|
-
context.report({
|
|
41
|
-
message: "missingTitle",
|
|
42
|
-
range: getTSNodeRange(tagName, sourceFile)
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
return { visitors: {
|
|
46
|
-
JsxElement: checkElement,
|
|
47
|
-
JsxSelfClosingElement: checkElement
|
|
48
|
-
} };
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
function isTitleElement(node) {
|
|
52
|
-
if (node.kind !== SyntaxKind.JsxElement && node.kind !== SyntaxKind.JsxSelfClosingElement) return false;
|
|
53
|
-
const childTagName = node.kind === SyntaxKind.JsxElement ? node.openingElement.tagName : node.tagName;
|
|
54
|
-
return childTagName.kind === SyntaxKind.Identifier && childTagName.text === "title";
|
|
55
|
-
}
|
|
56
|
-
//#endregion
|
|
57
|
-
export { svgTitles_default as default };
|
|
58
|
-
|
|
59
|
-
//# sourceMappingURL=svgTitles.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Reports positive tabIndex values.";
|
|
3
|
-
readonly id: "tabIndexPositiveValues";
|
|
4
|
-
readonly presets: readonly ["logical", "logicalStrict"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "noPositiveTabIndex", undefined>;
|
|
9
|
-
export default _default;
|
|
10
|
-
//# sourceMappingURL=tabIndexPositiveValues.d.ts.map
|
|
@@ -1,45 +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/tabIndexPositiveValues.ts
|
|
5
|
-
var tabIndexPositiveValues_default = ruleCreator.createRule(typescriptLanguage, {
|
|
6
|
-
about: {
|
|
7
|
-
description: "Reports positive tabIndex values.",
|
|
8
|
-
id: "tabIndexPositiveValues",
|
|
9
|
-
presets: ["logical", "logicalStrict"]
|
|
10
|
-
},
|
|
11
|
-
messages: { noPositiveTabIndex: {
|
|
12
|
-
primary: "Positive `tabIndex` values disrupt tab order and make keyboard navigation unpredictable.",
|
|
13
|
-
secondary: [
|
|
14
|
-
"Injecting elements on top of the default tab order with positive `tabIndex` values changes from the intuitive order for users.",
|
|
15
|
-
"Use tabIndex=\"0\" to include elements in the natural tab order, or tabIndex=\"-1\" to make them programmatically focusable.",
|
|
16
|
-
"This is required for WCAG 2.4.3 compliance."
|
|
17
|
-
],
|
|
18
|
-
suggestions: [
|
|
19
|
-
"Use tabIndex=\"0\" for elements that should be in the tab order",
|
|
20
|
-
"Use tabIndex=\"-1\" for elements that should be programmatically focusable",
|
|
21
|
-
"Remove tabIndex to use the default tab order"
|
|
22
|
-
]
|
|
23
|
-
} },
|
|
24
|
-
setup(context) {
|
|
25
|
-
return { visitors: { JsxAttribute(node, { sourceFile }) {
|
|
26
|
-
if (node.name.kind !== SyntaxKind.Identifier || node.name.text.toLowerCase() !== "tabindex" || !node.initializer) return;
|
|
27
|
-
const value = getInitializerValue(node.initializer);
|
|
28
|
-
if (value !== void 0 && value > 0) context.report({
|
|
29
|
-
message: "noPositiveTabIndex",
|
|
30
|
-
range: getTSNodeRange(node, sourceFile)
|
|
31
|
-
});
|
|
32
|
-
} } };
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
function getInitializerValue(initializer) {
|
|
36
|
-
if (initializer.kind === SyntaxKind.StringLiteral) {
|
|
37
|
-
const parsed = Number(initializer.text);
|
|
38
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
39
|
-
}
|
|
40
|
-
if (initializer.kind === SyntaxKind.JsxExpression) return initializer.expression?.kind === SyntaxKind.NumericLiteral ? Number(initializer.expression.text) : void 0;
|
|
41
|
-
}
|
|
42
|
-
//#endregion
|
|
43
|
-
export { tabIndexPositiveValues_default as default };
|
|
44
|
-
|
|
45
|
-
//# sourceMappingURL=tabIndexPositiveValues.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Disallow unescaped HTML entities in JSX text that may cause rendering issues.";
|
|
3
|
-
readonly id: "unescapedEntities";
|
|
4
|
-
readonly presets: readonly ["stylistic"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "unescapedEntity", undefined>;
|
|
9
|
-
export default _default;
|
|
10
|
-
//# sourceMappingURL=unescapedEntities.d.ts.map
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
3
|
-
//#region src/rules/unescapedEntities.ts
|
|
4
|
-
const problematicEntities = [
|
|
5
|
-
{
|
|
6
|
-
entity: "\"",
|
|
7
|
-
toBrace: "{\"\\\"\"}",
|
|
8
|
-
toHTML: """
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
entity: "'",
|
|
12
|
-
toBrace: "{\"'\"}",
|
|
13
|
-
toHTML: "'"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
entity: ">",
|
|
17
|
-
toBrace: "{'>'}",
|
|
18
|
-
toHTML: ">"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
entity: "}",
|
|
22
|
-
toBrace: "{'}'}",
|
|
23
|
-
toHTML: "}"
|
|
24
|
-
}
|
|
25
|
-
];
|
|
26
|
-
var unescapedEntities_default = ruleCreator.createRule(typescriptLanguage, {
|
|
27
|
-
about: {
|
|
28
|
-
description: "Disallow unescaped HTML entities in JSX text that may cause rendering issues.",
|
|
29
|
-
id: "unescapedEntities",
|
|
30
|
-
presets: ["stylistic"]
|
|
31
|
-
},
|
|
32
|
-
messages: { unescapedEntity: {
|
|
33
|
-
primary: "This unescaped entity `{{ entity }}` may not render properly.",
|
|
34
|
-
secondary: ["Certain characters like >, <, \", ', {, and } can cause issues in JSX when not properly escaped.", "React may not render these characters correctly or they might break JSX syntax."],
|
|
35
|
-
suggestions: ["Use HTML entities: {{ html }}", "Wrap in JSX expression: {{ brace }}"]
|
|
36
|
-
} },
|
|
37
|
-
setup(context) {
|
|
38
|
-
return { visitors: { JsxText(node, { sourceFile }) {
|
|
39
|
-
const nodeStart = node.getStart(sourceFile);
|
|
40
|
-
const reports = [];
|
|
41
|
-
for (const { entity, toBrace, toHTML } of problematicEntities) {
|
|
42
|
-
let index = 0;
|
|
43
|
-
while ((index = node.text.indexOf(entity, index)) !== -1) {
|
|
44
|
-
reports.push({
|
|
45
|
-
begin: nodeStart + index,
|
|
46
|
-
data: {
|
|
47
|
-
brace: toBrace,
|
|
48
|
-
entity,
|
|
49
|
-
html: toHTML
|
|
50
|
-
},
|
|
51
|
-
end: nodeStart + index + entity.length
|
|
52
|
-
});
|
|
53
|
-
index += entity.length;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
for (const report of reports.toSorted((a, b) => a.begin - b.begin)) context.report({
|
|
57
|
-
data: report.data,
|
|
58
|
-
message: "unescapedEntity",
|
|
59
|
-
range: {
|
|
60
|
-
begin: report.begin,
|
|
61
|
-
end: report.end
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
} } };
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
//#endregion
|
|
68
|
-
export { unescapedEntities_default as default };
|
|
69
|
-
|
|
70
|
-
//# sourceMappingURL=unescapedEntities.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
-
readonly description: "Disallow unnecessary JSX fragments that wrap a single child or have no children.";
|
|
3
|
-
readonly id: "unnecessaryFragments";
|
|
4
|
-
readonly presets: readonly ["stylistic"];
|
|
5
|
-
} & {
|
|
6
|
-
readonly pluginId: string;
|
|
7
|
-
readonly url: string;
|
|
8
|
-
}, "unnecessaryFragment", undefined>;
|
|
9
|
-
export default _default;
|
|
10
|
-
//# sourceMappingURL=unnecessaryFragments.d.ts.map
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { SyntaxKind } from "typescript";
|
|
3
|
-
import { typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
4
|
-
//#region src/rules/unnecessaryFragments.ts
|
|
5
|
-
var unnecessaryFragments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
6
|
-
about: {
|
|
7
|
-
description: "Disallow unnecessary JSX fragments that wrap a single child or have no children.",
|
|
8
|
-
id: "unnecessaryFragments",
|
|
9
|
-
presets: ["stylistic"]
|
|
10
|
-
},
|
|
11
|
-
messages: { unnecessaryFragment: {
|
|
12
|
-
primary: "Unnecessary fragment wrapping {{ childType }}.",
|
|
13
|
-
secondary: [
|
|
14
|
-
"Fragments are only needed when rendering multiple adjacent elements.",
|
|
15
|
-
"A fragment wrapping a single child or no children adds unnecessary complexity.",
|
|
16
|
-
"Remove the fragment and use the child directly, or remove empty fragments entirely."
|
|
17
|
-
],
|
|
18
|
-
suggestions: ["Remove the fragment wrapper and use the child element directly."]
|
|
19
|
-
} },
|
|
20
|
-
setup(context) {
|
|
21
|
-
function checkNodeChildren(node, range) {
|
|
22
|
-
const children = node.children.filter((child) => child.kind !== SyntaxKind.JsxText || !!child.text.trim().length);
|
|
23
|
-
let childType;
|
|
24
|
-
if (!children.length) childType = "no children";
|
|
25
|
-
else if (children.length === 1) childType = "a single child";
|
|
26
|
-
if (childType) context.report({
|
|
27
|
-
data: { childType },
|
|
28
|
-
message: "unnecessaryFragment",
|
|
29
|
-
range
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
return { visitors: {
|
|
33
|
-
JsxElement(node, { sourceFile }) {
|
|
34
|
-
if (node.openingElement.tagName.kind === SyntaxKind.Identifier && !node.openingElement.attributes.properties.length && node.openingElement.tagName.text === "Fragment") checkNodeChildren(node, {
|
|
35
|
-
begin: node.openingElement.getStart(sourceFile),
|
|
36
|
-
end: node.closingElement.getEnd()
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
JsxFragment(node, { sourceFile }) {
|
|
40
|
-
checkNodeChildren(node, {
|
|
41
|
-
begin: node.openingFragment.getStart(sourceFile),
|
|
42
|
-
end: node.closingFragment.getEnd()
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
} };
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
//#endregion
|
|
49
|
-
export { unnecessaryFragments_default as default };
|
|
50
|
-
|
|
51
|
-
//# sourceMappingURL=unnecessaryFragments.js.map
|