Skip to content

Commit b1b78c7

Browse files
committed
Add checkContextObjects option in display-name
1 parent 92baa00 commit b1b78c7

File tree

8 files changed

+177
-254
lines changed

8 files changed

+177
-254
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,6 @@ module.exports = [
293293
| :----------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- |
294294
| [boolean-prop-naming](docs/rules/boolean-prop-naming.md) | Enforces consistent naming for boolean props | | | | | |
295295
| [button-has-type](docs/rules/button-has-type.md) | Disallow usage of `button` elements without an explicit `type` attribute | | | | | |
296-
| [context-display-name](docs/rules/context-display-name.md) | Disallow missing displayName in a React context definition | | | | | |
297296
| [default-props-match-prop-types](docs/rules/default-props-match-prop-types.md) | Enforce all defaultProps have a corresponding non-required PropType | | | | | |
298297
| [destructuring-assignment](docs/rules/destructuring-assignment.md) | Enforce consistent usage of destructuring assignment of props, state, and context | | | 🔧 | | |
299298
| [display-name](docs/rules/display-name.md) | Disallow missing displayName in a React component definition | ☑️ | | | | |

docs/rules/context-display-name.md

Lines changed: 0 additions & 24 deletions
This file was deleted.

docs/rules/display-name.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const Hello = React.memo(function Hello({ a }) {
4545

4646
```js
4747
...
48-
"react/display-name": [<enabled>, { "ignoreTranspilerName": <boolean> }]
48+
"react/display-name": [<enabled>, { "ignoreTranspilerName": <boolean>, "checkContextObjects": <boolean> }]
4949
...
5050
```
5151

@@ -128,6 +128,27 @@ function HelloComponent() {
128128
module.exports = HelloComponent();
129129
```
130130

131+
### checkContextObjects
132+
133+
`displayName` allows you to [name your context](https://reactjs.org/docs/context.html#contextdisplayname) object. This name is used in the React dev tools for the context's `Provider` and `Consumer`.
134+
When `true` this rule will warn on context objects without a `displayName`.
135+
136+
## Rule Details
137+
138+
Examples of **incorrect** code for this rule:
139+
140+
```jsx
141+
const Hello = React.createContext();
142+
```
143+
144+
Examples of **correct** code for this rule:
145+
146+
```jsx
147+
const Hello = React.createContext();
148+
Hello.displayName = "HelloContext";
149+
```
150+
151+
131152
## About component detection
132153

133154
For this rule to work we need to detect React components, this could be very hard since components could be declared in a lot of ways.

lib/rules/context-display-name.js

Lines changed: 0 additions & 110 deletions
This file was deleted.

lib/rules/display-name.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const report = require('../util/report');
2121

2222
const messages = {
2323
noDisplayName: 'Component definition is missing display name',
24+
noContextDisplayName: 'Context definition is missing display name',
2425
};
2526

2627
module.exports = {
@@ -40,6 +41,9 @@ module.exports = {
4041
ignoreTranspilerName: {
4142
type: 'boolean',
4243
},
44+
checkContextObjects: {
45+
type: 'boolean',
46+
},
4347
},
4448
additionalProperties: false,
4549
}],
@@ -48,6 +52,9 @@ module.exports = {
4852
create: Components.detect((context, components, utils) => {
4953
const config = context.options[0] || {};
5054
const ignoreTranspilerName = config.ignoreTranspilerName || false;
55+
const checkContextObjects = config.checkContextObjects || false;
56+
57+
const contextObjects = new Map();
5158

5259
/**
5360
* Mark a prop type as declared
@@ -70,6 +77,36 @@ module.exports = {
7077
return node.type === 'CallExpression' && argumentIsCallExpression && utils.isPragmaComponentWrapper(node);
7178
}
7279

80+
/**
81+
* Checks if the node is a createContext call
82+
* @param {ASTNode} node - The AST node being checked.
83+
* @returns {Boolean} - True if node is a createContext call object literal, False if not.
84+
*/
85+
function isCreateContext(node) {
86+
if (!node.init) {
87+
return false;
88+
}
89+
90+
if (
91+
node.init.type === 'CallExpression' &&
92+
node.init.callee &&
93+
node.init.callee.name === 'createContext'
94+
) {
95+
return true;
96+
}
97+
98+
if (
99+
node.init.callee &&
100+
node.init.callee.type === 'MemberExpression' &&
101+
node.init.callee.property &&
102+
node.init.callee.property.name === 'createContext'
103+
) {
104+
return true;
105+
}
106+
107+
return false;
108+
}
109+
73110
/**
74111
* Reports missing display name for a given component
75112
* @param {Object} component The component to process
@@ -87,6 +124,18 @@ module.exports = {
87124
});
88125
}
89126

127+
/**
128+
* Reports missing display name for a given context object
129+
* @param {Object} contextObj The context object to process
130+
*/
131+
function reportMissingContextDisplayName(contextObj) {
132+
if (testReactVersion(context, '>= 16.3.0')) {
133+
report(context, messages.noContextDisplayName, 'noContextDisplayName', {
134+
node: contextObj.node,
135+
});
136+
}
137+
}
138+
90139
/**
91140
* Checks if the component have a name set by the transpiler
92141
* @param {ASTNode} node The AST node being checked.
@@ -144,6 +193,11 @@ module.exports = {
144193
// --------------------------------------------------------------------------
145194

146195
return {
196+
VariableDeclarator(node) {
197+
if (checkContextObjects && isCreateContext(node)) {
198+
contextObjects.set(node.id.name, { node, hasDisplayName: false });
199+
}
200+
},
147201
'ClassProperty, PropertyDefinition'(node) {
148202
if (!propsUtil.isDisplayNameDeclaration(node)) {
149203
return;
@@ -155,6 +209,14 @@ module.exports = {
155209
if (!propsUtil.isDisplayNameDeclaration(node.property)) {
156210
return;
157211
}
212+
if (
213+
checkContextObjects &&
214+
node.object &&
215+
node.object.name &&
216+
contextObjects.has(node.object.name)
217+
) {
218+
contextObjects.get(node.object.name).hasDisplayName = true;
219+
}
158220
const component = utils.getRelatedComponent(node);
159221
if (!component) {
160222
return;
@@ -258,6 +320,11 @@ module.exports = {
258320
values(list).filter((component) => !component.hasDisplayName).forEach((component) => {
259321
reportMissingDisplayName(component);
260322
});
323+
// Report missing display name for all context objects
324+
const contextsList = Array.from(contextObjects.values()).filter(
325+
(v) => !v.hasDisplayName
326+
);
327+
contextsList.forEach((contextObj) => reportMissingContextDisplayName(contextObj));
261328
},
262329
};
263330
}),

lib/rules/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
module.exports = {
66
'boolean-prop-naming': require('./boolean-prop-naming'),
77
'button-has-type': require('./button-has-type'),
8-
'context-display-name': require('./context-display-name'),
98
'default-props-match-prop-types': require('./default-props-match-prop-types'),
109
'destructuring-assignment': require('./destructuring-assignment'),
1110
'display-name': require('./display-name'),

0 commit comments

Comments
 (0)