Skip to content

Commit 238a4c6

Browse files
authored
Feat/rule image requires accessible prop (#31)
* feat: create image-requires-accessible-prop rule * test: add test for image-requires-accessible-prop * feat: add breaking-example in example-app * docs: add .md for image-requires-accessible-prop * feat: add quick-fix suggestion * fix: fix docs linting issues * chore: remove unnecessary comment * feat: simplify breaking examples
1 parent 28c61d2 commit 238a4c6

7 files changed

Lines changed: 186 additions & 5 deletions

File tree

example-app/.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
}
99
],
1010
"rules": {
11-
"@bam.tech/require-named-effect": "error"
11+
"@bam.tech/require-named-effect": "error",
12+
"@bam.tech/image-requires-accessible-prop": "error"
1213
}
1314
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Save without formatting: [⌘ + K] > [S]
2+
3+
// This should trigger an error breaking eslint-plugin-bam-custom-rules:
4+
// bam-custom-rules/image-requires-accessible-prop
5+
6+
import { Image, View } from "react-native";
7+
8+
const MyComponent = () => {
9+
return (
10+
<>
11+
<Image source={{ uri: "" }} accessibilityIgnoresInvertColors />
12+
<OtherImage source={{ uri: "random" }} accessibilityIgnoresInvertColors />
13+
<NotReallyAnImage></NotReallyAnImage>
14+
</>
15+
);
16+
};
17+
18+
const OtherImage = Image;
19+
const NotReallyAnImage = View;
20+
export default MyComponent;

packages/eslint-plugin/README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,13 @@ This plugin exports some custom rules that you can optionally use in your projec
5454

5555
<!-- begin auto-generated rules list -->
5656

57-
| Name | Description |
58-
| :--------------------------------------------------------- | :----------------------------------------------------- |
59-
| [require-named-effect](docs/rules/require-named-effect.md) | Enforces the use of named functions inside a useEffect |
57+
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
58+
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
59+
60+
| Name | Description | 🔧 | 💡 |
61+
| :----------------------------------------------------------------------------- | :----------------------------------------------------- | :-- | :-- |
62+
| [image-requires-accessible-prop](docs/rules/image-requires-accessible-prop.md) | Require accessible prop on image components | 🔧 | 💡 |
63+
| [require-named-effect](docs/rules/require-named-effect.md) | Enforces the use of named functions inside a useEffect | | |
6064

6165
<!-- end auto-generated rules list -->
6266

@@ -67,7 +71,8 @@ To use a rule, just declare it in your `.eslintrc`:
6771
{
6872
"plugins": ["@bam.tech"],
6973
"rules": {
70-
"@bam.tech/require-named-effect": "error"
74+
"@bam.tech/require-named-effect": "error",
75+
"@bam.tech/image-requires-accessible-prop": "error"
7176
}
7277
}
7378
```
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Require accessible prop on image components (`@bam.tech/image-requires-accessible-prop`)
2+
3+
🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Enforces to use an accessible prop on an image.
8+
9+
🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
10+
11+
## Rule Details
12+
13+
Examples of **incorrect** code for this rule:
14+
15+
```jsx
16+
<Image source={{ uri: "" }} />
17+
```
18+
19+
```jsx
20+
<BackgroundImage source={{ uri: "" }} />
21+
```
22+
23+
Examples of **correct** code for this rule:
24+
25+
```jsx
26+
<Image source={{ uri: "" }} accessible />
27+
```
28+
29+
```jsx
30+
<BackgroundImage source={{ uri: "" }} accessible={false} />
31+
```
32+
33+
## Further Reading
34+
35+
This rule is only relevant if all your image components are suffixed with `Image`. If the component has children, it won't trigger an error to avoid confusion with containers.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @fileoverview Image requires an accessible prop
3+
* @author Paul Briand
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
/** @type {import('eslint').Rule.RuleModule} */
12+
module.exports = {
13+
meta: {
14+
hasSuggestions: true,
15+
type: "problem", // `problem`, `suggestion`, or `layout`
16+
docs: {
17+
description: "Require accessible prop on image components",
18+
recommended: false,
19+
url: "https://github.com/bamlab/react-native-project-config/tree/main/packages/eslint-plugin/docs/rules/image-requires-accessible-prop.md", // URL to the documentation page for this rule
20+
},
21+
fixable: "code", // Or `code` or `whitespace`
22+
schema: [], // Add a schema if the rule has options
23+
messages: {
24+
imageRequiresAccessibleProp: "Image components require accessible prop",
25+
suggestAddingAccessibleProp: "Add accessible prop",
26+
},
27+
},
28+
29+
create(context) {
30+
return {
31+
JSXOpeningElement: (node) => {
32+
console.log(node);
33+
if (
34+
isImage(node) &&
35+
!node.attributes.some((attr) => attr.name.name === "accessible")
36+
) {
37+
context.report({
38+
node,
39+
messageId: "imageRequiresAccessibleProp",
40+
suggest: [
41+
{
42+
messageId: "suggestAddingAccessibleProp",
43+
fix: (fixer) => {
44+
const openingTagEnd = node.range[1];
45+
return fixer.insertTextBeforeRange(
46+
[openingTagEnd - (node.selfClosing ? 2 : 1), openingTagEnd],
47+
" accessible={true}"
48+
);
49+
},
50+
},
51+
],
52+
});
53+
}
54+
},
55+
};
56+
},
57+
};
58+
59+
const isImage = require("../utils/isImage");
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = (element) => {
2+
if (element.type === "JSXOpeningElement") {
3+
if (
4+
element.name.type == "JSXIdentifier" &&
5+
element.name.name.endsWith("Image")
6+
) {
7+
if (element.parent && element.parent.children.length > 0) {
8+
return false;
9+
}
10+
11+
return true;
12+
}
13+
}
14+
15+
return false;
16+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @fileoverview Image requires an accessible prop
3+
* @author Paul Briand
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
const rule = require("../../../lib/rules/image-requires-accessible-prop"),
12+
RuleTester = require("eslint").RuleTester;
13+
14+
//------------------------------------------------------------------------------
15+
// Tests
16+
//------------------------------------------------------------------------------
17+
const ruleTester = new RuleTester({
18+
parser: require.resolve("@typescript-eslint/parser"),
19+
parserOptions: {
20+
ecmaVersion: 2021,
21+
sourceType: "module",
22+
ecmaFeatures: {
23+
jsx: true,
24+
},
25+
},
26+
});
27+
28+
const valid = [
29+
`<Image source={{uri:""}} accessible />`,
30+
`<BackgroundImage source={{uri:""}} accessible={false} />`,
31+
];
32+
33+
const invalid = [
34+
`<Image source={{uri:""}} />`,
35+
`<BackgroundImage source={{uri:""}} />`,
36+
];
37+
38+
ruleTester.run("image-requires-accessible-prop", rule, {
39+
valid,
40+
41+
invalid: invalid.map((code) => ({
42+
code,
43+
errors: ["Image components require accessible prop"],
44+
})),
45+
});

0 commit comments

Comments
 (0)