Skip to content

Commit 52ef6d1

Browse files
Add tests, update readme, and update logic (#3)
* add basic unit tests * update readme, logic, and tests
1 parent 4557691 commit 52ef6d1

File tree

3 files changed

+200
-50
lines changed

3 files changed

+200
-50
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
# @fullstory/eslint-plugin-annotate-react
22

3-
An ESLint plugin for annotating React components.
3+
An ESLint plugin for adding 'data-attribute' to React components. The purpose of this plugin is to automatically
4+
make css selectors. Here is an example
5+
6+
```
7+
const myDiv = () => (
8+
<div/>
9+
); `;
10+
```
11+
12+
This plugin will autofix and add data-component to the div
13+
14+
```
15+
const myDiv = () => (
16+
<div data-component="temp"/>
17+
); `;
18+
```
19+
20+
This plugin is intended to not be too opinionated. In general the approach is to suggest to the developer to add 'data-attribute' when there is an obvious approach, but in questionable cases, the plugin will tend towards being quiet.
21+
22+
- When there is a [fragment](https://reactjs.org/docs/fragments.html) this plugin won't add data-attribute
23+
- Where there are multiple return elements this plugin won't add data-attribute
424

525
## Installation
626

@@ -36,6 +56,14 @@ Then configure the rules you want to use under the rules section.
3656
}
3757
```
3858

59+
## Maintaining this plugin
60+
61+
Tests can be ran using
62+
63+
```
64+
npm run test
65+
```
66+
3967
## Supported Rules
4068

4169
### @fullstory/annotate-react/data-component

index.js

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -138,49 +138,51 @@ const rules = {
138138
return flag;
139139
});
140140

141-
const [componentNode] = componentNodes;
141+
componentNodes.forEach((componentNode) => {
142+
const componentName =
143+
componentNode?.id?.name ??
144+
componentNode?.declarations?.map(
145+
(declaration) => declaration?.id?.name,
146+
);
147+
148+
let fixNode = null;
149+
150+
traverseTree(
151+
getReturnStatement(componentNode),
152+
visitorKeys,
153+
(current) => {
154+
if (isSubtreeDone(current)) {
155+
throw DONE_WITH_SUBTREE;
156+
} else if (isTreeDone(current, excludeComponentNames)) {
157+
fixNode = current.openingElement;
142158

143-
const componentName =
144-
componentNode?.id?.name ??
145-
componentNode?.declarations?.map(
146-
(declaration) => declaration?.id?.name,
159+
throw DONE_WITH_TREE;
160+
}
161+
},
147162
);
148163

149-
let fixNode = null;
150-
151-
traverseTree(
152-
getReturnStatement(componentNode),
153-
visitorKeys,
154-
(current) => {
155-
if (isSubtreeDone(current)) {
156-
throw DONE_WITH_SUBTREE;
157-
} else if (isTreeDone(current, excludeComponentNames)) {
158-
fixNode = current.openingElement;
159-
160-
throw DONE_WITH_TREE;
161-
}
162-
},
163-
);
164-
165-
if (Boolean(componentName)) {
166-
context.report({
167-
node: fixNode,
168-
message: `${
169-
Array.isArray(componentName) ? componentName[0] : componentName
170-
} is missing the data-component attribute for the top-level element.`,
171-
fix: (fixer) =>
172-
fixer.insertTextAfterRange(
173-
Boolean(fixNode.typeParameters)
174-
? fixNode.typeParameters.range
175-
: fixNode.name.range,
176-
`\ndata-component="${
177-
Array.isArray(componentName)
178-
? componentName[0]
179-
: componentName
180-
}"`,
181-
),
182-
});
183-
}
164+
if (Boolean(componentName)) {
165+
context.report({
166+
node: fixNode,
167+
message: `${
168+
Array.isArray(componentName)
169+
? componentName[0]
170+
: componentName
171+
} is missing the data-component attribute for the top-level element.`,
172+
fix: (fixer) =>
173+
fixer.insertTextAfterRange(
174+
Boolean(fixNode.typeParameters)
175+
? fixNode.typeParameters.range
176+
: fixNode.name.range,
177+
` data-component="${
178+
Array.isArray(componentName)
179+
? componentName[0]
180+
: componentName
181+
}"`,
182+
),
183+
});
184+
}
185+
});
184186
},
185187
};
186188
},

test.js

Lines changed: 129 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,90 @@ const { join } = require('path');
66
// Test File Definitions
77
//------------------------------------------------------------------------------
88

