GitHub
Twitter

Code Quality

🎵 This is how we do it 🎵

Welcome to our Gearbox Code Standards page!

Some of these rules are etched in stone. Others will be a little more fluid, as we continue to scale and adapt to the ever-changing tech landscape. It is what it is. 🤷🏻

We’ll do our very best to make sure we update this regularly, so check back in frequently if you are unsure.

  • Use soft tabs with two spaces—they’re the only way to guarantee code renders the same in any environment.

  • Try your best to limit all lines to a maximum of 80 characters. Do not ever go over 120.

  • Do not include trailing whitespace on any lines.

  • Use only unix-style newlines. \n not \r\n.

  • Use UTF-8 as the source file encoding.

  • Include one space before the opening brace for legibility.

  • Include 1 space before the opening parenthesis in control statements. Include no spaces before the argument list in function calls and declarations.

  • Set off operators with spaces.

  • End files with a single newline character.

  • Leave a blank line after blocks and before the next statement.

  • Use a single blank line within the bodies of methods or functions in cases where this improves readability.

  • Do not use leading commas.

  • Use additional trailing commas for cleaner diffs.

  • Use braces with all multi-line blocks.

  • If you’re using multi-line blocks with if and else, put else on the same line as your if block’s closing brace.

// Bad ❌
function test (){
const x=y+5
const dog = {
breed: 'Dachshund'
, legs: 'short'
};
if (x>6)
return false;
if(isJedi){
console.log (x)
}
else{
console.log (y)
}
return x
}
// Good ✅
function test() {
const x = y + 5;
const dog = {
breed: 'Dachshund',
legs: 'short',
};
if (x > 6) return false;
if (isJedi) {
console.log(x);
} else {
console.log(y);
}
return x;
}
  • Use const for all of your references; this avoids accidentally reassigning references.

  • If you must mutate references (in a loop, for example) use let.

  • Group all your consts and then group all your lets next to each other; this is helpful when later on you might need to assign a variable depending on one of the previous assigned variables. Conversely, never use grouped variable declarations unless deconstructing.

  • Use as descriptive variables as possible, whenever possible. Rely on readable self-describing code first and foremost.

// Bad ❌
a = 1;
var b = new Date();
var c = 2;
var d = 'Title',
e = 'Body';
if (true) {
c += 1;
}
// Good ✅
const id = 1;
const now = new Date();
const { title, body, ...rest } = props;
let page = 2;
if (true) {
page += 1;
}

Use === and !== instead of == and !=.

Conditional statements such as the if statement evaulate their expression using coercion with the ToBoolean abstract method and always follow these simple rules:

  • Objects evaluate to true.

  • Undefined evaluates to false.

  • Null evaluates to false.

  • Booleans evaluate to the value of the boolean.

  • Numbers evaluate to false if +00, or NaN, otherwise true.

  • Strings evaluate to false if an empty string (''), otherwise true.

  • Use shortcuts when possible.

  • Avoid single letter names. Be descriptive with your naming.

  • Use camelCase when naming objects, functions, and instances.

  • Use PascalCase when naming constructors or classes.

  • Use camelCase when you export-default a function. Your filename should be identical to your function’s name.

  • Use /** ... */ for multi-line comments. Include a description, specify types and values for all parameters and return values.

  • Use // for single line comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment.

  • Use // FIXME: to annotate problems.

  • Use // TODO: to annotate solutions to problems.

// Bad ❌
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
let dogs = 1;
// there are no cats
let cats = 0;
let element = document.createElement(tag); // create element
return element;
}
// Good ✅
/**
* make() returns a new element
* based on the passed in tag name
*
* @param {String} tag
* @return {Element} element
*/
function make(tag) {
// FIXME: this isn't nearly enough dogs
let dogs = 1;
// TODO: add more cats
let cats = 0;
// create element
let element = document.createElement(tag);
return element;
}
  • Use array destructuring for basic arrays.

  • Use object destructuring when accessing and using multiple properties of an object.

  • Use object destructuring for multiple return values, not array destructuring. You can add new properties over time or change the order of things without breaking call sites.

// ARRAY DESTRUCTURING
const arr = [1, 2, 3, 4];
// Bad ❌
const first = arr[0];
const second = arr[1];
// Good ✅
const [first, second] = arr;
// OBJECT DESTRUCTURING
// Bad ❌
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// Good ✅
function getFullName(obj) {
const { firstName, lastName } = obj;
return `${firstName} ${lastName}`;
}
// Best 🌟 - Destructure right inside the function definition.
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
// MULTIPLE RETURN VALUES
// Bad ❌
function processInput(input) {
return [left, right, top, bottom];
}
// The caller needs to think about the order of return data
const [left, __, top] = processInput(input);
// Good ✅
function processInput(input) {
return { left, right, top, bottom };
}
// The caller selects only the data they need
const { left, right } = processInput(input);
  • Use single quotes '' for strings (this will be handled by our Prettier settings).

  • When programmatically building up strings, use template strings instead of concatenation.

