-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New] add
no-unused-class-component-methods
Co-authored-by: meowtec <[email protected]> Co-authored-by: Jake Leventhal <[email protected]>
- Loading branch information
Showing
6 changed files
with
1,090 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Prevent declaring unused methods of component class (react/no-unused-class-component-methods) | ||
|
||
Warns you if you have defined a method or property but it is never being used anywhere. | ||
|
||
## Rule Details | ||
|
||
The following patterns are considered warnings: | ||
|
||
```jsx | ||
class Foo extends React.Component { | ||
handleClick() {} | ||
render() { | ||
return null; | ||
} | ||
} | ||
``` | ||
|
||
The following patterns are **not** considered warnings: | ||
|
||
```jsx | ||
class Foo extends React.Component { | ||
static getDerivedStateFromError(error) { | ||
return { hasError: true }; | ||
} | ||
action() {} | ||
componentDidMount() { | ||
this.action(); | ||
} | ||
render() { | ||
return null; | ||
} | ||
} | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
/** | ||
* @fileoverview Prevent declaring unused methods and properties of component class | ||
* @author Paweł Nowak, Berton Zhu | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const Components = require('../util/Components'); | ||
const docsUrl = require('../util/docsUrl'); | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
const LIFECYCLE_METHODS = new Set([ | ||
'constructor', | ||
'componentDidCatch', | ||
'componentDidMount', | ||
'componentDidUpdate', | ||
'componentWillMount', | ||
'componentWillReceiveProps', | ||
'componentWillUnmount', | ||
'componentWillUpdate', | ||
'getSnapshotBeforeUpdate', | ||
'render', | ||
'shouldComponentUpdate', | ||
'UNSAFE_componentWillMount', | ||
'UNSAFE_componentWillReceiveProps', | ||
'UNSAFE_componentWillUpdate' | ||
]); | ||
|
||
const ES6_LIFECYCLE = new Set([ | ||
'state' | ||
]); | ||
|
||
const ES5_LIFECYCLE = new Set([ | ||
'getInitialState', | ||
'getDefaultProps', | ||
'mixins' | ||
]); | ||
|
||
function isKeyLiteralLike(node, property) { | ||
return property.type === 'Literal' | ||
|| (property.type === 'TemplateLiteral' && property.expressions.length === 0) | ||
|| (node.computed === false && property.type === 'Identifier'); | ||
} | ||
|
||
// Descend through all wrapping TypeCastExpressions and return the expression | ||
// that was cast. | ||
function uncast(node) { | ||
while (node.type === 'TypeCastExpression') { | ||
node = node.expression; | ||
} | ||
return node; | ||
} | ||
|
||
// Return the name of an identifier or the string value of a literal. Useful | ||
// anywhere that a literal may be used as a key (e.g., member expressions, | ||
// method definitions, ObjectExpression property keys). | ||
function getName(node) { | ||
node = uncast(node); | ||
const type = node.type; | ||
|
||
if (type === 'Identifier') { | ||
return node.name; | ||
} | ||
if (type === 'Literal') { | ||
return String(node.value); | ||
} | ||
if (type === 'TemplateLiteral' && node.expressions.length === 0) { | ||
return node.quasis[0].value.raw; | ||
} | ||
return null; | ||
} | ||
|
||
function isThisExpression(node) { | ||
return uncast(node).type === 'ThisExpression'; | ||
} | ||
|
||
function getInitialClassInfo(node, isClass) { | ||
return { | ||
classNode: node, | ||
isClass, | ||
// Set of nodes where properties were defined. | ||
properties: new Set(), | ||
|
||
// Set of names of properties that we've seen used. | ||
usedProperties: new Set(), | ||
|
||
inStatic: false | ||
}; | ||
} | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'Prevent declaring unused methods of component class', | ||
category: 'Best Practices', | ||
recommended: false, | ||
url: docsUrl('no-unused-class-component-methods') | ||
}, | ||
schema: [ | ||
{ | ||
type: 'object', | ||
additionalProperties: false | ||
} | ||
] | ||
}, | ||
|
||
create: Components.detect((context, components, utils) => { | ||
let classInfo = null; | ||
|
||
// Takes an ObjectExpression node and adds all named Property nodes to the | ||
// current set of properties. | ||
function addProperty(node) { | ||
classInfo.properties.add(node); | ||
} | ||
|
||
// Adds the name of the given node as a used property if the node is an | ||
// Identifier or a Literal. Other node types are ignored. | ||
function addUsedProperty(node) { | ||
const name = getName(node); | ||
if (name) { | ||
classInfo.usedProperties.add(name); | ||
} | ||
} | ||
|
||
function reportUnusedProperties() { | ||
// Report all unused properties. | ||
for (const node of classInfo.properties) { // eslint-disable-line no-restricted-syntax | ||
const name = getName(node); | ||
if ( | ||
!classInfo.usedProperties.has(name) | ||
&& !LIFECYCLE_METHODS.has(name) | ||
&& (classInfo.isClass ? !ES6_LIFECYCLE.has(name) : !ES5_LIFECYCLE.has(name)) | ||
) { | ||
const className = (classInfo.classNode.id && classInfo.classNode.id.name) || ''; | ||
|
||
context.report({ | ||
node, | ||
message: `Unused method or property "${name}"${className ? ` of class "${className}"` : ''}` | ||
}); | ||
} | ||
} | ||
} | ||
|
||
function exitMethod() { | ||
if (!classInfo || !classInfo.inStatic) { | ||
return; | ||
} | ||
|
||
classInfo.inStatic = false; | ||
} | ||
|
||
return { | ||
ClassDeclaration(node) { | ||
if (utils.isES6Component(node)) { | ||
classInfo = getInitialClassInfo(node, true); | ||
} | ||
}, | ||
|
||
ObjectExpression(node) { | ||
if (utils.isES5Component(node)) { | ||
classInfo = getInitialClassInfo(node, false); | ||
} | ||
}, | ||
|
||
'ClassDeclaration:exit'() { | ||
if (!classInfo) { | ||
return; | ||
} | ||
reportUnusedProperties(); | ||
classInfo = null; | ||
}, | ||
|
||
'ObjectExpression:exit'(node) { | ||
if (!classInfo || classInfo.classNode !== node) { | ||
return; | ||
} | ||
reportUnusedProperties(); | ||
classInfo = null; | ||
}, | ||
|
||
Property(node) { | ||
if (!classInfo || classInfo.classNode !== node.parent) { | ||
return; | ||
} | ||
|
||
if (isKeyLiteralLike(node, node.key)) { | ||
addProperty(node.key); | ||
} | ||
}, | ||
|
||
'ClassProperty, MethodDefinition'(node) { | ||
if (!classInfo) { | ||
return; | ||
} | ||
|
||
if (node.static) { | ||
classInfo.inStatic = true; | ||
return; | ||
} | ||
|
||
if (isKeyLiteralLike(node, node.key)) { | ||
addProperty(node.key); | ||
} | ||
}, | ||
|
||
'ClassProperty:exit': exitMethod, | ||
'MethodDefinition:exit': exitMethod, | ||
|
||
MemberExpression(node) { | ||
if (!classInfo || classInfo.inStatic) { | ||
return; | ||
} | ||
|
||
if (isThisExpression(node.object) && isKeyLiteralLike(node, node.property)) { | ||
if (node.parent.type === 'AssignmentExpression' && node.parent.left === node) { | ||
// detect `this.property = xxx` | ||
addProperty(node.property); | ||
} else { | ||
// detect `this.property()`, `x = this.property`, etc. | ||
addUsedProperty(node.property); | ||
} | ||
} | ||
}, | ||
|
||
VariableDeclarator(node) { | ||
if (!classInfo || classInfo.inStatic) { | ||
return; | ||
} | ||
|
||
// detect `{ foo, bar: baz } = this` | ||
if (node.init && isThisExpression(node.init) && node.id.type === 'ObjectPattern') { | ||
node.id.properties.forEach((prop) => { | ||
if (prop.type === 'Property' && isKeyLiteralLike(prop, prop.key)) { | ||
addUsedProperty(prop.key); | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
}) | ||
}; |
Oops, something went wrong.