all files / rules/ no-constructor-state.js

100% Statements 36/36
100% Branches 31/31
100% Functions 12/12
100% Lines 36/36
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140                                                    16×               16×               14×     41×               41×     28×               25×   17×       15×                           14×             16×                           16×           16×          
/**
 * @fileoverview Use class arrow functions instead of binding in the constructor
 * @author Mark Battersby
 */
'use strict'
 
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
 
module.exports = {
  meta: {
    docs: {
      description:
        'Use class property instead of setting initial state in the constructor',
      category: 'ECMAScript 7',
      recommended: false
    },
    fixable: 'code',
    schema: []
  },
 
  create: function(context) {
    //----------------------------------------------------------------------
    // Helpers
    //----------------------------------------------------------------------
 
    function assigningToThis(node) {
      return (
        node.left &&
        node.left.type === 'MemberExpression' &&
        node.left.object &&
        node.left.object.type === 'ThisExpression'
      )
    }
 
    function assigningToState(node) {
      return (
        node.left &&
        node.left.type === 'MemberExpression' &&
        node.left.property &&
        node.left.property.name === 'state'
      )
    }
 
    function assigningSimpleValue(node) {
      return node.right && isSimple(node.right)
    }
 
    function isSimple(node) {
      return (
        isLiteral(node) ||
        isMagicIdentifier(node) ||
        isSimpleObject(node) ||
        isSimpleArray(node)
      )
    }
 
    function isLiteral(node) {
      return node.type === 'Literal'
    }
 
    function isMagicIdentifier(node) {
      return (
        node.type === 'Identifier' &&
        node.name === 'undefined' ||
        node.name === 'Infinity' ||
        node.name === 'NaN'
      )
    }
 
    function isSimpleObject(node) {
      return (
        node.type === 'ObjectExpression' &&
        node.properties.every(p => p.value && isSimple(p.value))
      )
    }
 
    function isSimpleArray(node) {
      return (
        node.type === 'ArrayExpression' &&
        node.elements.every(isSimple)
      )
    }
 
    function reconstructState(node) {
      const constructorNode = findConstructor(node)
      const newline =
        constructorNode.loc.start.line !== constructorNode.loc.end.line
      const indent = constructorNode.loc.start.column
 
      const sourceCode = context.getSourceCode()
 
      let leftText = ' state = '
      if (newline) {
        leftText = '\n\n' + ' '.repeat(indent) + 'state = '
      }
 
      const rightText = sourceCode.getText(node.right)
      return leftText + rightText
    }
 
    function findConstructor(node) {
      // this seems... maybe brittle
      return node.parent.parent.parent.parent
    }
 
    //----------------------------------------------------------------------
    // Public
    //----------------------------------------------------------------------
 
    function checkAssignment(node) {
      if (
        assigningToThis(node) &&
        assigningToState(node) &&
        assigningSimpleValue(node)
      ) {
        context.report({
          node: node,
          message: "Don't set initial state in the constructor",
          fix: fixer => [
            fixer.insertTextAfter(findConstructor(node), reconstructState(node)),
            fixer.remove(node)
          ]
        })
      }
    }
 
    const constructorDefinition = [
      'MethodDefinition[kind="constructor"]',
      'ExpressionStatement',
      'AssignmentExpression'
    ].join(' ')
 
    return {
      [constructorDefinition]: checkAssignment
    }
  }
}