Skip to content

Commit f25a8ec

Browse files
committed
[Refactor] create/extract isCreateElement and isDestructuredFromPragmaImport utils
This should improve detection in the following rules: - `button-has-type` - `forbid-elements` - `no-adjacent-inline-elements` - `no-children-prop` - `style-prop-object`
1 parent 76fdffe commit f25a8ec

9 files changed

+131
-121
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
1212

1313
### Changed
1414
* [readme] Update broken link for configuration files ([#3071] @prateek3255)
15+
* [Refactor] create/extract `isCreateElement` and `isDestructuredFromPragmaImport` utils (@ljharb)
1516

1617
[7.25.3]: https://github.com/yannickcr/eslint-plugin-react/compare/v7.25.2...v7.25.3
1718
[#3076]: https://github.com/yannickcr/eslint-plugin-react/pull/3076

lib/rules/button-has-type.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,7 @@
88
const getProp = require('jsx-ast-utils/getProp');
99
const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue');
1010
const docsUrl = require('../util/docsUrl');
11-
const pragmaUtil = require('../util/pragma');
12-
13-
// ------------------------------------------------------------------------------
14-
// Helpers
15-
// ------------------------------------------------------------------------------
16-
17-
function isCreateElement(node, context) {
18-
const pragma = pragmaUtil.getFromContext(context);
19-
return node.callee
20-
&& node.callee.type === 'MemberExpression'
21-
&& node.callee.property.name === 'createElement'
22-
&& node.callee.object
23-
&& node.callee.object.name === pragma
24-
&& node.arguments.length > 0;
25-
}
11+
const isCreateElement = require('../util/isCreateElement');
2612

2713
// ------------------------------------------------------------------------------
2814
// Rule Definition
@@ -150,7 +136,7 @@ module.exports = {
150136
checkValue(node, propValue);
151137
},
152138
CallExpression(node) {
153-
if (!isCreateElement(node, context)) {
139+
if (!isCreateElement(node, context) || node.arguments.length < 1) {
154140
return;
155141
}
156142

lib/rules/forbid-elements.js

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
const has = require('object.hasown/polyfill')();
99
const docsUrl = require('../util/docsUrl');
10+
const isCreateElement = require('../util/isCreateElement');
1011

1112
// ------------------------------------------------------------------------------
1213
// Rule Definition
@@ -65,14 +66,6 @@ module.exports = {
6566
}
6667
});
6768

68-
function isValidCreateElement(node) {
69-
return node.callee
70-
&& node.callee.type === 'MemberExpression'
71-
&& node.callee.object.name === 'React'
72-
&& node.callee.property.name === 'createElement'
73-
&& node.arguments.length > 0;
74-
}
75-
7669
function reportIfForbidden(element, node) {
7770
if (has(indexedForbidConfigs, element)) {
7871
const message = indexedForbidConfigs[element].message;
@@ -94,7 +87,7 @@ module.exports = {
9487
},
9588

9689
CallExpression(node) {
97-
if (!isValidCreateElement(node)) {
90+
if (!isCreateElement(node, context)) {
9891
return;
9992
}
10093

lib/rules/no-adjacent-inline-elements.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'use strict';
77

88
const docsUrl = require('../util/docsUrl');
9+
const isCreateElement = require('../util/isCreateElement');
910

1011
// ------------------------------------------------------------------------------
1112
// Helpers
@@ -108,7 +109,7 @@ module.exports = {
108109
validate(node, node.children);
109110
},
110111
CallExpression(node) {
111-
if (!node.callee || node.callee.type !== 'MemberExpression' || node.callee.property.name !== 'createElement') {
112+
if (!isCreateElement(node, context)) {
112113
return;
113114
}
114115
if (node.arguments.length < 2 || !node.arguments[2]) {

lib/rules/no-children-prop.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'use strict';
77

88
const docsUrl = require('../util/docsUrl');
9+
const isCreateElement = require('../util/isCreateElement');
910

1011
// ------------------------------------------------------------------------------
1112
// Helpers
@@ -14,13 +15,12 @@ const docsUrl = require('../util/docsUrl');
1415
/**
1516
* Checks if the node is a createElement call with a props literal.
1617
* @param {ASTNode} node - The AST node being checked.
18+
* @param {Context} context - The AST node being checked.
1719
* @returns {Boolean} - True if node is a createElement call with a props
1820
* object literal, False if not.
1921
*/
20-
function isCreateElementWithProps(node) {
21-
return node.callee
22-
&& node.callee.type === 'MemberExpression'
23-
&& node.callee.property.name === 'createElement'
22+
function isCreateElementWithProps(node, context) {
23+
return isCreateElement(node, context)
2424
&& node.arguments.length > 1
2525
&& node.arguments[1].type === 'ObjectExpression';
2626
}
@@ -80,7 +80,7 @@ module.exports = {
8080
});
8181
},
8282
CallExpression(node) {
83-
if (!isCreateElementWithProps(node)) {
83+
if (!isCreateElementWithProps(node, context)) {
8484
return;
8585
}
8686

lib/rules/style-prop-object.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
const variableUtil = require('../util/variable');
99
const docsUrl = require('../util/docsUrl');
10+
const isCreateElement = require('../util/isCreateElement');
1011

1112
// ------------------------------------------------------------------------------
1213
// Rule Definition
@@ -74,9 +75,7 @@ module.exports = {
7475
return {
7576
CallExpression(node) {
7677
if (
77-
node.callee
78-
&& node.callee.type === 'MemberExpression'
79-
&& node.callee.property.name === 'createElement'
78+
isCreateElement(node, context)
8079
&& node.arguments.length > 1
8180
) {
8281
if (node.arguments[0].name) {

lib/util/Components.js

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const jsxUtil = require('./jsx');
1717
const usedPropTypesUtil = require('./usedPropTypes');
1818
const defaultPropsUtil = require('./defaultProps');
1919
const isFirstLetterCapitalized = require('./isFirstLetterCapitalized');
20+
const isCreateElement = require('./isCreateElement');
21+
const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport');
2022

2123
function getId(node) {
2224
return node && node.range.join(':');
@@ -287,70 +289,7 @@ function componentRule(rule, context) {
287289
* @returns {Boolean} True if createElement is destructured from the pragma
288290
*/
289291
isDestructuredFromPragmaImport(variable) {
290-
const variables = variableUtil.variablesInScope(context);
291-
const variableInScope = variableUtil.getVariable(variables, variable);
292-
if (variableInScope) {
293-
const latestDef = variableUtil.getLatestVariableDefinition(variableInScope);
294-
if (latestDef) {
295-
// check if latest definition is a variable declaration: 'variable = value'
296-
if (latestDef.node.type === 'VariableDeclarator' && latestDef.node.init) {
297-
// check for: 'variable = pragma.variable'
298-
if (
299-
latestDef.node.init.type === 'MemberExpression'
300-
&& latestDef.node.init.object.type === 'Identifier'
301-
&& latestDef.node.init.object.name === pragma
302-
) {
303-
return true;
304-
}
305-
// check for: '{variable} = pragma'
306-
if (
307-
latestDef.node.init.type === 'Identifier'
308-
&& latestDef.node.init.name === pragma
309-
) {
310-
return true;
311-
}
312-
313-
// "require('react')"
314-
let requireExpression = null;
315-
316-
// get "require('react')" from: "{variable} = require('react')"
317-
if (latestDef.node.init.type === 'CallExpression') {
318-
requireExpression = latestDef.node.init;
319-
}
320-
// get "require('react')" from: "variable = require('react').variable"
321-
if (
322-
!requireExpression
323-
&& latestDef.node.init.type === 'MemberExpression'
324-
&& latestDef.node.init.object.type === 'CallExpression'
325-
) {
326-
requireExpression = latestDef.node.init.object;
327-
}
328-
329-
// check proper require.
330-
if (
331-
requireExpression
332-
&& requireExpression.callee
333-
&& requireExpression.callee.name === 'require'
334-
&& requireExpression.arguments[0]
335-
&& requireExpression.arguments[0].value === pragma.toLocaleLowerCase()
336-
) {
337-
return true;
338-
}
339-
340-
return false;
341-
}
342-
343-
// latest definition is an import declaration: import {<variable>} from 'react'
344-
if (
345-
latestDef.parent
346-
&& latestDef.parent.type === 'ImportDeclaration'
347-
&& latestDef.parent.source.value === pragma.toLocaleLowerCase()
348-
) {
349-
return true;
350-
}
351-
}
352-
}
353-
return false;
292+
return isDestructuredFromPragmaImport(variable, context);
354293
},
355294

356295
/**
@@ -360,29 +299,7 @@ function componentRule(rule, context) {
360299
* @returns {Boolean} True if createElement called from pragma
361300
*/
362301
isCreateElement(node) {
363-
// match `React.createElement()`
364-
if (
365-
node
366-
&& node.callee
367-
&& node.callee.object
368-
&& node.callee.object.name === pragma
369-
&& node.callee.property
370-
&& node.callee.property.name === 'createElement'
371-
) {
372-
return true;
373-
}
374-
375-
// match `createElement()`
376-
if (
377-
node
378-
&& node.callee
379-
&& node.callee.name === 'createElement'
380-
&& this.isDestructuredFromPragmaImport('createElement')
381-
) {
382-
return true;
383-
}
384-
385-
return false;
302+
return isCreateElement(node, context);
386303
},
387304

388305
/**

lib/util/isCreateElement.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
const pragmaUtil = require('./pragma');
4+
const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport');
5+
6+
/**
7+
* Checks if the node is a createElement call
8+
* @param {ASTNode} node - The AST node being checked.
9+
* @param {Context} context - The AST node being checked.
10+
* @returns {Boolean} - True if node is a createElement call object literal, False if not.
11+
*/
12+
module.exports = function isCreateElement(node, context) {
13+
const pragma = pragmaUtil.getFromContext(context);
14+
if (
15+
node.callee
16+
&& node.callee.type === 'MemberExpression'
17+
&& node.callee.property.name === 'createElement'
18+
&& node.callee.object
19+
&& node.callee.object.name === pragma
20+
) {
21+
return true;
22+
}
23+
24+
if (
25+
node
26+
&& node.callee
27+
&& node.callee.name === 'createElement'
28+
&& isDestructuredFromPragmaImport('createElement', context)
29+
) {
30+
return true;
31+
}
32+
33+
return false;
34+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use strict';
2+
3+
const pragmaUtil = require('./pragma');
4+
const variableUtil = require('./variable');
5+
6+
/**
7+
* Check if variable is destructured from pragma import
8+
*
9+
* @param {string} variable The variable name to check
10+
* @param {Context} context eslint context
11+
* @returns {Boolean} True if createElement is destructured from the pragma
12+
*/
13+
module.exports = function isDestructuredFromPragmaImport(variable, context) {
14+
const pragma = pragmaUtil.getFromContext(context);
15+
const variables = variableUtil.variablesInScope(context);
16+
const variableInScope = variableUtil.getVariable(variables, variable);
17+
if (variableInScope) {
18+
const latestDef = variableUtil.getLatestVariableDefinition(variableInScope);
19+
if (latestDef) {
20+
// check if latest definition is a variable declaration: 'variable = value'
21+
if (latestDef.node.type === 'VariableDeclarator' && latestDef.node.init) {
22+
// check for: 'variable = pragma.variable'
23+
if (
24+
latestDef.node.init.type === 'MemberExpression'
25+
&& latestDef.node.init.object.type === 'Identifier'
26+
&& latestDef.node.init.object.name === pragma
27+
) {
28+
return true;
29+
}
30+
// check for: '{variable} = pragma'
31+
if (
32+
latestDef.node.init.type === 'Identifier'
33+
&& latestDef.node.init.name === pragma
34+
) {
35+
return true;
36+
}
37+
38+
// "require('react')"
39+
let requireExpression = null;
40+
41+
// get "require('react')" from: "{variable} = require('react')"
42+
if (latestDef.node.init.type === 'CallExpression') {
43+
requireExpression = latestDef.node.init;
44+
}
45+
// get "require('react')" from: "variable = require('react').variable"
46+
if (
47+
!requireExpression
48+
&& latestDef.node.init.type === 'MemberExpression'
49+
&& latestDef.node.init.object.type === 'CallExpression'
50+
) {
51+
requireExpression = latestDef.node.init.object;
52+
}
53+
54+
// check proper require.
55+
if (
56+
requireExpression
57+
&& requireExpression.callee
58+
&& requireExpression.callee.name === 'require'
59+
&& requireExpression.arguments[0]
60+
&& requireExpression.arguments[0].value === pragma.toLocaleLowerCase()
61+
) {
62+
return true;
63+
}
64+
65+
return false;
66+
}
67+
68+
// latest definition is an import declaration: import {<variable>} from 'react'
69+
if (
70+
latestDef.parent
71+
&& latestDef.parent.type === 'ImportDeclaration'
72+
&& latestDef.parent.source.value === pragma.toLocaleLowerCase()
73+
) {
74+
return true;
75+
}
76+
}
77+
}
78+
return false;
79+
};

0 commit comments

Comments
 (0)