123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- /**
- * @fileoverview Prevent direct mutation of this.state
- * @author David Petersen
- * @author Nicolas Fernandez <@burabure>
- */
- 'use strict';
- const Components = require('../util/Components');
- const docsUrl = require('../util/docsUrl');
- // ------------------------------------------------------------------------------
- // Rule Definition
- // ------------------------------------------------------------------------------
- module.exports = {
- meta: {
- docs: {
- description: 'Prevent direct mutation of this.state',
- category: 'Possible Errors',
- recommended: true,
- url: docsUrl('no-direct-mutation-state')
- }
- },
- create: Components.detect((context, components, utils) => {
- /**
- * Checks if the component is valid
- * @param {Object} component The component to process
- * @returns {Boolean} True if the component is valid, false if not.
- */
- function isValid(component) {
- return Boolean(component && !component.mutateSetState);
- }
- /**
- * Reports undeclared proptypes for a given component
- * @param {Object} component The component to process
- */
- function reportMutations(component) {
- let mutation;
- for (let i = 0, j = component.mutations.length; i < j; i++) {
- mutation = component.mutations[i];
- context.report({
- node: mutation,
- message: 'Do not mutate state directly. Use setState().'
- });
- }
- }
- /**
- * Walks throughs the MemberExpression to the top-most property.
- * @param {Object} node The node to process
- * @returns {Object} The outer-most MemberExpression
- */
- function getOuterMemberExpression(node) {
- while (node.object && node.object.property) {
- node = node.object;
- }
- return node;
- }
- /**
- * Determine if this MemberExpression is for `this.state`
- * @param {Object} node The node to process
- * @returns {Boolean}
- */
- function isStateMemberExpression(node) {
- return node.object.type === 'ThisExpression' && node.property.name === 'state';
- }
- /**
- * Determine if we should currently ignore assignments in this component.
- * @param {?Object} component The component to process
- * @returns {Boolean} True if we should skip assignment checks.
- */
- function shouldIgnoreComponent(component) {
- return !component || (component.inConstructor && !component.inCallExpression);
- }
- // --------------------------------------------------------------------------
- // Public
- // --------------------------------------------------------------------------
- return {
- MethodDefinition(node) {
- if (node.kind === 'constructor') {
- components.set(node, {
- inConstructor: true
- });
- }
- },
- CallExpression: function(node) {
- components.set(node, {
- inCallExpression: true
- });
- },
- AssignmentExpression(node) {
- const component = components.get(utils.getParentComponent());
- if (shouldIgnoreComponent(component) || !node.left || !node.left.object) {
- return;
- }
- const item = getOuterMemberExpression(node.left);
- if (isStateMemberExpression(item)) {
- const mutations = (component && component.mutations) || [];
- mutations.push(node.left.object);
- components.set(node, {
- mutateSetState: true,
- mutations
- });
- }
- },
- UpdateExpression(node) {
- const component = components.get(utils.getParentComponent());
- if (shouldIgnoreComponent(component) || node.argument.type !== 'MemberExpression') {
- return;
- }
- const item = getOuterMemberExpression(node.argument);
- if (isStateMemberExpression(item)) {
- const mutations = (component && component.mutations) || [];
- mutations.push(item);
- components.set(node, {
- mutateSetState: true,
- mutations
- });
- }
- },
- 'CallExpression:exit': function(node) {
- components.set(node, {
- inCallExpression: false
- });
- },
- 'MethodDefinition:exit': function (node) {
- if (node.kind === 'constructor') {
- components.set(node, {
- inConstructor: false
- });
- }
- },
- 'Program:exit': function () {
- const list = components.list();
- Object.keys(list).forEach(key => {
- if (!isValid(list[key])) {
- reportMutations(list[key]);
- }
- });
- }
- };
- })
- };
|