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

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 8.9 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.
[0c6b92a]76 * @returns {boolean} True if React.forwardRef is nested inside React.memo, false if not.
[d565449]77 */
78 function isNestedMemo(node) {
[0c6b92a]79 return astUtil.isCallExpression(node)
80 && node.arguments
81 && astUtil.isCallExpression(node.arguments[0])
82 && utils.isPragmaComponentWrapper(node);
[d565449]83 }
84
85 /**
86 * Reports missing display name for a given component
87 * @param {Object} component The component to process
88 */
89 function reportMissingDisplayName(component) {
90 if (
91 testReactVersion(context, '^0.14.10 || ^15.7.0 || >= 16.12.0')
92 && isNestedMemo(component.node)
93 ) {
94 return;
95 }
96
97 report(context, messages.noDisplayName, 'noDisplayName', {
98 node: component.node,
99 });
100 }
101
102 /**
103 * Reports missing display name for a given context object
104 * @param {Object} contextObj The context object to process
105 */
106 function reportMissingContextDisplayName(contextObj) {
107 report(context, messages.noContextDisplayName, 'noContextDisplayName', {
108 node: contextObj.node,
109 });
110 }
111
112 /**
113 * Checks if the component have a name set by the transpiler
114 * @param {ASTNode} node The AST node being checked.
[0c6b92a]115 * @returns {boolean} True if component has a name, false if not.
[d565449]116 */
117 function hasTranspilerName(node) {
118 const namedObjectAssignment = (
119 node.type === 'ObjectExpression'
120 && node.parent
121 && node.parent.parent
122 && node.parent.parent.type === 'AssignmentExpression'
123 && (
124 !node.parent.parent.left.object
125 || node.parent.parent.left.object.name !== 'module'
126 || node.parent.parent.left.property.name !== 'exports'
127 )
128 );
129 const namedObjectDeclaration = (
130 node.type === 'ObjectExpression'
131 && node.parent
132 && node.parent.parent
133 && node.parent.parent.type === 'VariableDeclarator'
134 );
135 const namedClass = (
136 (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')
137 && node.id
138 && !!node.id.name
139 );
140
141 const namedFunctionDeclaration = (
142 (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')
143 && node.id
144 && !!node.id.name
145 );
146
147 const namedFunctionExpression = (
148 astUtil.isFunctionLikeExpression(node)
149 && node.parent
150 && (node.parent.type === 'VariableDeclarator' || node.parent.type === 'Property' || node.parent.method === true)
151 && (!node.parent.parent || !componentUtil.isES5Component(node.parent.parent, context))
152 );
153
154 if (
155 namedObjectAssignment || namedObjectDeclaration
156 || namedClass
157 || namedFunctionDeclaration || namedFunctionExpression
158 ) {
159 return true;
160 }
161 return false;
162 }
163
164 // --------------------------------------------------------------------------
165 // Public
166 // --------------------------------------------------------------------------
167
168 return {
169 ExpressionStatement(node) {
170 if (checkContextObjects && isCreateContext(node)) {
171 contextObjects.set(node.expression.left.name, { node, hasDisplayName: false });
172 }
173 },
174 VariableDeclarator(node) {
175 if (checkContextObjects && isCreateContext(node)) {
176 contextObjects.set(node.id.name, { node, hasDisplayName: false });
177 }
178 },
179 'ClassProperty, PropertyDefinition'(node) {
180 if (!propsUtil.isDisplayNameDeclaration(node)) {
181 return;
182 }
183 markDisplayNameAsDeclared(node);
184 },
185
186 MemberExpression(node) {
187 if (!propsUtil.isDisplayNameDeclaration(node.property)) {
188 return;
189 }
190 if (
191 checkContextObjects
192 && node.object
193 && node.object.name
194 && contextObjects.has(node.object.name)
195 ) {
196 contextObjects.get(node.object.name).hasDisplayName = true;
197 }
198 const component = utils.getRelatedComponent(node);
199 if (!component) {
200 return;
201 }
[0c6b92a]202 markDisplayNameAsDeclared(astUtil.unwrapTSAsExpression(component.node));
[d565449]203 },
204
205 'FunctionExpression, FunctionDeclaration, ArrowFunctionExpression'(node) {
206 if (ignoreTranspilerName || !hasTranspilerName(node)) {
207 return;
208 }
209 if (components.get(node)) {
210 markDisplayNameAsDeclared(node);
211 }
212 },
213
214 MethodDefinition(node) {
215 if (!propsUtil.isDisplayNameDeclaration(node.key)) {
216 return;
217 }
218 markDisplayNameAsDeclared(node);
219 },
220
221 'ClassExpression, ClassDeclaration'(node) {
222 if (ignoreTranspilerName || !hasTranspilerName(node)) {
223 return;
224 }
225 markDisplayNameAsDeclared(node);
226 },
227
228 ObjectExpression(node) {
229 if (!componentUtil.isES5Component(node, context)) {
230 return;
231 }
232 if (ignoreTranspilerName || !hasTranspilerName(node)) {
233 // Search for the displayName declaration
234 node.properties.forEach((property) => {
235 if (!property.key || !propsUtil.isDisplayNameDeclaration(property.key)) {
236 return;
237 }
238 markDisplayNameAsDeclared(node);
239 });
240 return;
241 }
242 markDisplayNameAsDeclared(node);
243 },
244
245 CallExpression(node) {
246 if (!utils.isPragmaComponentWrapper(node)) {
247 return;
248 }
249
250 if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) {
251 // Skip over React.forwardRef declarations that are embedded within
252 // a React.memo i.e. React.memo(React.forwardRef(/* ... */))
253 // This means that we raise a single error for the call to React.memo
254 // instead of one for React.memo and one for React.forwardRef
255 const isWrappedInAnotherPragma = utils.getPragmaComponentWrapper(node);
256 if (
257 !isWrappedInAnotherPragma
258 && (ignoreTranspilerName || !hasTranspilerName(node.arguments[0]))
259 ) {
260 return;
261 }
262
263 if (components.get(node)) {
264 markDisplayNameAsDeclared(node);
265 }
266 }
267 },
268
269 'Program:exit'() {
270 const list = components.list();
271 // Report missing display name for all components
272 values(list).filter((component) => !component.hasDisplayName).forEach((component) => {
273 reportMissingDisplayName(component);
274 });
275 if (checkContextObjects) {
276 // Report missing display name for all context objects
277 forEach(
278 filter(contextObjects.values(), (v) => !v.hasDisplayName),
279 (contextObj) => reportMissingContextDisplayName(contextObj)
280 );
281 }
282 },
283 };
284 }),
285};
Note: See TracBrowser for help on using the repository browser.