An Airtame approach to Javascript and React
This config is inspired by Airbnb's Javascript and React styleguides with an Airtame touch and the help of Prettier for styling.
Install as a development dependency to your project
$ yarn add --dev eslint-config-airtame
or
$ npm install --save-dev eslint-config-airtame
Then, on your eslint
configuration file
{
"extends": "airtame"
}
If using prettier extension on your text editor, the following options are necessary
"eslintIntegration": true,
"printWidth": 100,
"trailingComma": "es5",
"singleQuote": true
Use camelCase when naming objects, functions, and instances.
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
Always use curly brackets, even when doing single line blocks.
// bad
if (foo) foo++;
while (bar)
baz();
if (foo) {
baz();
} else qux();
// good
if (foo) {
foo++;
}
while (bar) {
baz();
}
if (foo) {
baz();
} else {
qux();
}
Use dot notation when accessing properties, unless they are variables.
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
Prefer ===
and !==
over ==
and !=
to guarantee proper comparisons.
// bad
if (x == 5) {
console.log(x);
}
if (x != 5) {
console.log(x);
}
// good
if (x === 5) {
console.log(x);
}
if (x !== 5) {
console.log(x);
}
Make sure that there is an export
for every import
in the file.
Given
// ./foo.js
export default function () { return 42 }
// ./bar.js
export function bar() { return null }
// ./baz.js
module.exports = function () { /* ... */ }
// node_modules/some-module/index.js
exports.sharedFunction = function shared() { /* ... */ }
Expect
// bad
import bar from './bar' // no default export found in ./bar
import baz from './baz' // no default export found in ./baz
// good
import foo from './foo'
// assuming 'node_modules' are ignored (true by default)
import someModule from 'some-module'
Only allow valid exports.
// bad
export default class MyClass { /*...*/ } // Multiple default exports.
function makeClass() { return new MyClass(...arguments) }
export default makeClass // Multiple default exports.
// also bad
export const foo = function () { /*...*/ } // Multiple exports of name 'foo'.
function bar() { /*...*/ }
export { bar as foo } // Multiple exports of name 'foo'.
Ensure all imports appear before any other statements.
// bad
import foo from './foo'
// some module-level initializer
initWith(foo)
import bar from './bar'
// good
import foo from './foo'
import bar from './bar'
// some module-level initializer
initWith(foo)
Ensures an imported module can be resolved to a module. This includes CommonJS and AMD modules.
Ensure named imports correspond to a named export in the remote file.
Given
// ./foo.js
export const foo = "I'm so foo"
Expect
// bad
// ./baz.js
import { notFoo } from './foo'
// good
import { foo } from './foo'
Ensure named imports correspond to a named export in the remote file.
Given
// @module ./named-exports
export const a = 1
const b = 2
export { b }
const c = 3
export { c as d }
export class ExportedClass { }
and
// @module ./deep
export const e = "MC2"
Expect
// good
// @module ./foo
import * as names from './named-exports'
function great() {
return names.a + names.b // so great https://youtu.be/ei7mb8UxEl8
}
function notGreat() {
doSomethingWith(names.c) // Reported: 'c' not found in imported namespace 'names'.
const { a, b, c } = names // also reported, only for 'c'
}
// also tunnels through re-exported namespaces!
function deepTrouble() {
doSomethingWith(names.deep.e) // fine
doSomethingWith(names.deep.f) // Reported: 'f' not found in deeply imported namespace 'names.deep'.
}
Forbid the use of mutable exports with var
or let
.
// bad
export let count = 2
export var count = 3
let count = 4
export { count }
// good
export const count = 1
export function getCount() {}
export class Counter {}
Forbid Webpack loader syntax in imports to avoid dependency on a bundler.
// bad
import myModule from 'my-loader!my-module';
import theme from 'style!css!./theme.css';
var myModule = require('my-loader!./my-module');
var theme = require('style!css!./theme.css');
// good
import myModule from 'my-module';
import theme from './theme.css';
var myModule = require('my-module');
var theme = require('./theme.css');
Prefer a default export if module exports a single name
// bad.js
// There is only a single module export and it's a named export.
export const foo = 'foo';
// good1.js
// There is a default export.
export const foo = 'foo';
const bar = 'bar';
export default 'bar';
// good2.js
// There is more than one named export in the module.
export const foo = 'foo';
export const bar = 'bar';
// good3.js
// There is more than one named export in the module
const foo = 'foo';
const bar = 'bar';
export { foo, bar }
// good4.js
// There is a default export.
const foo = 'foo';
export { foo as default }
// export-star.js
// Any batch export will disable this rule. The remote module is not inspected.
export * from './other-module'
Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
// bad
<div role="datepicker"></div>
<div role="range"></div>
<div role=""></div>
// good
<div role="button"></div>
<div role={role}></div>
<div></div>
Make sure all images have the alt
prop
// bad
<img {...props} />
// good
<img alt="Airtame logo" {...props} />
Make sure alt
props don't contain image or picture as part of it, as screen readers already call this out.
// bad
<img src="https://github.com/airtame/javascript/raw/master/foo" alt="Photo of foo being weird." />
// good
<img src="https://github.com/airtame/javascript/raw/master/foo" alt="Foo eating a sandwich." />
Do not use the accessKey
prop.
Why? Inconsistencies between keyboard shortcuts and keyboard commands used by people using screenreaders and keyboards complicate accessibility.
// bad
<div accessKey="h" />
// good
<div />
Each line of code should be no longer than 100 characters
Only use PascalCase to name classes or constructors
// bad
function user(options) {
this.name = options.name;
}
const bad = new user({
name: 'nope',
});
// good
class User {
constructor(options) {
this.name = options.name;
}
}
const good = new User({
name: 'yup',
});
Alerts are thrown as warnings to allow debugging. Make sure to remove before deployment.
Use literal syntax for array creation.
// bad
const items = new Array();
// good
const items = [];
Use braces to create blocks in case and default clauses that contain lexical declarations (e.g. let
, const
, function
, and class
).
Why? Lexical declarations are visible in the entire switch block but only get initialized when assigned, which only happens when its case is reached. This causes problems when multiple case clauses attempt to define the same thing.
// bad
switch (foo) {
case 1:
let x = 1;
break;
case 2:
const y = 2;
break;
default:
class C {}
}
// good
switch (foo) {
case 1: {
let x = 1;
break;
}
case 2: {
const y = 2;
break;
}
case 4:
bar();
break;
default: {
class C {}
}
}
Prevent variable assignments in conditions
// bad
if (foo = 5) {
// stuff
}
Avoid confusing arrow function syntax (=>
) with comparison operators (<=
, >=
).
// bad
const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;
// bad
const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;
// good
const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);
// good
const itemHeight = (item) => {
const { height, largeSize, smallSize } = item;
return height > 256 ? largeSize : smallSize;
};
Console logs are thrown as warnings to allow debugging. Make sure to remove before deployment.
Avoid duplicate class members.
Why? Duplicate class member declarations will silently prefer the last one - having duplicates is almost certainly a bug.
// bad
class Foo {
bar() { return 1; }
bar() { return 2; }
}
// good
class Foo {
bar() { return 1; }
}
// good
class Foo {
bar() { return 2; }
}
Only import a path once per file
Why? Having multiple lines that import from the same path can make code harder to maintain.
// bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';
// good
import foo, { named1, named2 } from 'foo';
// good
import foo, {
named1,
named2,
} from 'foo';
Do not write empty blocks of code
// bad
if (foo) {
}
while (foo) {
}
switch(foo) {
}
try {
doSomething();
} catch(ex) {
} finally {
}
// good
if (foo) {
// empty
}
Do not use iterators like for-in
and for-of
Why? This enforces the immutable rule. Dealing with pure functions that return values is easier to reason about than side effects.
// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;
// good
let sum = 0;
numbers.forEach(num => sum += num);
sum === 15;
Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead.
Why? Browsers will allow you to do it, but they all interpret it differently.
// bad
for (var i=10; i; i--) {
(function() { return i; })();
}
// good
var a = function() {};
for (var i=10; i; i--) {
a();
}
Do not add more empty lines than needed
// bad
var foo = 5;
var bar = 3;
// good
var biz = 2;
function foo() {
...
}
Do not use more than one space between identifiers unless it's for indentation
// bad
if(foo === "bar") {}
// good
if (foo === "bar") {}
Ternaries should not be nested and generally be single line expressions, unless it's inside JSX
// bad
const foo = maybe1 > maybe2
? "bar"
: value1 > value2 ? "baz" : null;
// better
const maybeNull = value1 > value2 ? 'baz' : null;
const foo = maybe1 > maybe2
? 'bar'
: maybeNull;
// best
const maybeNull = value1 > value2 ? 'baz' : null;
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
Never use the Function constructor to create a new function.
Why? Creating a function in this way evaluates a string similarly to eval(), which opens vulnerabilities.
// bad
var add = new Function('a', 'b', 'return a + b');
// still bad
var subtract = Function('a', 'b', 'return a - b');
// good
const multiply = function(a, b) { return a * b };
Use the literal object syntax for object creation
// bad
const item = new Object();
// good
const item = {};
Do not reasign the value of you're parameters. This usually means you're creating some unwanted behavior
// bad
function foo(bar) {
bar = bar * 5;
return bar;
}
// good
function foo(bar) {
return bar * 5;
}
Avoid using ++
and --
Why? Per the eslint documentation, unary increment and decrement statements are subject to automatic semicolon insertion and can cause silent errors with incrementing or decrementing values within an application. It is also more expressive to mutate your values with statements like
num += 1
instead ofnum++
ornum ++
. Disallowing unary increment and decrement statements also prevents you from pre-incrementing/pre-decrementing values unintentionally which can also cause unexpected behavior in your programs.
// bad
a++;
// good
a += 1;
See no-iterator
Only throw valid errors as part of a throw
.
// bad
throw "error";
throw 0;
throw undefined;
throw null;
// good
throw new Error();
throw new Error("error");
Make sure all variables are defined. Use let
or const
to define your variables.
// bad
a = 5;
// good
let b = 5;
const x = 10;
Only used a ternary when it's necessary and not for implicit conditions
// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;
// good
const foo = a || b;
const bar = !!c;
const baz = !c;
All declared variables must be used. Otherwise the variable shouldn't exist;
// bad
var x = 5;
...
// good
var y = 5;
myFunctionCall(y);
Classes have a default constructor if one is not specified, which means an empty constructor is not necessary
// bad
class Jedi {
constructor() {}
getName() {
return this.name;
}
}
// bad
class Rey extends Jedi {
constructor(...args) {
super(...args);
}
}
// good
class Rey extends Jedi {
constructor(...args) {
super(...args);
this.name = 'Rey';
}
}
Only escape characters that need to be escaped
Why? Backslashes harm readability, thus they should only be present when necessary.
// bad
const foo = '\'this\' \i\s \"quoted\"';
// good
const foo = '\'this\' is "quoted"';
const foo = `my name is '${name}'`;
Never use var
. Use let
or const
instead.
Why?
let
is block-scoped rather than function-scoped likevar
.
// bad
var x = 5;
// good
let y = 5;
Use object shorthand notation when possible.
// bad
function foo(bar) {
let awesome = bar;
return {
awesome: awesome
}
}
// good
function biz(bar) {
let awesome = bar;
return {awesome};
}
Declare each variable individually
Why? Readability
// bad
var foo, bar, biz;
// good;
let foo;
let bar;
let biz;
User arrow functions for your callbacks
Why? It creates a version of the function that executes in the context of
this,
which is usually what you want, and is a more concise syntax.
// bad
[1, 2, 3].map(function (x) {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
If your variable is never reassigned, use const
instead of let
Never use arguments
, opt to use rest syntax ...
instead
Why?
...
is explicit about which arguments you want pulled. Plus, rest arguments are a real Array, and not merely Array-like likearguments
.
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
Prefer the use of the spread operator ...
to call variadic functions.
// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);
// good
const x = [1, 2, 3, 4, 5];
console.log(...x);
// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));
// good
new Date(...[2016, 8, 5]);
Use template strings instead of doing string concatenation
let age = 10;
// bad
const greeting = 'Mary is ' + age + ' years old';
// good
const greeting2 = `Tim is also ${age} years old`;
Always set the radix when using parseInt()
// bad
var num = parseInt("071"); // 57
// good
var num = parseInt("071", 10); // 71
Do not set explicit value for boolean JSX attributes.
// bad
<Component disabled={true} />
// good
<Component disabled />
Never use bind in the render
function. Arrow functions are allowed.
// bad
render() {
return (
<Component onClick={this.myMethod.bind(this)} />
);
}
// good
constructor() {
super();
this.myMethod = this.myMethod.bind(this);
}
render() {
return (
<div>
<Component onClick={this.myMethod} />
<Component onClick={(evt) => { this.myMethod(evt) }} />
</div>
);
}
Use pascal case for your component names
// bad
class myComponent extends React.Component {
...
}
// good
class MyComponent extends React.Component {
}
Always use a space before a self closing tag.
// bad
<Component/>
// good
<Component />
Prevents React from being marked as unused
Prevent variables used in JSX to be incorrectly marked as unused
Prevents the use of the isMounted lifecycle method
Why? isMounted is an anti-pattern, is not available when using ES6 classes, and is on its way to being officially deprecated.
Write only one React component per file, unless they are Stateless components
Always use ref callbacks
Why? String callbacks will be deprecated in future versions of React
// bad
var Hello = React.createClass({
render: function() {
return <div ref="hello">Hello, world.</div>;
}
});
// good
class Hello extends React.Component {
constructor() {
super();
var component = this.hello;
}
myMethod() {
// ...do something with component
}
render() {
return <div ref={(c) => { this.hello = c; }}>Hello, world.</div>;
}
}
Write react components using ES6 Class vs React.createClass
// bad
const Listing = React.createClass({
// ...
render() {
return <div>{this.state.hello}</div>;
}
});
// good
class Listing extends React.Component {
// ...
render() {
return <div>{this.state.hello}</div>;
}
}
Use stateless functions when possibles
// bad
class ComponentThatOnlyRenders extends React.Component {
render() {
return (
<div className={this.props.className}>Hello World</div>
)
}
}
// good
const ComponentThatOnlyRenders = function(props) {
return (
<div className={props.className}>Hello World</div>
);
}
Always return a value in the render
function
// bad
render() {
(<div />);
}
// good
render() {
return (<div />);
}
Always self close Components that have no children
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
Always include JSDoc documentation for all your methods and functions
// bad
function add(a, b) {
return a + b;
}
// good
/**
* Multiplies to values
* @param {number} a The first number to be multiplied
* @param {number} b The second number to be multiplied
* @return {number} The result of the multiplication
*/
function multiply(a, b) {
return a * b;
}
Always add a space after your comments
Why? Readability
// bad
//my comment
/*my comment*/
// good
// My comment
/**
* My block comment
*/
/******************************
* A pretty block comment *
******************************/
Ensure your JSDoc is valid and all parameters and your return are documented
// bad
function foo (bar, biz) {
var y;
// do stuff
return y;
}
/**
* This is function foo
*/
function foo (bar, biz) {
var y;
// do stuff
return y;
}
/**
* This is function foo
* @param {number} foo - Only one parameter explained and no return is bad
*/
function foo (bar, biz) {
var y;
// do stuff
return y;
}
// good
/**
* This is function foo
* @param {number} foo - description of first param
* @param {number} bar - description of second param
* @return {number} Description of the return
*/
function foo (bar, biz) {
var y;
// do stuff
return y;
}