source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/display-name.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 9.0 KB
RevLine 
[d565449]1/**
2 * @fileoverview Prevent missing displayName in a React component definition
3 * @author Yannick Croissant
4 */
5
6'use strict';
7
8const values = require('object.values');
9const filter = require('es-iterator-helpers/Iterator.prototype.filter');
10const forEach = require('es-iterator-helpers/Iterator.prototype.forEach');
11
12const Components = require('../util/Components');
13const isCreateContext = require('../util/isCreateContext');
14const astUtil = require('../util/ast');
15const componentUtil = require('../util/componentUtil');
16const docsUrl = require('../util/docsUrl');
17const testReactVersion = require('../util/version').testReactVersion;
18const propsUtil = require('../util/props');
19const report = require('../util/report');
20
21// ------------------------------------------------------------------------------
22// Rule Definition
23// ------------------------------------------------------------------------------
24
25const messages = {
26 noDisplayName: 'Component definition is missing display name',
27 noContextDisplayName: 'Context definition is missing display name',
28};
29
30/** @type {import('eslint').Rule.RuleModule} */
31module.exports = {
32 meta: {
33 docs: {
34 description: 'Disallow missing displayName in a React component definition',
35 category: 'Best Practices',
36 recommended: true,
37 url: docsUrl('display-name'),
38 },
39
40 messages,
41
42 schema: [{
43 type: 'object',
44 properties: {
45 ignoreTranspilerName: {
46 type: 'boolean',
47 },
48 checkContextObjects: {
49 type: 'boolean',
50 },
51 },
52 additionalProperties: false,
53 }],
54 },
55
56 create: Components.detect((context, components, utils) => {
57 const config = context.options[0] || {};
58 const ignoreTranspilerName = config.ignoreTranspilerName || false;
59 const checkContextObjects = (config.checkContextObjects || false) && testReactVersion(context, '>= 16.3.0');
60
61 const contextObjects = new Map();
62
63 /**
64 * Mark a prop type as declared
65 * @param {ASTNode} node The AST node being checked.
66 */
67 function markDisplayNameAsDeclared(node) {
68 components.set(node, {
69 hasDisplayName: true,
70 });
71 }
72
73 /**
74 * Checks if React.forwardRef is nested inside React.memo
75 * @param {ASTNode} node The AST node being checked.
76 * @returns {Boolean} True if React.forwardRef is nested inside React.memo, false if not.
77 */
78 function isNestedMemo(node) {
79 const argumentIsCallExpression = node.arguments && node.arguments[0] && node.arguments[0].type === 'CallExpression';
80
81 return node.type === 'CallExpression' && argumentIsCallExpression && utils.isPragmaComponentWrapper(node);
82 }
83
84 /**
85 * Reports missing display name for a given component
86 * @param {Object} component The component to process
87 */
88 function reportMissingDisplayName(component) {
89 if (
90 testReactVersion(context, '^0.14.10 || ^15.7.0 || >= 16.12.0')
91 && isNestedMemo(component.node)
92 ) {
93 return;
94 }
95
96 report(context, messages.noDisplayName, 'noDisplayName', {
97 node: component.node,
98 });
99 }
100
101 /**
102 * Reports missing display name for a given context object
103 * @param {Object} contextObj The context object to process
104 */
105 function reportMissingContextDisplayName(contextObj) {
106 report(context, messages.noContextDisplayName, 'noContextDisplayName', {
107 node: contextObj.node,
108 });
109 }
110
111 /**
112 * Checks if the component have a name set by the transpiler
113 * @param {ASTNode} node The AST node being checked.
114 * @returns {Boolean} True if component has a name, false if not.
115 */
116 function hasTranspilerName(node) {
117 const namedObjectAssignment = (
118 node.type === 'ObjectExpression'
119 && node.parent
120 && node.parent.parent
121 && node.parent.parent.type === 'AssignmentExpression'
122 && (
123 !node.parent.parent.left.object
124 || node.parent.parent.left.object.name !== 'module'
125 || node.parent.parent.left.property.name !== 'exports'
126 )
127 );
128 const namedObjectDeclaration = (
129 node.type === 'ObjectExpression'
130 && node.parent
131 && node.parent.parent
132 && node.parent.parent.type === 'VariableDeclarator'
133 );
134 const namedClass = (
135 (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')
136 && node.id
137 && !!node.id.name
138 );
139
140 const namedFunctionDeclaration = (
141 (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')
142 && node.id
143 && !!node.id.name
144 );
145
146 const namedFunctionExpression = (
147 astUtil.isFunctionLikeExpression(node)
148 && node.parent
149 && (node.parent.type === 'VariableDeclarator' || node.parent.type === 'Property' || node.parent.method === true)
150 && (!node.parent.parent || !componentUtil.isES5Component(node.parent.parent, context))
151 );
152
153 if (
154 namedObjectAssignment || namedObjectDeclaration
155 || namedClass
156 || namedFunctionDeclaration || namedFunctionExpression
157 ) {
158 return true;
159 }
160 return false;
161 }
162
163 // --------------------------------------------------------------------------
164 // Public
165 // --------------------------------------------------------------------------
166
167 return {
168 ExpressionStatement(node) {
169 if (checkContextObjects && isCreateContext(node)) {
170 contextObjects.set(node.expression.left.name, { node, hasDisplayName: false });
171 }
172 },
173 VariableDeclarator(node) {
174 if (checkContextObjects && isCreateContext(node)) {
175 contextObjects.set(node.id.name, { node, hasDisplayName: false });
176 }
177 },
178 'ClassProperty, PropertyDefinition'(node) {
179 if (!propsUtil.isDisplayNameDeclaration(node)) {
180 return;
181 }
182 markDisplayNameAsDeclared(node);
183 },
184
185 MemberExpression(node) {
186 if (!propsUtil.isDisplayNameDeclaration(node.property)) {
187 return;
188 }
189 if (
190 checkContextObjects
191 && node.object
192 && node.object.name
193 && contextObjects.has(node.object.name)
194 ) {
195 contextObjects.get(node.object.name).hasDisplayName = true;
196 }
197 const component = utils.getRelatedComponent(node);
198 if (!component) {
199 return;
200 }
201 markDisplayNameAsDeclared(component.node.type === 'TSAsExpression' ? component.node.expression : component.node);
202 },
203
204 'FunctionExpression, FunctionDeclaration, ArrowFunctionExpression'(node) {
205 if (ignoreTranspilerName || !hasTranspilerName(node)) {
206 return;
207 }
208 if (components.get(node)) {
209 markDisplayNameAsDeclared(node);
210 }
211 },
212
213 MethodDefinition(node) {
214 if (!propsUtil.isDisplayNameDeclaration(node.key)) {
215 return;
216 }
217 markDisplayNameAsDeclared(node);
218 },
219
220 'ClassExpression, ClassDeclaration'(node) {
221 if (ignoreTranspilerName || !hasTranspilerName(node)) {
222 return;
223 }
224 markDisplayNameAsDeclared(node);
225 },
226
227 ObjectExpression(node) {
228 if (!componentUtil.isES5Component(node, context)) {
229 return;
230 }
231 if (ignoreTranspilerName || !hasTranspilerName(node)) {
232 // Search for the displayName declaration
233 node.properties.forEach((property) => {
234 if (!property.key || !propsUtil.isDisplayNameDeclaration(property.key)) {
235 return;
236 }
237 markDisplayNameAsDeclared(node);
238 });
239 return;
240 }
241 markDisplayNameAsDeclared(node);
242 },
243
244 CallExpression(node) {
245 if (!utils.isPragmaComponentWrapper(node)) {
246 return;
247 }
248
249 if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) {
250 // Skip over React.forwardRef declarations that are embedded within
251 // a React.memo i.e. React.memo(React.forwardRef(/* ... */))
252 // This means that we raise a single error for the call to React.memo
253 // instead of one for React.memo and one for React.forwardRef
254 const isWrappedInAnotherPragma = utils.getPragmaComponentWrapper(node);
255 if (
256 !isWrappedInAnotherPragma
257 && (ignoreTranspilerName || !hasTranspilerName(node.arguments[0]))
258 ) {
259 return;
260 }
261
262 if (components.get(node)) {
263 markDisplayNameAsDeclared(node);
264 }
265 }
266 },
267
268 'Program:exit'() {
269 const list = components.list();
270 // Report missing display name for all components
271 values(list).filter((component) => !component.hasDisplayName).forEach((component) => {
272 reportMissingDisplayName(component);
273 });
274 if (checkContextObjects) {
275 // Report missing display name for all context objects
276 forEach(
277 filter(contextObjects.values(), (v) => !v.hasDisplayName),
278 (contextObj) => reportMissingContextDisplayName(contextObj)
279 );
280 }
281 },
282 };
283 }),
284};
Note: See TracBrowser for help on using the repository browser.