Skip to content

Commit

Permalink
[New] display-name: add checkContextObjects option
Browse files Browse the repository at this point in the history
  • Loading branch information
JulesBlm authored and ljharb committed Feb 9, 2023
1 parent abb4871 commit a684ae1
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,10 +5,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange

## Unreleased

### Added
* [`display-name`]: add `checkContextObjects` option ([#3529][] @JulesBlm)

### Fixed
* [`no-array-index-key`]: consider flatMap ([#3530][] @k-yle)

[#3530]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3530
[#3529]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3529

## [7.32.2] - 2023.01.28

Expand Down
29 changes: 28 additions & 1 deletion docs/rules/display-name.md
Expand Up @@ -45,7 +45,7 @@ const Hello = React.memo(function Hello({ a }) {

```js
...
"react/display-name": [<enabled>, { "ignoreTranspilerName": <boolean> }]
"react/display-name": [<enabled>, { "ignoreTranspilerName": <boolean>, "checkContextObjects": <boolean> }]
...
```

Expand Down Expand Up @@ -128,6 +128,33 @@ function HelloComponent() {
module.exports = HelloComponent();
```

### checkContextObjects (default: `false`)

`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`.
When `true` this rule will warn on context objects without a `displayName`.

Examples of **incorrect** code for this rule:

```jsx
const Hello = React.createContext();
```

```jsx
const Hello = createContext();
```

Examples of **correct** code for this rule:

```jsx
const Hello = React.createContext();
Hello.displayName = "HelloContext";
```

```jsx
const Hello = createContext();
Hello.displayName = "HelloContext";
```

## About component detection

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.
Expand Down
41 changes: 41 additions & 0 deletions lib/rules/display-name.js
Expand Up @@ -8,6 +8,7 @@
const values = require('object.values');

const Components = require('../util/Components');
const isCreateContext = require('../util/isCreateContext');
const astUtil = require('../util/ast');
const componentUtil = require('../util/componentUtil');
const docsUrl = require('../util/docsUrl');
Expand All @@ -21,6 +22,7 @@ const report = require('../util/report');

const messages = {
noDisplayName: 'Component definition is missing display name',
noContextDisplayName: 'Context definition is missing display name',
};

module.exports = {
Expand All @@ -40,6 +42,9 @@ module.exports = {
ignoreTranspilerName: {
type: 'boolean',
},
checkContextObjects: {
type: 'boolean',
},
},
additionalProperties: false,
}],
Expand All @@ -48,6 +53,9 @@ module.exports = {
create: Components.detect((context, components, utils) => {
const config = context.options[0] || {};
const ignoreTranspilerName = config.ignoreTranspilerName || false;
const checkContextObjects = (config.checkContextObjects || false) && testReactVersion(context, '>= 16.3.0');

const contextObjects = new Map();

/**
* Mark a prop type as declared
Expand Down Expand Up @@ -87,6 +95,16 @@ module.exports = {
});
}

/**
* Reports missing display name for a given context object
* @param {Object} contextObj The context object to process
*/
function reportMissingContextDisplayName(contextObj) {
report(context, messages.noContextDisplayName, 'noContextDisplayName', {
node: contextObj.node,
});
}

/**
* Checks if the component have a name set by the transpiler
* @param {ASTNode} node The AST node being checked.
Expand Down Expand Up @@ -144,6 +162,16 @@ module.exports = {
// --------------------------------------------------------------------------

return {
ExpressionStatement(node) {
if (checkContextObjects && isCreateContext(node)) {
contextObjects.set(node.expression.left.name, { node, hasDisplayName: false });
}
},
VariableDeclarator(node) {
if (checkContextObjects && isCreateContext(node)) {
contextObjects.set(node.id.name, { node, hasDisplayName: false });
}
},
'ClassProperty, PropertyDefinition'(node) {
if (!propsUtil.isDisplayNameDeclaration(node)) {
return;
Expand All @@ -155,6 +183,14 @@ module.exports = {
if (!propsUtil.isDisplayNameDeclaration(node.property)) {
return;
}
if (
checkContextObjects
&& node.object
&& node.object.name
&& contextObjects.has(node.object.name)
) {
contextObjects.get(node.object.name).hasDisplayName = true;
}
const component = utils.getRelatedComponent(node);
if (!component) {
return;
Expand Down Expand Up @@ -258,6 +294,11 @@ module.exports = {
values(list).filter((component) => !component.hasDisplayName).forEach((component) => {
reportMissingDisplayName(component);
});
if (checkContextObjects) {
// Report missing display name for all context objects
const contextsList = Array.from(contextObjects.values()).filter((v) => !v.hasDisplayName);
contextsList.forEach((contextObj) => reportMissingContextDisplayName(contextObj));
}
},
};
}),
Expand Down
53 changes: 53 additions & 0 deletions lib/util/isCreateContext.js
@@ -0,0 +1,53 @@
'use strict';

/**
* Checks if the node is a React.createContext call
* @param {ASTNode} node - The AST node being checked.
* @returns {Boolean} - True if node is a React.createContext call, false if not.
*/
module.exports = function isCreateContext(node) {
if (
node.init
&& node.init.type === 'CallExpression'
&& node.init.callee
&& node.init.callee.name === 'createContext'
) {
return true;
}

if (
node.init
&& node.init.callee
&& node.init.callee.type === 'MemberExpression'
&& node.init.callee.property
&& node.init.callee.property.name === 'createContext'
) {
return true;
}

if (
node.expression
&& node.expression.type === 'AssignmentExpression'
&& node.expression.operator === '='
&& node.expression.right.type === 'CallExpression'
&& node.expression.right.callee
&& node.expression.right.callee.name === 'createContext'
) {
return true;
}

if (
node.expression
&& node.expression.type === 'AssignmentExpression'
&& node.expression.operator === '='
&& node.expression.right.type === 'CallExpression'
&& node.expression.right.callee
&& node.expression.right.callee.type === 'MemberExpression'
&& node.expression.right.callee.property
&& node.expression.right.callee.property.name === 'createContext'
) {
return true;
}

return false;
};

0 comments on commit a684ae1

Please sign in to comment.