ESLint plugin to disable all mutation in JavaScript.
JavaScript
Latest commit 926b1eb Aug 15, 2016 @jhusain committed on GitHub Merge pull request #7 from wunderflats/fix/6
allow `module.exports`
Permalink
Failed to load latest commit information.
.gitignore Initial commit Mar 9, 2016
LICENSE Initial commit Mar 9, 2016
README.md Update README.md Mar 9, 2016
index.js allow `module.exports` Mar 15, 2016
package.json Initial commit. Mar 9, 2016

README.md

eslint-plugin-immutable

This is an ESLint plugin to disable all mutation in JavaScript. Think this is a bit too restrictive? Well if you're using Redux and React, there isn't much reason for your code to be mutating anything. Redux maintains a mutable pointer to your immutable application state, and React manages your DOM state. Your components should be stateless functions, translating data into Virtual DOM objects whenever Redux emits a new state. These ESLint rules explicitly prohibit mutation, effectively forcing you to write code very similar to Elm in React.

Installing

npm install eslint-plugin-immutable --save-dev

ESLint Rules

There are three rules in the plugin:

no-let

There's no reason to use let in a Redux/React application, because all your state is managed by either Redux or React. Use const instead, and avoid state bugs altogether.

let x = 5; // <- Unexpected let or var, use const.

What about for loops? Loops can be replaced with the Array methods like map, filter, and so on. If you find the built-in JS Array methods lacking, use lodash.

const SearchResults = 
  ({ results }) => 
    <ul>{
      results.map(result => <li>result</li>) // <- Who needs let?
    }</ul>;

no-this

Thanks to libraries like recompose and Redux's React Container components, there's not much reason to build Components using React.createClass or ES6 classes anymore. The no-this rule makes this explicit.

const Message = React.createClass({
  render: function() {
    return <div>{ this.props.message }</div>; // <- no this allowed
  }
})

Instead of creating classes, you should use React 0.14's Stateless Functional Components and save yourself some keystrokes:

const Message = ({message}) => <div>{ message }</div>;

What about lifecycle methods like shouldComponentUpdate? We can use the recompose library to apply these optimizations to your Stateless Functional Components. The recompose library relies on the fact that your Redux state is immutable to efficiently implement shouldComponentUpdate for you.

import { pure, onlyUpdateForKeys } from 'recompose';

const Message = ({message}) => <div>{ message }</div>;

// Optimized version of same component, using shallow comparison of props
// Same effect as React's PureRenderMixin
const OptimizedMessage = pure(Message);

// Even more optimized: only updates if specific prop keys have changed
const HyperOptimizedMessage = onlyUpdateForKeys(['message'], Message);

no-mutation

You might think that prohibiting the use of let and var would eliminate mutation from your JavaScript code. Wrong. Turns out that there's a pretty big loophole in const...

const point = { x: 23, y: 44 };
point.x = 99; // This is legal

This is why the no-mutation rule exists. This rule prevents you from assigning a value to the result of a member expression.

const point = { x: 23, y: 44 };
point.x = 99; // <- No object mutation allowed.

This rule is just as effective as using Object.freeze() to prevent mutations in your Redux reducers. However this rule has no run-time cost. A good alternative to object mutation is to use the object spread syntax coming in ES2016.

const point = { x: 23, y: 44 };
const transformedPoint = { ...point, x: 99 };

You can enable this syntax using the syntax-object-rest-spread Babel plug-in.

Supplementary ESLint Rules to Enable

The rules in this package alone can not eliminate mutation in your JavaScript programs. To go the distance I suggest you also enable the following built-in ESLint rules:

  • no-var (self-explanatory)
  • no-undef (prevents assigning to global variables that haven't been declared)
  • no-param-reassign (prevents assigning to variables introduced as function parameters)

Sample Configuration File

Here's a sample ESLint configuration file that activates these rules:

{
    "extends": "airbnb",
    "plugins": [
        "immutable"
    ],
    "rules": {
        "immutable/no-let": 2,
        "immutable/no-this": 2,
        "immutable/no-mutation": 2
    }
}

Special Thanks to cerealbox who paired with me on this.