// Bad ❌
const name = 'Mr. Gearbox';
// Good ✅
const name = 'Mr. Gearbox';
// Bad ❌
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// Bad ❌
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// Good ✅
function sayHi(name) {
return `How are you, ${name}?`;
}
  • Use the literal syntax for array creation.

  • Use Array.push() instead of direct assignment to add items to an array.

  • Use array spreads to copy arrays (...).

  • To convert an array-like object to an array, use Array.from().

// Bad ❌
const items = new Array();
// Good ✅
const items = [];
// Bad ❌
items[items.length] = 'abracadabra';
// Good ✅
items.push('abracadabra');
// WORST 🤮 (woof...)
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// Good ✅
const itemsCopy = [...items];
// Good ✅
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
  • Use function declarations instead of function expressions.

    • Function declarations are named, so they’re easier to identify in call stacks. Also, the whole body of a function declaration is hoisted, whereas only the reference of a function expression is hoisted. This rule makes it possible to always use Arrow Functions in place of function expressions.

  • Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead. Browsers will allow you to do it, but they all interpret it differently.

  • When you must use function expressions (as when passing an anonymous function), use arrow function notation. 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.

  • If the function body fits on one line and there is only a single argument, feel free to omit the braces and parentheses, and use the implicit return.

// Bad ❌
const foo = function () {
// ...
};
// Good ✅
function foo() {
// ...
}
// Bad ❌
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// This is OK
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
// Bad ❌
[1, 2, 3].map(function (x) {
return x * x;
});
// Good ✅
[1, 2, 3].map((x) => {
return x * x;
});
// Good ✅
[1, 2, 3].map((x) => x * x);
  • Never name a parameter arguments. This will take precedence over the arguments object that is given to every function scope.

  • Never use arguments, opt to use rest syntax ... instead.

  • Use default parameter syntax rather than mutating function arguments.

// Bad ❌
function nope(name, options, arguments) {
// ...
}
// Good ✅
function yup(name, options, args) {
// ...
}
// Bad ❌
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// Good ✅
function concatenateAll(...args) {
return args.join('');
}
// Bad ❌
function handleThings(options) {
// No! We shouldn't mutate function arguments.
// Double bad: if opts is falsy it'll be set to an object which may
// be what you want but it can introduce subtle bugs.
this.options = options || {};
// ...
}
  • Use the literal syntax for object creation.

  • Don’t use reserved words as keys. It won’t work in IE8. (More info). Use readable synonyms instead.

  • Use computed property names when creating objects with dynamic property names. They allow you to define all the properties of an object in one place.

  • Use object method shorthand.

  • Use property value shorthand. Group your shorthand properties at the beginning of your object declaration.

  • Use dot notation when accessing properties. Use subscript notation [] when accessing properties with a variable.

// LITERAL SYNTAX / OBJECT METHOD SHORTHAND / RESERVED WORDS
// Bad ❌
const superman = new Object(
default: { clark: 'kent' },
private: true,
class: 'alien',
laserEyes: function (target) {
return `${target} is real dead`;
},
);
// Good ✅
const superman = {
defaults: { clark: 'kent' },
hidden: true,
type: 'alien',
laserEyes(target) {
return `${target} is real dead`;
},
};
// COMPUTED PROPERTY NAMES
function getKey(k) {
return `a key named ${k}`;
}
// Bad ❌
const obj = {
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// Good ✅
const obj = {
name: 'San Francisco',
[getKey('enabled')]: true,
};
// PROPERTY VALUE SHORTHAND
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// Bad ❌
const obj = {
episodeOne: 1,
lukeSkywalker,
twoJedisWalkIntoACantina: 2,
anakinSkywalker,
};
// Good ✅
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJedisWalkIntoACantina: 2,
};
// ACCESSING PROPERTIES
const luke = {
jedi: true,
age: 28,
};
// Bad ❌
const isJedi = luke['jedi'];
// Good ✅
const isJedi = luke.jedi;
  • To help differentiate between a React and a Stitches component, prepend all Stitches component with a $ .

  • Unless absolutely necessary, do not export your Stitches components.

  • Use the util shorthands for CSS properties as often as possible for clarity.

  • Order of CSS code blocks: styles → media queries → pseudo selectors → variants

  • Leave a single line space before each media query, pseudo selector and variant block for legibility.

