123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- /**
- * @fileoverview Prevent declaring unused methods and properties of component class
- * @author Paweł Nowak, Berton Zhu
- */
- 'use strict';
- const docsUrl = require('../util/docsUrl');
- const componentUtil = require('../util/componentUtil');
- const report = require('../util/report');
- // ------------------------------------------------------------------------------
- // Rule Definition
- // ------------------------------------------------------------------------------
- const LIFECYCLE_METHODS = new Set([
- 'constructor',
- 'componentDidCatch',
- 'componentDidMount',
- 'componentDidUpdate',
- 'componentWillMount',
- 'componentWillReceiveProps',
- 'componentWillUnmount',
- 'componentWillUpdate',
- 'getChildContext',
- '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,
- };
- }
- const messages = {
- unused: 'Unused method or property "{{name}}"',
- unusedWithClass: 'Unused method or property "{{name}}" of class "{{className}}"',
- };
- module.exports = {
- meta: {
- docs: {
- description: 'Disallow declaring unused methods of component class',
- category: 'Best Practices',
- recommended: false,
- url: docsUrl('no-unused-class-component-methods'),
- },
- messages,
- schema: [],
- },
- create: ((context) => {
- 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) || '';
- const messageID = className ? 'unusedWithClass' : 'unused';
- report(
- context,
- messages[messageID],
- messageID,
- {
- node,
- data: {
- name,
- className,
- },
- }
- );
- }
- }
- }
- function exitMethod() {
- if (!classInfo || !classInfo.inStatic) {
- return;
- }
- classInfo.inStatic = false;
- }
- return {
- ClassDeclaration(node) {
- if (componentUtil.isES6Component(node, context)) {
- classInfo = getInitialClassInfo(node, true);
- }
- },
- ObjectExpression(node) {
- if (componentUtil.isES5Component(node, context)) {
- 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, PropertyDefinition'(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,
- 'PropertyDefinition: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
- .filter((prop) => prop.type === 'Property' && isKeyLiteralLike(prop, prop.key))
- .forEach((prop) => {
- addUsedProperty(prop.key);
- });
- }
- },
- };
- }),
- };
|