@hypersolid-dev/eslint-config 4.0.0 → 6.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.
- package/CHANGELOG.md +39 -0
- package/README.md +54 -7
- package/dist/base.js +65 -7
- package/dist/jest.d.ts +3 -0
- package/dist/jest.js +56 -0
- package/dist/plugins/hypersolid.d.ts +3 -0
- package/dist/plugins/hypersolid.js +38 -0
- package/dist/plugins/props-destructuring.js +14 -13
- package/dist/plugins/react-native-a11y/index.js +4 -2
- package/dist/plugins/react-native-a11y/rules/accessibility-actions-need-handler.js +2 -2
- package/dist/plugins/react-native-a11y/rules/accessibility-hint.js +6 -4
- package/dist/plugins/react-native-a11y/rules/accessibility-label.js +10 -6
- package/dist/plugins/react-native-a11y/rules/accessibility-role.js +6 -4
- package/dist/plugins/react-native-a11y/rules/accessibility-value-needs-range.js +25 -27
- package/dist/plugins/react-native-a11y/rules/adjustable-needs-value.js +16 -18
- package/dist/plugins/react-native-a11y/rules/gate-announce-for-accessibility.js +14 -10
- package/dist/plugins/react-native-a11y/rules/input-purpose.js +10 -6
- package/dist/plugins/react-native-a11y/rules/no-aria-legacy-mix.js +20 -22
- package/dist/plugins/react-native-a11y/rules/no-deprecated-accessibility-focus.js +12 -14
- package/dist/plugins/react-native-a11y/rules/no-dropped-aria-on-text.js +2 -2
- package/dist/plugins/react-native-a11y/rules/no-font-scaling-disabled.js +24 -26
- package/dist/plugins/react-native-a11y/rules/no-ineffective-accessibility-props.js +34 -36
- package/dist/plugins/react-native-a11y/rules/no-invalid-accessibility-state.js +31 -33
- package/dist/plugins/react-native-a11y/rules/no-nested-focus-stops.js +2 -2
- package/dist/plugins/react-native-a11y/rules/no-press-in-activation.js +2 -2
- package/dist/plugins/react-native-a11y/rules/no-unnamed-interactive.js +6 -4
- package/dist/plugins/react-native-a11y/rules/require-invert-colors-opt-out.js +8 -6
- package/dist/plugins/react-native-a11y/rules/require-modal-a11y-on-sheets.js +7 -5
- package/dist/plugins/react-native-a11y/utils/get-amount-of-text-children.js +19 -13
- package/dist/plugins/react-native-a11y/utils/get-element-name.js +1 -1
- package/dist/plugins/rules/boolean-guard.d.ts +30 -0
- package/dist/plugins/rules/boolean-guard.js +143 -0
- package/dist/plugins/rules/comment-style.d.ts +26 -0
- package/dist/plugins/rules/comment-style.js +167 -0
- package/dist/plugins/rules/effect-takes-callback.d.ts +20 -0
- package/dist/plugins/rules/effect-takes-callback.js +106 -0
- package/dist/plugins/rules/jsdoc-for-props.d.ts +18 -0
- package/dist/plugins/rules/jsdoc-for-props.js +57 -0
- package/dist/plugins/rules/no-callback-payload.d.ts +21 -0
- package/dist/plugins/rules/no-callback-payload.js +217 -0
- package/dist/plugins/rules/no-markup-variable.d.ts +22 -0
- package/dist/plugins/rules/no-markup-variable.js +123 -0
- package/dist/plugins/rules/no-unnecessary-use-callback.d.ts +24 -0
- package/dist/plugins/rules/no-unnecessary-use-callback.js +117 -0
- package/dist/plugins/rules/regex-pattern-const.d.ts +19 -0
- package/dist/plugins/rules/regex-pattern-const.js +82 -0
- package/dist/plugins/rules/test-id-default.d.ts +4 -0
- package/dist/plugins/rules/test-id-default.js +182 -0
- package/dist/plugins/rules/test-id-format.d.ts +4 -0
- package/dist/plugins/rules/test-id-format.js +118 -0
- package/dist/plugins/rules/test-id-from-props.d.ts +4 -0
- package/dist/plugins/rules/test-id-from-props.js +90 -0
- package/dist/plugins/rules/undefined-check.d.ts +5 -0
- package/dist/plugins/rules/undefined-check.js +64 -0
- package/dist/plugins/rules/webview-allow-list.d.ts +5 -0
- package/dist/plugins/rules/webview-allow-list.js +105 -0
- package/dist/plugins/utils/comments.d.ts +15 -0
- package/dist/plugins/utils/comments.js +15 -0
- package/dist/plugins/utils/component-name.d.ts +11 -0
- package/dist/plugins/utils/component-name.js +32 -0
- package/dist/plugins/utils/create-rule.d.ts +5 -0
- package/dist/plugins/utils/create-rule.js +4 -0
- package/dist/plugins/utils/expressions.d.ts +52 -0
- package/dist/plugins/utils/expressions.js +290 -0
- package/dist/plugins/utils/types.d.ts +20 -0
- package/dist/plugins/utils/types.js +91 -0
- package/dist/react-native.js +93 -12
- package/dist/shared.d.ts +22 -3
- package/dist/shared.js +202 -75
- package/package.json +13 -3
|
@@ -18,32 +18,30 @@ const accessibilityValueNeedsRange = createRule({
|
|
|
18
18
|
},
|
|
19
19
|
schema: [],
|
|
20
20
|
},
|
|
21
|
-
create(context) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
};
|
|
47
|
-
},
|
|
21
|
+
create: (context) => ({
|
|
22
|
+
JSXOpeningElement: (node) => {
|
|
23
|
+
const attribute = getProperty(node, "accessibilityValue");
|
|
24
|
+
if (attribute?.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const { expression } = attribute.value;
|
|
28
|
+
// A value built elsewhere cannot be read here
|
|
29
|
+
if (expression.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// A spread may carry the range, and cannot be read from here
|
|
33
|
+
if (expression.properties.some((property) => property.type === AST_NODE_TYPES.SpreadElement)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const keys = keysOf(expression);
|
|
37
|
+
if (!keys.includes("now") || keys.includes("text")) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (keys.includes("min") && keys.includes("max")) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
context.report({ node: attribute, messageId: "valueWithoutRange" });
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
48
46
|
});
|
|
49
47
|
export default accessibilityValueNeedsRange;
|
|
@@ -18,23 +18,21 @@ const adjustableNeedsValue = createRule({
|
|
|
18
18
|
},
|
|
19
19
|
schema: [],
|
|
20
20
|
},
|
|
21
|
-
create(context) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
};
|
|
38
|
-
},
|
|
21
|
+
create: (context) => ({
|
|
22
|
+
JSXOpeningElement: (node) => {
|
|
23
|
+
const role = getLiteralPropertyValue(node, ["accessibilityRole", "role"]);
|
|
24
|
+
if (typeof role !== "string" || !ROLES_NEEDING_VALUE.has(role)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (hasSpreadProps(node) || hasProperty(node, VALUE_PROPS)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
context.report({
|
|
31
|
+
node,
|
|
32
|
+
messageId: "missingAccessibilityValue",
|
|
33
|
+
data: { role },
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
39
37
|
});
|
|
40
38
|
export default adjustableNeedsValue;
|
|
@@ -28,20 +28,22 @@ const gateAnnounceForAccessibility = createRule({
|
|
|
28
28
|
},
|
|
29
29
|
schema: [],
|
|
30
30
|
},
|
|
31
|
-
create(context) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
create: (context) => {
|
|
32
|
+
/**
|
|
33
|
+
* A bare announce is the legitimate cross-platform pattern. The double
|
|
34
|
+
* announcement only happens where a live region covers Android already, so
|
|
35
|
+
* that pairing is the trigger rather than the call on its own.
|
|
36
|
+
*/
|
|
35
37
|
let hasLiveRegion = false;
|
|
36
38
|
const announces = [];
|
|
37
39
|
const platformChecks = [];
|
|
38
40
|
return {
|
|
39
|
-
JSXOpeningElement(node) {
|
|
41
|
+
JSXOpeningElement: (node) => {
|
|
40
42
|
if (getProperty(node, LIVE_REGION_PROPS)) {
|
|
41
43
|
hasLiveRegion = true;
|
|
42
44
|
}
|
|
43
45
|
},
|
|
44
|
-
MemberExpression(node) {
|
|
46
|
+
MemberExpression: (node) => {
|
|
45
47
|
if (node.property.type !== AST_NODE_TYPES.Identifier) {
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
@@ -56,13 +58,15 @@ const gateAnnounceForAccessibility = createRule({
|
|
|
56
58
|
platformChecks.push(node);
|
|
57
59
|
}
|
|
58
60
|
},
|
|
59
|
-
"Program:exit"() {
|
|
61
|
+
"Program:exit": () => {
|
|
60
62
|
if (!hasLiveRegion) {
|
|
61
63
|
return;
|
|
62
64
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* ponytail: any Platform.OS in the same function counts as the gate.
|
|
67
|
+
* Lenient on purpose — a guard clause, a switch and a named boolean are
|
|
68
|
+
* all real gates, and none of them is an ancestor of the call.
|
|
69
|
+
*/
|
|
66
70
|
const gatedScopes = new Set(platformChecks.map((check) => enclosingScope(check)));
|
|
67
71
|
for (const announce of announces) {
|
|
68
72
|
if (gatedScopes.has(enclosingScope(announce))) {
|
|
@@ -42,13 +42,13 @@ const inputPurpose = createRule({
|
|
|
42
42
|
},
|
|
43
43
|
],
|
|
44
44
|
},
|
|
45
|
-
create(context) {
|
|
45
|
+
create: (context) => {
|
|
46
46
|
const components = getComponents({
|
|
47
47
|
context,
|
|
48
48
|
defaultComponents: TEXT_INPUTS,
|
|
49
49
|
});
|
|
50
50
|
return {
|
|
51
|
-
JSXOpeningElement(node) {
|
|
51
|
+
JSXOpeningElement: (node) => {
|
|
52
52
|
const componentName = getElementName(node);
|
|
53
53
|
if (componentName === undefined) {
|
|
54
54
|
return;
|
|
@@ -56,12 +56,16 @@ const inputPurpose = createRule({
|
|
|
56
56
|
if (!components.includes(componentName)) {
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The missing token may arrive through the spread; an explicit one is
|
|
61
|
+
* already wrong whatever the spread carries
|
|
62
|
+
*/
|
|
61
63
|
const autoComplete = getProperty(node, "autoComplete");
|
|
62
64
|
const textContentType = getProperty(node, "textContentType");
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
/**
|
|
66
|
+
* One fix either way, so one finding: the sharper message where only
|
|
67
|
+
* the iOS half is already set
|
|
68
|
+
*/
|
|
65
69
|
if (!autoComplete && !hasSpreadProps(node)) {
|
|
66
70
|
context.report(textContentType
|
|
67
71
|
? { node: textContentType, messageId: "missingAutoComplete" }
|
|
@@ -30,28 +30,26 @@ const noAriaLegacyMix = createRule({
|
|
|
30
30
|
},
|
|
31
31
|
schema: [],
|
|
32
32
|
},
|
|
33
|
-
create(context) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
const stateKey = STATE_ALIAS_KEYS[alias] ?? VALUE_ALIAS_KEYS[alias];
|
|
41
|
-
// A grouped prop only clashes on the key the alias maps onto
|
|
42
|
-
const isClashes = stateKey
|
|
43
|
-
? hasStateKey(node, legacy, stateKey)
|
|
44
|
-
: getProperty(node, legacy) !== undefined;
|
|
45
|
-
if (isClashes) {
|
|
46
|
-
context.report({
|
|
47
|
-
node,
|
|
48
|
-
messageId: "ariaLegacyMix",
|
|
49
|
-
data: { alias, legacy },
|
|
50
|
-
});
|
|
51
|
-
}
|
|
33
|
+
create: (context) => ({
|
|
34
|
+
JSXOpeningElement: (node) => {
|
|
35
|
+
for (const [alias, legacy] of Object.entries(ALIAS_TO_LEGACY)) {
|
|
36
|
+
if (!getProperty(node, alias)) {
|
|
37
|
+
continue;
|
|
52
38
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
const stateKey = STATE_ALIAS_KEYS[alias] ?? VALUE_ALIAS_KEYS[alias];
|
|
40
|
+
// A grouped prop only clashes on the key the alias maps onto
|
|
41
|
+
const isClashes = stateKey
|
|
42
|
+
? hasStateKey(node, legacy, stateKey)
|
|
43
|
+
: getProperty(node, legacy) !== undefined;
|
|
44
|
+
if (isClashes) {
|
|
45
|
+
context.report({
|
|
46
|
+
node,
|
|
47
|
+
messageId: "ariaLegacyMix",
|
|
48
|
+
data: { alias, legacy },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
56
54
|
});
|
|
57
55
|
export default noAriaLegacyMix;
|
|
@@ -13,19 +13,17 @@ const noDeprecatedAccessibilityFocus = createRule({
|
|
|
13
13
|
},
|
|
14
14
|
schema: [],
|
|
15
15
|
},
|
|
16
|
-
create(context) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
},
|
|
16
|
+
create: (context) => ({
|
|
17
|
+
// Matched on the property so an aliased import is caught too
|
|
18
|
+
MemberExpression: (node) => {
|
|
19
|
+
if (node.property.type === AST_NODE_TYPES.Identifier &&
|
|
20
|
+
node.property.name === "setAccessibilityFocus") {
|
|
21
|
+
context.report({
|
|
22
|
+
node,
|
|
23
|
+
messageId: "deprecatedSetAccessibilityFocus",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
30
28
|
});
|
|
31
29
|
export default noDeprecatedAccessibilityFocus;
|
|
@@ -36,13 +36,13 @@ const noDroppedAriaOnText = createRule({
|
|
|
36
36
|
},
|
|
37
37
|
],
|
|
38
38
|
},
|
|
39
|
-
create(context) {
|
|
39
|
+
create: (context) => {
|
|
40
40
|
const components = getComponents({
|
|
41
41
|
context,
|
|
42
42
|
defaultComponents: TEXTS,
|
|
43
43
|
});
|
|
44
44
|
return {
|
|
45
|
-
JSXOpeningElement(node) {
|
|
45
|
+
JSXOpeningElement: (node) => {
|
|
46
46
|
const componentName = getElementName(node);
|
|
47
47
|
if (componentName === undefined) {
|
|
48
48
|
return;
|
|
@@ -19,31 +19,29 @@ const noFontScalingDisabled = createRule({
|
|
|
19
19
|
},
|
|
20
20
|
schema: [],
|
|
21
21
|
},
|
|
22
|
-
create(context) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
};
|
|
47
|
-
},
|
|
22
|
+
create: (context) => ({
|
|
23
|
+
JSXOpeningElement: (node) => {
|
|
24
|
+
const allowFontScaling = getProperty(node, "allowFontScaling");
|
|
25
|
+
if (allowFontScaling &&
|
|
26
|
+
getLiteralPropertyValue(node, "allowFontScaling") === false) {
|
|
27
|
+
context.report({
|
|
28
|
+
node: allowFontScaling,
|
|
29
|
+
messageId: "fontScalingDisabled",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const cap = getProperty(node, "maxFontSizeMultiplier");
|
|
33
|
+
const multiplier = getLiteralPropertyValue(node, "maxFontSizeMultiplier");
|
|
34
|
+
if (cap &&
|
|
35
|
+
typeof multiplier === "number" &&
|
|
36
|
+
multiplier !== NO_MAXIMUM &&
|
|
37
|
+
multiplier < MINIMUM_MULTIPLIER) {
|
|
38
|
+
context.report({
|
|
39
|
+
node: cap,
|
|
40
|
+
messageId: "fontScalingCapped",
|
|
41
|
+
data: { multiplier, minimum: MINIMUM_MULTIPLIER },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
48
46
|
});
|
|
49
47
|
export default noFontScalingDisabled;
|
|
@@ -31,46 +31,44 @@ const noIneffectiveAccessibilityProps = createRule({
|
|
|
31
31
|
},
|
|
32
32
|
schema: [],
|
|
33
33
|
},
|
|
34
|
-
create(context) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
create: (context) => ({
|
|
35
|
+
JSXOpeningElement: (node) => {
|
|
36
|
+
const role = getLiteralPropertyValue(node, ["accessibilityRole", "role"]);
|
|
37
|
+
if (role === "listitem") {
|
|
38
|
+
context.report({ node, messageId: "listItemRole" });
|
|
39
|
+
}
|
|
40
|
+
if (role === "alert") {
|
|
41
|
+
context.report({ node, messageId: "alertRole" });
|
|
42
|
+
}
|
|
43
|
+
const isHidden = isDefinitelyHidden(node);
|
|
44
|
+
if (isHidden && hasLabelProperty(node)) {
|
|
45
|
+
context.report({ node, messageId: "hiddenWithLabel" });
|
|
46
|
+
}
|
|
47
|
+
if (getLiteralPropertyValue(node, "accessible") === false) {
|
|
48
|
+
const name = getElementName(node);
|
|
49
|
+
if (isHidden) {
|
|
50
|
+
context.report({ node, messageId: "hiddenWithAccessibleFalse" });
|
|
40
51
|
}
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (isHidden && hasLabelProperty(node)) {
|
|
46
|
-
context.report({ node, messageId: "hiddenWithLabel" });
|
|
47
|
-
}
|
|
48
|
-
if (getLiteralPropertyValue(node, "accessible") === false) {
|
|
49
|
-
const name = getElementName(node);
|
|
50
|
-
if (isHidden) {
|
|
51
|
-
context.report({ node, messageId: "hiddenWithAccessibleFalse" });
|
|
52
|
-
}
|
|
53
|
-
else if (name !== undefined &&
|
|
54
|
-
DEFAULTS_TO_INACCESSIBLE.has(name) &&
|
|
55
|
-
// A spread may carry the `accessible` this one is overriding.
|
|
56
|
-
!hasSpreadProps(node)) {
|
|
57
|
-
context.report({
|
|
58
|
-
node,
|
|
59
|
-
messageId: "accessibleFalseIsDefault",
|
|
60
|
-
data: { name },
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const labelledBy = getProperty(node, LABELLED_BY_PROPS);
|
|
65
|
-
if (labelledBy && !hasLabelProperty(node)) {
|
|
52
|
+
else if (name !== undefined &&
|
|
53
|
+
DEFAULTS_TO_INACCESSIBLE.has(name) &&
|
|
54
|
+
// A spread may carry the `accessible` this one is overriding.
|
|
55
|
+
!hasSpreadProps(node)) {
|
|
66
56
|
context.report({
|
|
67
57
|
node,
|
|
68
|
-
messageId: "
|
|
69
|
-
data: {
|
|
58
|
+
messageId: "accessibleFalseIsDefault",
|
|
59
|
+
data: { name },
|
|
70
60
|
});
|
|
71
61
|
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
62
|
+
}
|
|
63
|
+
const labelledBy = getProperty(node, LABELLED_BY_PROPS);
|
|
64
|
+
if (labelledBy && !hasLabelProperty(node)) {
|
|
65
|
+
context.report({
|
|
66
|
+
node,
|
|
67
|
+
messageId: "labelledByWithoutLabel",
|
|
68
|
+
data: { prop: labelledBy.name.name },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
75
73
|
});
|
|
76
74
|
export default noIneffectiveAccessibilityProps;
|
|
@@ -44,39 +44,37 @@ const noInvalidAccessibilityState = createRule({
|
|
|
44
44
|
},
|
|
45
45
|
schema: [],
|
|
46
46
|
},
|
|
47
|
-
create(context) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
create: (context) => ({
|
|
48
|
+
JSXOpeningElement: (node) => {
|
|
49
|
+
const attribute = getProperty(node, "accessibilityState");
|
|
50
|
+
if (attribute?.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const { expression } = attribute.value;
|
|
54
|
+
// An object literal here is already rejected by the React Native types
|
|
55
|
+
if (expression.type !== AST_NODE_TYPES.Identifier) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const resolved = resolveObjectBinding(context.sourceCode.getScope(expression), expression);
|
|
59
|
+
if (!resolved) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
for (const property of resolved.properties) {
|
|
63
|
+
if (property.type !== AST_NODE_TYPES.Property ||
|
|
64
|
+
property.key.type !== AST_NODE_TYPES.Identifier ||
|
|
65
|
+
ACCESSIBILITY_STATE_KEYS.includes(property.key.name)) {
|
|
66
|
+
continue;
|
|
53
67
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
property.key.type !== AST_NODE_TYPES.Identifier ||
|
|
66
|
-
ACCESSIBILITY_STATE_KEYS.includes(property.key.name)) {
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
context.report({
|
|
70
|
-
node: property,
|
|
71
|
-
messageId: "invalidStateKey",
|
|
72
|
-
data: {
|
|
73
|
-
key: property.key.name,
|
|
74
|
-
supported: ACCESSIBILITY_STATE_KEYS.join(", "),
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
},
|
|
68
|
+
context.report({
|
|
69
|
+
node: property,
|
|
70
|
+
messageId: "invalidStateKey",
|
|
71
|
+
data: {
|
|
72
|
+
key: property.key.name,
|
|
73
|
+
supported: ACCESSIBILITY_STATE_KEYS.join(", "),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
81
79
|
});
|
|
82
80
|
export default noInvalidAccessibilityState;
|
|
@@ -30,7 +30,7 @@ const noNestedFocusStops = createRule({
|
|
|
30
30
|
},
|
|
31
31
|
],
|
|
32
32
|
},
|
|
33
|
-
create(context) {
|
|
33
|
+
create: (context) => {
|
|
34
34
|
// The touchables plus whatever this project wraps them in
|
|
35
35
|
const components = getComponents({
|
|
36
36
|
context,
|
|
@@ -44,7 +44,7 @@ const noNestedFocusStops = createRule({
|
|
|
44
44
|
hasTouchHandler(node));
|
|
45
45
|
};
|
|
46
46
|
return {
|
|
47
|
-
JSXOpeningElement(node) {
|
|
47
|
+
JSXOpeningElement: (node) => {
|
|
48
48
|
const componentName = getElementName(node);
|
|
49
49
|
if (componentName === undefined ||
|
|
50
50
|
!isDefinitelyGrouped(node) ||
|
|
@@ -29,13 +29,13 @@ const noPressInActivation = createRule({
|
|
|
29
29
|
},
|
|
30
30
|
],
|
|
31
31
|
},
|
|
32
|
-
create(context) {
|
|
32
|
+
create: (context) => {
|
|
33
33
|
const components = getComponents({
|
|
34
34
|
context,
|
|
35
35
|
defaultComponents: TOUCHABLES,
|
|
36
36
|
});
|
|
37
37
|
return {
|
|
38
|
-
JSXOpeningElement(node) {
|
|
38
|
+
JSXOpeningElement: (node) => {
|
|
39
39
|
const componentName = getElementName(node);
|
|
40
40
|
if (componentName === undefined) {
|
|
41
41
|
return;
|
|
@@ -38,16 +38,18 @@ const noUnnamedInteractive = createRule({
|
|
|
38
38
|
},
|
|
39
39
|
],
|
|
40
40
|
},
|
|
41
|
-
create(context) {
|
|
41
|
+
create: (context) => {
|
|
42
42
|
const components = getComponents({ context });
|
|
43
43
|
return {
|
|
44
|
-
JSXOpeningElement(node) {
|
|
44
|
+
JSXOpeningElement: (node) => {
|
|
45
45
|
const componentName = getElementName(node);
|
|
46
46
|
if (componentName === undefined) {
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
/**
|
|
50
|
+
* accessibility-label owns the components it knows by name, so a
|
|
51
|
+
* control does not collect a finding from both rules
|
|
52
|
+
*/
|
|
51
53
|
if (ALREADY_COVERED.has(componentName) &&
|
|
52
54
|
!components.includes(componentName)) {
|
|
53
55
|
return;
|
|
@@ -29,17 +29,19 @@ const requireInvertColorsOptOut = createRule({
|
|
|
29
29
|
},
|
|
30
30
|
],
|
|
31
31
|
},
|
|
32
|
-
create(context) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
create: (context) => {
|
|
33
|
+
/**
|
|
34
|
+
* Matched on the element name, so an image from expo-image or a wrapper
|
|
35
|
+
* named through additionalComponents is checked the same way. Deliberately
|
|
36
|
+
* not gated on the import statement: that reads the source text and misses
|
|
37
|
+
* a codebase whose quote style differs.
|
|
38
|
+
*/
|
|
37
39
|
const components = getComponents({
|
|
38
40
|
context,
|
|
39
41
|
defaultComponents: IMAGES,
|
|
40
42
|
});
|
|
41
43
|
return {
|
|
42
|
-
JSXOpeningElement(node) {
|
|
44
|
+
JSXOpeningElement: (node) => {
|
|
43
45
|
const componentName = getElementName(node);
|
|
44
46
|
if (componentName === undefined) {
|
|
45
47
|
return;
|
|
@@ -27,13 +27,15 @@ const requireModalA11yOnSheets = createRule({
|
|
|
27
27
|
},
|
|
28
28
|
],
|
|
29
29
|
},
|
|
30
|
-
create(context) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
create: (context) => {
|
|
31
|
+
/**
|
|
32
|
+
* No default list: a sheet is whatever this project calls one, and
|
|
33
|
+
* hardcoding one library's component names would fire on a codebase that
|
|
34
|
+
* happens to reuse them. Configure through additionalComponents.
|
|
35
|
+
*/
|
|
34
36
|
const components = getComponents({ context });
|
|
35
37
|
return {
|
|
36
|
-
JSXOpeningElement(node) {
|
|
38
|
+
JSXOpeningElement: (node) => {
|
|
37
39
|
const componentName = getElementName(node);
|
|
38
40
|
if (componentName === undefined) {
|
|
39
41
|
return;
|