@hypersolid-dev/eslint-config 5.0.0 → 7.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 +37 -21
- package/dist/base.js +19 -6
- package/dist/jest.d.ts +3 -0
- package/dist/jest.js +56 -0
- package/dist/plugins/{hypersolid.d.ts → base/index.d.ts} +1 -0
- package/dist/plugins/base/index.js +15 -0
- package/dist/plugins/{rules → base/rules}/comment-style.js +45 -36
- package/dist/plugins/{rules → base/rules}/regex-pattern-const.js +2 -2
- package/dist/plugins/{rules → base/rules}/undefined-check.js +4 -4
- package/dist/plugins/react/index.d.ts +4 -0
- package/dist/plugins/react/index.js +23 -0
- package/dist/plugins/{rules → react/rules}/boolean-guard.js +5 -5
- package/dist/plugins/{rules → react/rules}/effect-takes-callback.js +2 -2
- package/dist/plugins/{rules → react/rules}/jsdoc-for-props.js +3 -3
- package/dist/plugins/{rules → react/rules}/no-callback-payload.js +20 -17
- package/dist/plugins/{rules → react/rules}/no-markup-variable.js +43 -37
- package/dist/plugins/{rules → react/rules}/no-unnecessary-use-callback.js +3 -3
- package/dist/plugins/react/rules/props-destructuring.d.ts +3 -0
- package/dist/plugins/{props-destructuring.js → react/rules/props-destructuring.js} +13 -17
- package/dist/plugins/{react-native-a11y → react-native/a11y}/index.js +1 -1
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-actions-need-handler.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-hint.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-label.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-role.js +3 -3
- package/dist/plugins/react-native/a11y/rules/accessibility-value-needs-range.js +47 -0
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/adjustable-needs-value.js +17 -19
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/gate-announce-for-accessibility.js +5 -5
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/input-purpose.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-aria-legacy-mix.js +21 -23
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-deprecated-accessibility-focus.js +13 -15
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-dropped-aria-on-text.js +3 -3
- package/dist/plugins/react-native/a11y/rules/no-font-scaling-disabled.js +47 -0
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-ineffective-accessibility-props.js +35 -37
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-invalid-accessibility-state.js +32 -34
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-nested-focus-stops.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-press-in-activation.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-unnamed-interactive.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/require-invert-colors-opt-out.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/require-modal-a11y-on-sheets.js +3 -3
- package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-amount-of-text-children.js +15 -13
- package/dist/plugins/{props-destructuring.d.ts → react-native/index.d.ts} +1 -0
- package/dist/plugins/react-native/index.js +17 -0
- package/dist/plugins/{rules → react-native/rules}/test-id-default.js +24 -28
- package/dist/plugins/{rules → react-native/rules}/test-id-format.js +16 -20
- package/dist/plugins/{rules → react-native/rules}/test-id-from-props.js +25 -29
- package/dist/plugins/{rules → react-native/rules}/webview-allow-list.js +24 -28
- package/dist/plugins/{utils → shared}/expressions.d.ts +3 -3
- package/dist/plugins/{utils → shared}/expressions.js +94 -78
- package/dist/plugins/{utils → shared}/types.js +14 -12
- package/dist/react-native.js +37 -8
- package/dist/shared.d.ts +3 -3
- package/dist/shared.js +169 -113
- package/package.json +12 -2
- package/dist/plugins/hypersolid.js +0 -38
- package/dist/plugins/react-native-a11y/rules/accessibility-value-needs-range.js +0 -49
- package/dist/plugins/react-native-a11y/rules/no-font-scaling-disabled.js +0 -49
- package/dist/plugins/react-native-a11y/utils/create-rule.js +0 -3
- package/dist/plugins/utils/create-rule.d.ts +0 -5
- /package/dist/plugins/{rules → base/rules}/comment-style.d.ts +0 -0
- /package/dist/plugins/{rules → base/rules}/regex-pattern-const.d.ts +0 -0
- /package/dist/plugins/{rules → base/rules}/undefined-check.d.ts +0 -0
- /package/dist/plugins/{rules → react/rules}/boolean-guard.d.ts +0 -0
- /package/dist/plugins/{rules → react/rules}/effect-takes-callback.d.ts +0 -0
- /package/dist/plugins/{rules → react/rules}/jsdoc-for-props.d.ts +0 -0
- /package/dist/plugins/{rules → react/rules}/no-callback-payload.d.ts +0 -0
- /package/dist/plugins/{rules → react/rules}/no-markup-variable.d.ts +0 -0
- /package/dist/plugins/{rules → react/rules}/no-unnecessary-use-callback.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/accessibility-props.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/accessibility-props.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/accessibility-state.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/accessibility-state.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/interactive.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/interactive.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/react-native-components.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/react-native-components.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/schema-properties.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/constants/schema-properties.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/index.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/models/optional-options.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/models/optional-options.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-actions-need-handler.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-hint.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-label.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-role.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/accessibility-value-needs-range.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/adjustable-needs-value.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/gate-announce-for-accessibility.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/input-purpose.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-aria-legacy-mix.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-deprecated-accessibility-focus.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-dropped-aria-on-text.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-font-scaling-disabled.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-ineffective-accessibility-props.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-invalid-accessibility-state.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-nested-focus-stops.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-press-in-activation.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-unnamed-interactive.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/require-invert-colors-opt-out.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/require-modal-a11y-on-sheets.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-amount-of-text-children.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-components.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-components.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-element-name.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-element-name.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-jsx-descendants.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-jsx-descendants.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-literal-property-value.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-literal-property-value.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-literal-text-child.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-literal-text-child.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-property.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-property.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/has-accessible-name.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/has-accessible-name.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/has-property.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/has-property.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/has-spread-props.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/has-spread-props.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-definitely-grouped.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-definitely-grouped.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-hidden-from-accessibility.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-hidden-from-accessibility.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-inside-focus-stop.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-inside-focus-stop.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-interactive.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-interactive.js +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-not-a-focus-stop.d.ts +0 -0
- /package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/is-not-a-focus-stop.js +0 -0
- /package/dist/plugins/{rules → react-native/rules}/test-id-default.d.ts +0 -0
- /package/dist/plugins/{rules → react-native/rules}/test-id-format.d.ts +0 -0
- /package/dist/plugins/{rules → react-native/rules}/test-id-from-props.d.ts +0 -0
- /package/dist/plugins/{rules → react-native/rules}/webview-allow-list.d.ts +0 -0
- /package/dist/plugins/{utils → shared}/comments.d.ts +0 -0
- /package/dist/plugins/{utils → shared}/comments.js +0 -0
- /package/dist/plugins/{utils → shared}/component-name.d.ts +0 -0
- /package/dist/plugins/{utils → shared}/component-name.js +0 -0
- /package/dist/plugins/{react-native-a11y/utils → shared}/create-rule.d.ts +0 -0
- /package/dist/plugins/{utils → shared}/create-rule.js +0 -0
- /package/dist/plugins/{utils → shared}/types.d.ts +0 -0
package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-dropped-aria-on-text.js
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ARIA_DROPPED_ON_TEXT } from "../constants/accessibility-props.js";
|
|
2
2
|
import { TEXTS } from "../constants/react-native-components.js";
|
|
3
3
|
import schemaProperties from "../constants/schema-properties.js";
|
|
4
|
-
import createRule from "
|
|
4
|
+
import createRule from "../../../shared/create-rule.js";
|
|
5
5
|
import getComponents from "../utils/get-components.js";
|
|
6
6
|
import getElementName from "../utils/get-element-name.js";
|
|
7
7
|
import getProperty from "../utils/get-property.js";
|
|
@@ -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;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import createRule from "../../../shared/create-rule.js";
|
|
2
|
+
import getLiteralPropertyValue from "../utils/get-literal-property-value.js";
|
|
3
|
+
import getProperty from "../utils/get-property.js";
|
|
4
|
+
/** WCAG 1.4.4 asks for text to resize up to 200%. */
|
|
5
|
+
const MINIMUM_MULTIPLIER = 2;
|
|
6
|
+
/** React Native reads `0` as "no maximum", which is the opposite of a cap. */
|
|
7
|
+
const NO_MAXIMUM = 0;
|
|
8
|
+
const noFontScalingDisabled = createRule({
|
|
9
|
+
name: "no-font-scaling-disabled",
|
|
10
|
+
meta: {
|
|
11
|
+
defaultOptions: [],
|
|
12
|
+
type: "problem",
|
|
13
|
+
docs: {
|
|
14
|
+
description: "Disallow opting text out of the system font scale below the level WCAG 1.4.4 requires.",
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
fontScalingDisabled: "'allowFontScaling={false}' stops this text respecting the system font size. Cap it with 'maxFontSizeMultiplier' instead if one element cannot grow.",
|
|
18
|
+
fontScalingCapped: "'maxFontSizeMultiplier={{{ multiplier }}}' caps text below the 200% WCAG 1.4.4 requires. Reserve a cap under {{ minimum }} for chrome whose information is available elsewhere.",
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
},
|
|
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
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
export default noFontScalingDisabled;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IMAGES, VIEWS } from "../constants/react-native-components.js";
|
|
2
|
-
import createRule from "
|
|
2
|
+
import createRule from "../../../shared/create-rule.js";
|
|
3
3
|
import getElementName from "../utils/get-element-name.js";
|
|
4
4
|
import getLiteralPropertyValue from "../utils/get-literal-property-value.js";
|
|
5
5
|
import getProperty from "../utils/get-property.js";
|
|
@@ -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;
|
package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-invalid-accessibility-state.js
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ASTUtils, AST_NODE_TYPES, } from "@typescript-eslint/utils";
|
|
2
2
|
import { ACCESSIBILITY_STATE_KEYS } from "../constants/accessibility-state.js";
|
|
3
|
-
import createRule from "
|
|
3
|
+
import createRule from "../../../shared/create-rule.js";
|
|
4
4
|
import getProperty from "../utils/get-property.js";
|
|
5
5
|
/** `{ … } as const` and `{ … } satisfies T` are still that object. */
|
|
6
6
|
const unwrap = (node) => {
|
|
@@ -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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TOUCHABLES } from "../constants/react-native-components.js";
|
|
2
2
|
import schemaProperties from "../constants/schema-properties.js";
|
|
3
|
-
import createRule from "
|
|
3
|
+
import createRule from "../../../shared/create-rule.js";
|
|
4
4
|
import getComponents from "../utils/get-components.js";
|
|
5
5
|
import getElementName from "../utils/get-element-name.js";
|
|
6
6
|
import getJsxDescendants from "../utils/get-jsx-descendants.js";
|
|
@@ -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) ||
|
package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-press-in-activation.js
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TOUCHABLES } from "../constants/react-native-components.js";
|
|
2
2
|
import schemaProperties from "../constants/schema-properties.js";
|
|
3
|
-
import createRule from "
|
|
3
|
+
import createRule from "../../../shared/create-rule.js";
|
|
4
4
|
import getComponents from "../utils/get-components.js";
|
|
5
5
|
import getElementName from "../utils/get-element-name.js";
|
|
6
6
|
import getProperty from "../utils/get-property.js";
|
|
@@ -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;
|
package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/no-unnamed-interactive.js
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NAME_PROPS } from "../constants/interactive.js";
|
|
2
2
|
import { IMAGES, INPUTS, TEXTS, TOUCHABLES, } from "../constants/react-native-components.js";
|
|
3
3
|
import schemaProperties from "../constants/schema-properties.js";
|
|
4
|
-
import createRule from "
|
|
4
|
+
import createRule from "../../../shared/create-rule.js";
|
|
5
5
|
import getAmountOfTextChildren from "../utils/get-amount-of-text-children.js";
|
|
6
6
|
import getComponents from "../utils/get-components.js";
|
|
7
7
|
import getElementName from "../utils/get-element-name.js";
|
|
@@ -38,10 +38,10 @@ 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;
|
package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/require-invert-colors-opt-out.js
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IMAGES } from "../constants/react-native-components.js";
|
|
2
2
|
import schemaProperties from "../constants/schema-properties.js";
|
|
3
|
-
import createRule from "
|
|
3
|
+
import createRule from "../../../shared/create-rule.js";
|
|
4
4
|
import getComponents from "../utils/get-components.js";
|
|
5
5
|
import getElementName from "../utils/get-element-name.js";
|
|
6
6
|
import hasProperty from "../utils/has-property.js";
|
|
@@ -29,7 +29,7 @@ const requireInvertColorsOptOut = createRule({
|
|
|
29
29
|
},
|
|
30
30
|
],
|
|
31
31
|
},
|
|
32
|
-
create(context) {
|
|
32
|
+
create: (context) => {
|
|
33
33
|
/**
|
|
34
34
|
* Matched on the element name, so an image from expo-image or a wrapper
|
|
35
35
|
* named through additionalComponents is checked the same way. Deliberately
|
|
@@ -41,7 +41,7 @@ const requireInvertColorsOptOut = createRule({
|
|
|
41
41
|
defaultComponents: IMAGES,
|
|
42
42
|
});
|
|
43
43
|
return {
|
|
44
|
-
JSXOpeningElement(node) {
|
|
44
|
+
JSXOpeningElement: (node) => {
|
|
45
45
|
const componentName = getElementName(node);
|
|
46
46
|
if (componentName === undefined) {
|
|
47
47
|
return;
|
package/dist/plugins/{react-native-a11y → react-native/a11y}/rules/require-modal-a11y-on-sheets.js
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import schemaProperties from "../constants/schema-properties.js";
|
|
2
|
-
import createRule from "
|
|
2
|
+
import createRule from "../../../shared/create-rule.js";
|
|
3
3
|
import getComponents from "../utils/get-components.js";
|
|
4
4
|
import getElementName from "../utils/get-element-name.js";
|
|
5
5
|
import hasProperty from "../utils/has-property.js";
|
|
@@ -27,7 +27,7 @@ const requireModalA11yOnSheets = createRule({
|
|
|
27
27
|
},
|
|
28
28
|
],
|
|
29
29
|
},
|
|
30
|
-
create(context) {
|
|
30
|
+
create: (context) => {
|
|
31
31
|
/**
|
|
32
32
|
* No default list: a sheet is whatever this project calls one, and
|
|
33
33
|
* hardcoding one library's component names would fire on a codebase that
|
|
@@ -35,7 +35,7 @@ const requireModalA11yOnSheets = createRule({
|
|
|
35
35
|
*/
|
|
36
36
|
const components = getComponents({ context });
|
|
37
37
|
return {
|
|
38
|
-
JSXOpeningElement(node) {
|
|
38
|
+
JSXOpeningElement: (node) => {
|
|
39
39
|
const componentName = getElementName(node);
|
|
40
40
|
if (componentName === undefined) {
|
|
41
41
|
return;
|
package/dist/plugins/{react-native-a11y → react-native/a11y}/utils/get-amount-of-text-children.js
RENAMED
|
@@ -32,6 +32,20 @@ const branchesOf = (expression) => {
|
|
|
32
32
|
};
|
|
33
33
|
const isJsxChild = (expression) => expression.type === AST_NODE_TYPES.JSXElement ||
|
|
34
34
|
expression.type === AST_NODE_TYPES.JSXFragment;
|
|
35
|
+
const countExpressionText = (expression, amount) => {
|
|
36
|
+
if (expression.type === AST_NODE_TYPES.Literal ||
|
|
37
|
+
expression.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
38
|
+
return amount + 1;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* JSX inside an expression renders like any other child, and a branch
|
|
42
|
+
* that renders text still names the control
|
|
43
|
+
*/
|
|
44
|
+
const rendered = (isJsxChild(expression) ? [expression] : branchesOf(expression)).filter(isJsxChild);
|
|
45
|
+
return rendered.length > 0
|
|
46
|
+
? getAmountOfTextChildren(rendered, amount)
|
|
47
|
+
: amount;
|
|
48
|
+
};
|
|
35
49
|
/**
|
|
36
50
|
* Counts children that render visible text, which is what makes an
|
|
37
51
|
* `accessibilityLabel` redundant. Whitespace between JSX tags is not text, and
|
|
@@ -42,19 +56,7 @@ const getAmountOfTextChildren = (children, oldAmount = 0) => children.reduce((am
|
|
|
42
56
|
return child.value.trim().length > 0 ? amount + 1 : amount;
|
|
43
57
|
}
|
|
44
58
|
if (child.type === AST_NODE_TYPES.JSXExpressionContainer) {
|
|
45
|
-
|
|
46
|
-
if (expression.type === AST_NODE_TYPES.Literal ||
|
|
47
|
-
expression.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
48
|
-
return amount + 1;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* JSX inside an expression renders like any other child, and a branch
|
|
52
|
-
* that renders text still names the control
|
|
53
|
-
*/
|
|
54
|
-
const rendered = (isJsxChild(expression) ? [expression] : branchesOf(expression)).filter(isJsxChild);
|
|
55
|
-
return rendered.length > 0
|
|
56
|
-
? getAmountOfTextChildren(rendered, amount)
|
|
57
|
-
: amount;
|
|
59
|
+
return countExpressionText(child.expression, amount);
|
|
58
60
|
}
|
|
59
61
|
if (child.type === AST_NODE_TYPES.JSXFragment) {
|
|
60
62
|
return getAmountOfTextChildren(child.children, amount);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import testIdDefault from './rules/test-id-default.js';
|
|
2
|
+
import testIdFormat from './rules/test-id-format.js';
|
|
3
|
+
import testIdFromProps from './rules/test-id-from-props.js';
|
|
4
|
+
import webviewAllowList from './rules/webview-allow-list.js';
|
|
5
|
+
/** ESLint reads a scoped rule ID up to its last slash as the plugin name. */
|
|
6
|
+
// One object per plugin name: flat config rejects two different objects under the same name.
|
|
7
|
+
const plugin = {
|
|
8
|
+
meta: { name: '@hypersolid-dev/react-native' },
|
|
9
|
+
// typescript-eslint's `RuleModule` and ESLint's `Rule.RuleModule` type the same runtime object differently.
|
|
10
|
+
rules: {
|
|
11
|
+
'test-id-default': testIdDefault,
|
|
12
|
+
'test-id-format': testIdFormat,
|
|
13
|
+
'test-id-from-props': testIdFromProps,
|
|
14
|
+
'webview-allow-list': webviewAllowList,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
export default plugin;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
3
|
-
import createRule from '
|
|
3
|
+
import createRule from '../../shared/create-rule.js';
|
|
4
4
|
/**
|
|
5
5
|
* A component's `testID` prop defaults to the component's own name, kebab-cased.
|
|
6
6
|
*
|
|
@@ -31,24 +31,20 @@ const PASCAL_CASE_PATTERN = /^[A-Z]/;
|
|
|
31
31
|
const LOWER_TO_UPPER_PATTERN = /(?<=[a-z0-9])(?=[A-Z])/g;
|
|
32
32
|
const ACRONYM_END_PATTERN = /(?<=[A-Z])(?=[A-Z][a-z])/g;
|
|
33
33
|
/** `CompetitionLogo` -> `competition-logo`, `AdSlotURLBar` -> `ad-slot-url-bar`. */
|
|
34
|
-
|
|
35
|
-
return name.replaceAll(LOWER_TO_UPPER_PATTERN, '-').replaceAll(ACRONYM_END_PATTERN, '-').toLowerCase();
|
|
36
|
-
}
|
|
34
|
+
const kebab = (name) => name.replaceAll(LOWER_TO_UPPER_PATTERN, '-').replaceAll(ACRONYM_END_PATTERN, '-').toLowerCase();
|
|
37
35
|
/** The component a file is named for: its basename, or its folder's for an `index` file. */
|
|
38
|
-
|
|
36
|
+
const componentName = (filename) => {
|
|
39
37
|
const name = path.basename(filename).replace(EXTENSION_PATTERN, '').replace(PLATFORM_PATTERN, '');
|
|
40
38
|
return name === 'index' ? path.basename(path.dirname(filename)) : name;
|
|
41
|
-
}
|
|
42
|
-
|
|
39
|
+
};
|
|
40
|
+
const isHoc = (callee) => {
|
|
43
41
|
if (callee.type === AST_NODE_TYPES.Identifier)
|
|
44
42
|
return HOC_NAMES.has(callee.name);
|
|
45
43
|
return callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed && HOC_NAMES.has(callee.property.name);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return Boolean(node && FUNCTION_TYPES.has(node.type));
|
|
49
|
-
}
|
|
44
|
+
};
|
|
45
|
+
const isFunction = (node) => Boolean(node && FUNCTION_TYPES.has(node.type));
|
|
50
46
|
/** Through `memo(…)`, `forwardRef(…)` and type assertions, to the component inside. */
|
|
51
|
-
|
|
47
|
+
const unwrapComponent = (node) => {
|
|
52
48
|
let current = node ?? undefined;
|
|
53
49
|
while (current) {
|
|
54
50
|
if (TS_WRAPPERS.has(current.type))
|
|
@@ -59,31 +55,31 @@ function unwrapComponent(node) {
|
|
|
59
55
|
return current;
|
|
60
56
|
}
|
|
61
57
|
return current;
|
|
62
|
-
}
|
|
58
|
+
};
|
|
63
59
|
/** What a function is held by, out through the calls and assertions it is declared behind. */
|
|
64
|
-
|
|
60
|
+
const heldBy = (fn) => {
|
|
65
61
|
let held = fn.parent;
|
|
66
62
|
while (held && (held.type === AST_NODE_TYPES.CallExpression || TS_WRAPPERS.has(held.type)))
|
|
67
63
|
held = held.parent;
|
|
68
64
|
return held;
|
|
69
|
-
}
|
|
65
|
+
};
|
|
70
66
|
/** Whether a function is the file's own component: named `component`, or an anonymous default export. */
|
|
71
|
-
|
|
67
|
+
const isComponent = (fn, component) => {
|
|
72
68
|
if (fn.type === AST_NODE_TYPES.FunctionDeclaration)
|
|
73
69
|
return !fn.id || fn.id.name === component;
|
|
74
70
|
const held = heldBy(fn);
|
|
75
71
|
if (held?.type === AST_NODE_TYPES.ExportDefaultDeclaration)
|
|
76
72
|
return true;
|
|
77
73
|
return held?.type === AST_NODE_TYPES.VariableDeclarator && held.id.type === AST_NODE_TYPES.Identifier && held.id.name === component;
|
|
78
|
-
}
|
|
74
|
+
};
|
|
79
75
|
/** The `testID` property of a destructured parameter or declarator. */
|
|
80
|
-
|
|
76
|
+
const testIdProperty = (pattern) => {
|
|
81
77
|
if (pattern?.type !== AST_NODE_TYPES.ObjectPattern)
|
|
82
78
|
return undefined;
|
|
83
79
|
return pattern.properties.find((property) => property.type === AST_NODE_TYPES.Property && !property.computed && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === 'testID');
|
|
84
|
-
}
|
|
80
|
+
};
|
|
85
81
|
/** The names a component's props object goes by: the first parameter, or the rest of a destructured one without `testID`. */
|
|
86
|
-
|
|
82
|
+
const propsNames = (fn) => {
|
|
87
83
|
const first = fn.params.at(0);
|
|
88
84
|
if (first?.type === AST_NODE_TYPES.Identifier)
|
|
89
85
|
return [first.name];
|
|
@@ -91,15 +87,15 @@ function propsNames(fn) {
|
|
|
91
87
|
return [];
|
|
92
88
|
const rest = first.properties.find((property) => property.type === AST_NODE_TYPES.RestElement);
|
|
93
89
|
return rest?.argument.type === AST_NODE_TYPES.Identifier ? [rest.argument.name] : [];
|
|
94
|
-
}
|
|
90
|
+
};
|
|
95
91
|
/** The file's own component that `node` sits in, however deep in callbacks. */
|
|
96
|
-
|
|
92
|
+
const ownComponentAround = (node, component) => {
|
|
97
93
|
for (let current = node.parent; current; current = current.parent) {
|
|
98
94
|
if (isFunction(current) && isComponent(current, component))
|
|
99
95
|
return current;
|
|
100
96
|
}
|
|
101
97
|
return undefined;
|
|
102
|
-
}
|
|
98
|
+
};
|
|
103
99
|
const testIdDefault = createRule({
|
|
104
100
|
name: 'test-id-default',
|
|
105
101
|
meta: {
|
|
@@ -113,7 +109,7 @@ const testIdDefault = createRule({
|
|
|
113
109
|
wrong: 'Default this to `"{{ expected }}"`, not `{{ actual }}`: the default is the component\'s own name in kebab-case, so a reader can predict the id without opening the file.',
|
|
114
110
|
},
|
|
115
111
|
},
|
|
116
|
-
create(context) {
|
|
112
|
+
create: (context) => {
|
|
117
113
|
const component = componentName(context.filename);
|
|
118
114
|
// The file's own component only; anything else in here is private to it.
|
|
119
115
|
if (!PASCAL_CASE_PATTERN.test(component))
|
|
@@ -146,7 +142,7 @@ const testIdDefault = createRule({
|
|
|
146
142
|
check(testIdProperty(node.params[0]));
|
|
147
143
|
};
|
|
148
144
|
return {
|
|
149
|
-
VariableDeclarator(node) {
|
|
145
|
+
VariableDeclarator: (node) => {
|
|
150
146
|
// `const { testID } = props` inside the file's own component reads the same prop.
|
|
151
147
|
if (node.id.type === AST_NODE_TYPES.ObjectPattern && node.init?.type === AST_NODE_TYPES.Identifier) {
|
|
152
148
|
const fn = ownComponentAround(node, component);
|
|
@@ -157,20 +153,20 @@ const testIdDefault = createRule({
|
|
|
157
153
|
if (node.id.type === AST_NODE_TYPES.Identifier)
|
|
158
154
|
checkFunction(unwrapComponent(node.init), node.id.name);
|
|
159
155
|
},
|
|
160
|
-
FunctionDeclaration(node) {
|
|
156
|
+
FunctionDeclaration: (node) => {
|
|
161
157
|
if (node.id)
|
|
162
158
|
checkFunction(node, node.id.name);
|
|
163
159
|
else if (node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration)
|
|
164
160
|
checkFunction(node, component);
|
|
165
161
|
},
|
|
166
162
|
// `export default memo(({ testID }) => …)`: the anonymous component.
|
|
167
|
-
ExportDefaultDeclaration(node) {
|
|
163
|
+
ExportDefaultDeclaration: (node) => {
|
|
168
164
|
const init = unwrapComponent(node.declaration);
|
|
169
165
|
if (init?.type !== AST_NODE_TYPES.FunctionDeclaration)
|
|
170
166
|
checkFunction(init, component);
|
|
171
167
|
},
|
|
172
168
|
// `props.testID` read inside the component, with no default anywhere.
|
|
173
|
-
MemberExpression(node) {
|
|
169
|
+
MemberExpression: (node) => {
|
|
174
170
|
if (node.computed || node.property.type !== AST_NODE_TYPES.Identifier || node.property.name !== 'testID')
|
|
175
171
|
return;
|
|
176
172
|
if (node.object.type !== AST_NODE_TYPES.Identifier)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
-
import createRule from '
|
|
3
|
-
import { leavesOf } from '
|
|
2
|
+
import createRule from '../../shared/create-rule.js';
|
|
3
|
+
import { leavesOf } from '../../shared/expressions.js';
|
|
4
4
|
/**
|
|
5
5
|
* A `testID` written out in full is lowercase kebab-case: `crest`,
|
|
6
6
|
* `vote-input`, `top-scorers-card-home-crest`.
|
|
@@ -29,34 +29,30 @@ const LEADING_SEPARATOR_PATTERN = /^[^A-Za-z0-9]/;
|
|
|
29
29
|
const TRAILING_SEPARATOR_PATTERN = /[^A-Za-z0-9]$/;
|
|
30
30
|
const TEST_ID = 'testID';
|
|
31
31
|
/** `Match.playerItem_photo` -> `match-player-item-photo`. */
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.toLowerCase();
|
|
39
|
-
}
|
|
32
|
+
const kebab = (value) => value
|
|
33
|
+
.replaceAll(LOWER_TO_UPPER_PATTERN, '-')
|
|
34
|
+
.replaceAll(ACRONYM_END_PATTERN, '-')
|
|
35
|
+
.replaceAll(NON_ALPHANUMERIC_RUN_PATTERN, '-')
|
|
36
|
+
.replaceAll(EDGE_DASHES_PATTERN, '')
|
|
37
|
+
.toLowerCase();
|
|
40
38
|
/** A piece between substitutions keeps a separator where it meets one: `_photo` becomes `-photo`. */
|
|
41
|
-
|
|
39
|
+
const kebabFragment = (value) => {
|
|
42
40
|
const core = kebab(value);
|
|
43
41
|
const lead = LEADING_SEPARATOR_PATTERN.test(value) ? '-' : '';
|
|
44
42
|
const trail = TRAILING_SEPARATOR_PATTERN.test(value) ? '-' : '';
|
|
45
43
|
return core ? `${lead}${core}${trail}` : '-';
|
|
46
|
-
}
|
|
44
|
+
};
|
|
47
45
|
/** Whether `inner` is written inside `outer`, rather than elsewhere in the file. */
|
|
48
|
-
|
|
49
|
-
return inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
|
|
50
|
-
}
|
|
46
|
+
const isInside = (outer, inner) => inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
|
|
51
47
|
/** The written string an expression is, or `undefined` when it is not one. */
|
|
52
|
-
|
|
48
|
+
const writtenString = (node) => {
|
|
53
49
|
if (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string')
|
|
54
50
|
return node.value;
|
|
55
51
|
// A template with nothing substituted is a literal written the long way.
|
|
56
52
|
if (node.type === AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0)
|
|
57
53
|
return node.quasis[0]?.value.cooked ?? undefined;
|
|
58
54
|
return undefined;
|
|
59
|
-
}
|
|
55
|
+
};
|
|
60
56
|
const testIdFormat = createRule({
|
|
61
57
|
name: 'test-id-format',
|
|
62
58
|
meta: {
|
|
@@ -68,7 +64,7 @@ const testIdFormat = createRule({
|
|
|
68
64
|
format: 'Write this `testID` as `{{ suggestion }}`, not "{{ value }}": an id is lowercase kebab-case throughout, depth included. A dot is a wildcard in Maestro\'s `id:` matcher, and a capital is one shift key away from a flake — so a child derives with a dash, `` `${testID}-remote-image` ``, at every level.',
|
|
69
65
|
},
|
|
70
66
|
},
|
|
71
|
-
create(context) {
|
|
67
|
+
create: (context) => {
|
|
72
68
|
// Reported on the string when it sits inside the expression, else on the expression a name led from.
|
|
73
69
|
const check = (expression, leaf) => {
|
|
74
70
|
const value = writtenString(leaf);
|
|
@@ -101,7 +97,7 @@ const testIdFormat = createRule({
|
|
|
101
97
|
}
|
|
102
98
|
};
|
|
103
99
|
return {
|
|
104
|
-
JSXAttribute(node) {
|
|
100
|
+
JSXAttribute: (node) => {
|
|
105
101
|
if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.name.name !== TEST_ID)
|
|
106
102
|
return;
|
|
107
103
|
const { value } = node;
|
|
@@ -111,7 +107,7 @@ const testIdFormat = createRule({
|
|
|
111
107
|
checkExpression(value.expression);
|
|
112
108
|
},
|
|
113
109
|
// A `testID` default, and a `testID:` in an options or fixture object.
|
|
114
|
-
Property(node) {
|
|
110
|
+
Property: (node) => {
|
|
115
111
|
if (node.computed || node.key.type !== AST_NODE_TYPES.Identifier || node.key.name !== TEST_ID)
|
|
116
112
|
return;
|
|
117
113
|
checkExpression(node.value.type === AST_NODE_TYPES.AssignmentPattern ? node.value.right : node.value);
|