123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- /**
- * @fileoverview Enforce React components to have a shouldComponentUpdate method
- * @author Evgueni Naverniouk
- */
- 'use strict';
- const values = require('object.values');
- const Components = require('../util/Components');
- const componentUtil = require('../util/componentUtil');
- const docsUrl = require('../util/docsUrl');
- const report = require('../util/report');
- const messages = {
- noShouldComponentUpdate: 'Component is not optimized. Please add a shouldComponentUpdate method.',
- };
- module.exports = {
- meta: {
- docs: {
- description: 'Enforce React components to have a shouldComponentUpdate method',
- category: 'Best Practices',
- recommended: false,
- url: docsUrl('require-optimization'),
- },
- messages,
- schema: [{
- type: 'object',
- properties: {
- allowDecorators: {
- type: 'array',
- items: {
- type: 'string',
- },
- },
- },
- additionalProperties: false,
- }],
- },
- create: Components.detect((context, components) => {
- const configuration = context.options[0] || {};
- const allowDecorators = configuration.allowDecorators || [];
- /**
- * Checks to see if our component is decorated by PureRenderMixin via reactMixin
- * @param {ASTNode} node The AST node being checked.
- * @returns {Boolean} True if node is decorated with a PureRenderMixin, false if not.
- */
- function hasPureRenderDecorator(node) {
- if (node.decorators && node.decorators.length) {
- for (let i = 0, l = node.decorators.length; i < l; i++) {
- if (
- node.decorators[i].expression
- && node.decorators[i].expression.callee
- && node.decorators[i].expression.callee.object
- && node.decorators[i].expression.callee.object.name === 'reactMixin'
- && node.decorators[i].expression.callee.property
- && node.decorators[i].expression.callee.property.name === 'decorate'
- && node.decorators[i].expression.arguments
- && node.decorators[i].expression.arguments.length
- && node.decorators[i].expression.arguments[0].name === 'PureRenderMixin'
- ) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * Checks to see if our component is custom decorated
- * @param {ASTNode} node The AST node being checked.
- * @returns {Boolean} True if node is decorated name with a custom decorated, false if not.
- */
- function hasCustomDecorator(node) {
- const allowLength = allowDecorators.length;
- if (allowLength && node.decorators && node.decorators.length) {
- for (let i = 0; i < allowLength; i++) {
- for (let j = 0, l = node.decorators.length; j < l; j++) {
- if (
- node.decorators[j].expression
- && node.decorators[j].expression.name === allowDecorators[i]
- ) {
- return true;
- }
- }
- }
- }
- return false;
- }
- /**
- * Checks if we are declaring a shouldComponentUpdate method
- * @param {ASTNode} node The AST node being checked.
- * @returns {Boolean} True if we are declaring a shouldComponentUpdate method, false if not.
- */
- function isSCUDeclared(node) {
- return Boolean(
- node
- && node.name === 'shouldComponentUpdate'
- );
- }
- /**
- * Checks if we are declaring a PureRenderMixin mixin
- * @param {ASTNode} node The AST node being checked.
- * @returns {Boolean} True if we are declaring a PureRenderMixin method, false if not.
- */
- function isPureRenderDeclared(node) {
- let hasPR = false;
- if (node.value && node.value.elements) {
- for (let i = 0, l = node.value.elements.length; i < l; i++) {
- if (node.value.elements[i] && node.value.elements[i].name === 'PureRenderMixin') {
- hasPR = true;
- break;
- }
- }
- }
- return Boolean(
- node
- && node.key.name === 'mixins'
- && hasPR
- );
- }
- /**
- * Mark shouldComponentUpdate as declared
- * @param {ASTNode} node The AST node being checked.
- */
- function markSCUAsDeclared(node) {
- components.set(node, {
- hasSCU: true,
- });
- }
- /**
- * Reports missing optimization for a given component
- * @param {Object} component The component to process
- */
- function reportMissingOptimization(component) {
- report(context, messages.noShouldComponentUpdate, 'noShouldComponentUpdate', {
- node: component.node,
- });
- }
- /**
- * Checks if we are declaring function in class
- * @returns {Boolean} True if we are declaring function in class, false if not.
- */
- function isFunctionInClass() {
- let blockNode;
- let scope = context.getScope();
- while (scope) {
- blockNode = scope.block;
- if (blockNode && blockNode.type === 'ClassDeclaration') {
- return true;
- }
- scope = scope.upper;
- }
- return false;
- }
- return {
- ArrowFunctionExpression(node) {
- // Skip if the function is declared in the class
- if (isFunctionInClass()) {
- return;
- }
- // Stateless Functional Components cannot be optimized (yet)
- markSCUAsDeclared(node);
- },
- ClassDeclaration(node) {
- if (!(
- hasPureRenderDecorator(node)
- || hasCustomDecorator(node)
- || componentUtil.isPureComponent(node, context)
- )) {
- return;
- }
- markSCUAsDeclared(node);
- },
- FunctionDeclaration(node) {
- // Skip if the function is declared in the class
- if (isFunctionInClass()) {
- return;
- }
- // Stateless Functional Components cannot be optimized (yet)
- markSCUAsDeclared(node);
- },
- FunctionExpression(node) {
- // Skip if the function is declared in the class
- if (isFunctionInClass()) {
- return;
- }
- // Stateless Functional Components cannot be optimized (yet)
- markSCUAsDeclared(node);
- },
- MethodDefinition(node) {
- if (!isSCUDeclared(node.key)) {
- return;
- }
- markSCUAsDeclared(node);
- },
- ObjectExpression(node) {
- // Search for the shouldComponentUpdate declaration
- const found = node.properties.some((property) => (
- property.key
- && (isSCUDeclared(property.key) || isPureRenderDeclared(property))
- ));
- if (found) {
- markSCUAsDeclared(node);
- }
- },
- 'Program:exit'() {
- // Report missing shouldComponentUpdate for all components
- values(components.list())
- .filter((component) => !component.hasSCU)
- .forEach((component) => {
- reportMissingOptimization(component);
- });
- },
- };
- }),
- };
|