| 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.forEach((prop) => {            if (prop.type === 'Property' && isKeyLiteralLike(prop, prop.key)) {              addUsedProperty(prop.key);            }          });        }      },    };  }),};
 |