// Bad ❌
export const LogoGrid = styled('div', {
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gridRowGap: '$6',
gridColumnGap: '$4',
marginBottom: '$7',
'@>s': {
gridTemplateColumns: 'repeat(4, 1fr)',
},
variants: {
isRed: {
true: {
color: 'red',
},
},
},
});
// Good ✅
const $LogoGrid = styled('div', {
d: 'grid',
gtc: 'repeat(2, 1fr)',
grg: '$6',
gcg: '$4',
mb: '$7',
'@>s': {
gtc: 'repeat(4, 1fr)',
},
variants: {
isRed: {
true: {
c: 'red',
},
},
},
});
  • Write as arrow functions.

  • Use implicit returns whenever possible for clarity sake.

  • Write a default export statement at the end of the file.

// Bad ❌
export default const Button = () => {
return <$Button className={className}>{children}</$Button>
}
// Good ✅
const Button = () => <$Button/>
export default Button
// Good ✅
const Button = () => (
<$Button className={className}>{children}</$Button>
)
export default Button
// Good ✅
const Button = () => ({
gear: 'box'
})
export default Button
  • Always destructure the props object.

  • If the variable that is destructured from the prop is also an object, as is usually the case when working with incoming data, destructure the object in the top level of the component. A return statement will be needed in this case. Leave one line of space before the return statement.

// Bad ❌
const MediaWithText = (props) => (
<$MediaWithText>
<Media image={props.image} />
<Text content={props.content} />
</$MediaWithText>
);
// Good ✅
const MediaWithText = ({ image, content }) => (
<$MediaWithText>
<Media image={image} />
<Text content={content} />
</$MediaWithText>
);
// Bad ❌
const HomePage = ({ data }) => (
<$Wrapper>
<MediaWithText image={data.image} content={data.content} />
</$Wrapper>
);
// Good ✅
const HomePage = ({ data }) => {
const { image, content } = data;
return (
<$Wrapper>
<MediaWithText image={image} content={content} />
</$Wrapper>
);
};
  • Oftentimes, you will need to tweak a style of a component to fit into a different context.

  • If the tweak is small (margin, padding, color, font, etc..) you can use the Stitches css prop to pass those styles along.

  • It the change requires more code (layout changes, etc) the css prop is not a great option, as it quickly bloats the component’s code and makes it difficult to parse.

  • If you see that happening, consider adding a variant to the Stitches component instead.

// Bad ❌
const MediaWithText = {( image, content )} => (
<$MediaWithText>
<Media image={image} />
// Way to many styles - the bloat is real.
<Text content={content} css={{
d: 'flex',
fd: 'column',
ai: 'center',
jc: 'center',
c: 'blue',
textDecoration: 'underline',
}}
</$MediaWithText>
)
// Good ✅
const MediaWithText = ({ image, content }) => (
<$MediaWithText>
<Media image={image} />
<Text content={content} css={{ mt: '$3', c: 'blue' }} />
</$MediaWithText>
)
// Good ✅
const MediaWithText = ({ image, content }) => (
<$MediaWithText>
<Media image={image} />
// In this case, we've added a 'layout' variant to the Text component and
// gave it the styles in the first example.
// Now we can simply reference whatever variant we choose
// and it will apply those styles. Much cleaner.
<Text content={content} layout='vertical' />
</$MediaWithText>
)

This is something we’re continually discussing and improving, as it so often depends on the context of the project we’re working on at any given time.

In a nutshell, we’ve adopted a BEM-style naming system (Block-Element-Modifier, for those who are unfamiliar), except it becomes more like:

Component → Element → Modifier

Let’s look at an example in our R&P components folder for a recent project we completed:

Component Naming Example

You can see that the building blocks of the Card component (heading, excerpt, image, etc.) all start with the Component (Card) followed by the element itself. There are also variations of the Card component itself (CardFounder, CardPartner). By starting with the main component name, the components become easier to organize.

There will be an index.js file in every component folder. We choose one main component to be the default export, and make everything else a named export. Here is the index.js file in the Cards folder:

Index.js Example

To keep our import statements easier to read and manage, we’ve created aliases for most of these folders. These are found in our jsconfig.json file in the web folder:

Path Aliases Example

The two that you will use the most are @design and @components. Here is an example of these aliases in action:

File Import Example

This eliminates the need for long file paths. 👍

💡
See something that is out of date or that could be improved?Please let the team know!1. You can create a Github issue2. Pull down the repo and create a PR with your suggested changes implimented.3. Or just let someone know in the R&P Slack Channel.We love making things better.