9+
const singleComponent = `const temp = () => {
10+
<Icon data-component="temp" name="metric" size={24} />;
11+
};`;
12+
const singleComponentError = `const temp = () => {
13+
<Icon name="metric" size={24} />;
14+
};`;
15+
16+
const genericTest = `
17+
const yAxis = (xScale, xTicks) => (
18+
<BottomAxis<Date> data-component="yAxis" width={1} height={1} xScale={xScale} xTicks={xTicks}>
19+
123
20+
</BottomAxis>
21+
); `;
22+
const genericTestError = `
23+
const yAxis = (xScale, xTicks) => (
24+
<BottomAxis<Date> width={1} height={1} xScale={xScale} xTicks={xTicks}>
25+
123
26+
</BottomAxis>
27+
); `;
28+
29+
const renamingComponentDoesntError = `
30+
const myDiv = () => (
31+
<div data-component="temp"/>
32+
); `;
33+
34+
const nestedComponentsError = /* tsx */ `
35+
export const FooChart: React.FC<FooChartProps> = props => {
36+
const [spacing, setSpacing] = useState({ top: 8, right: 38, bottom: 50, left: 50 });
37+
38+
return (
39+
<Chart height={400} spacing={spacing}>
40+
{({ height, width, overlay }) => (
41+
<InnerFooChart
42+
width={width}
43+
height={height}
44+
overlay={overlay}
45+
spacing={spacing}
46+
setSpacing={setSpacing}
47+
{...props}
48+
/>
49+
)}
50+
</Chart>
51+
);
52+
};`;
53+
54+
const nestedComponents = /* tsx */ `
55+
export const FooChart: React.FC<FooChartProps> = props => {
56+
const [spacing, setSpacing] = useState({ top: 8, right: 38, bottom: 50, left: 50 });
57+
58+
return (
59+
<Chart data-component="FooChart" height={400} spacing={spacing}>
60+
{({ height, width, overlay }) => (
61+
<InnerFooChart
62+
width={width}
63+
height={height}
64+
overlay={overlay}
65+
spacing={spacing}
66+
setSpacing={setSpacing}
67+
{...props}
68+
/>
69+
)}
70+
</Chart>
71+
);
72+
};`;
73+
74+
const multipleComponentsErrors = `
75+
const Component1 = () => <div />;
76+
const Component2 = () => <span />;
77+
`;
78+
const multipleComponents = `
79+
const Component1 = () => <div data-component="Component1" />;
80+
const Component2 = () => <span data-component="Component2" />;
81+
`;
82+
83+
const fragmentsWontUpdate = `const Component = () => {
84+
<>
85+
<a/>
86+
<a/>
87+
<a/>
88+
</>
89+
};`;
90+
991
const tests = {
10-
'data-component-tsx': {
92+
'data-component': {
1193
// Require the actual rule definition
1294
rule: require('./index').rules['data-component'],
1395

@@ -31,20 +113,58 @@ const tests = {
31113

32114
// Define the test cases
33115
testCases: {
34-
valid: [],
116+
valid: [
117+
{
118+
code: singleComponent,
119+
},
120+
{
121+
code: genericTest,
122+
},
123+
{
124+
code: renamingComponentDoesntError,
125+
},
126+
{
127+
code: nestedComponents,
128+
},
129+
{
130+
code: multipleComponents,
131+
},
132+
{
133+
// Multiple return paths should not trigger the eslint warning
134+
code: fragmentsWontUpdate,
135+
},
136+
],
35137
invalid: [
36138
{
37-
code: /* tsx */ `const temp = () => {
38-
<Icon name="metrics-insights/insight-icon" size={24} />;
39-
};`,
40-
output: /* tsx */ `const temp = () => {
41-
<Icon
42-
data-component="temp" name="metrics-insights/insight-icon" size={24} />;
43-
};`,
139+
code: singleComponentError,
140+
output: singleComponent,
44141
errors: [
45142
'temp is missing the data-component attribute for the top-level element.',
46143
],
47144
},
145+
{
146+
code: genericTestError,
147+
output: genericTest,
148+
errors: [
149+
'yAxis is missing the data-component attribute for the top-level element.',
150+
],
151+
},
152+
{
153+
code: nestedComponentsError,
154+
output: nestedComponents,
155+
errors: [
156+
'FooChart is missing the data-component attribute for the top-level element.',
157+
],
158+
},
159+
{
160+
// Multiple components with errors
161+
code: multipleComponentsErrors,
162+
output: multipleComponents,
163+
errors: [
164+
'Component1 is missing the data-component attribute for the top-level element.',
165+
'Component2 is missing the data-component attribute for the top-level element.',
166+
],
167+
},
48168
],
49169
},
50170
},

0 commit comments

Comments
 (0)