10 KiB
Style guides and linting
See the relevant style guides for our guidelines and for information on linting:
JavaScript
We defer to Airbnb on most style-related conventions and enforce them with eslint.
See our current .eslintrc for specific rules and patterns.
Common
ESlint
-
Never disable eslint rules unless you have a good reason. You may see a lot of legacy files with
/* eslint-disable some-rule, some-other-rule */
at the top, but legacy files are a special case. Any time you develop a new feature or refactor an existing one, you should abide by the eslint rules. -
Never Ever EVER disable eslint globally for a file
// bad
/* eslint-disable */
// better
/* eslint-disable some-rule, some-other-rule */
// best
// nothing :)
- If you do need to disable a rule for a single violation, try to do it as locally as possible
// bad
/* eslint-disable no-new */
import Foo from 'foo';
new Foo();
// better
import Foo from 'foo';
// eslint-disable-next-line no-new
new Foo();
-
There are few rules that we need to disable due to technical debt. Which are:
-
When they are needed always place ESlint directive comment blocks on the first line of a script, followed by any global declarations, then a blank newline prior to any imports or code.
// bad
/* global Foo */
/* eslint-disable no-new */
import Bar from './bar';
// good
/* eslint-disable no-new */
/* global Foo */
import Bar from './bar';
-
Never disable the
no-undef
rule. Declare globals with/* global Foo */
instead. -
When declaring multiple globals, always use one
/* global [name] */
line per variable.
// bad
/* globals Flash, Cookies, jQuery */
// good
/* global Flash */
/* global Cookies */
/* global jQuery */
- Use up to 3 parameters for a function or class. If you need more accept an Object instead.
// bad
fn(p1, p2, p3, p4) {}
// good
fn(options) {}
Modules, Imports, and Exports
- Use ES module syntax to import modules
// bad
require('foo');
// good
import Foo from 'foo';
// bad
module.exports = Foo;
// good
export default Foo;
- Relative paths: when importing a module in the same directory, a child
directory, or an immediate parent directory prefer relative paths. When
importing a module which is two or more levels up, prefer either
~/
oree/
.
In app/assets/javascripts/my-feature/subdir:
// bad
import Foo from '~/my-feature/foo';
import Bar from '~/my-feature/subdir/bar';
import Bin from '~/my-feature/subdir/lib/bin';
// good
import Foo from '../foo';
import Bar from './bar';
import Bin from './lib/bin';
In spec/javascripts:
// bad
import Foo from '../../app/assets/javascripts/my-feature/foo';
// good
import Foo from '~/my-feature/foo';
When referencing an EE component:
// bad
import Foo from '../../../../../ee/app/assets/javascripts/my-feature/ee-foo';
// good
import Foo from 'ee/my-feature/foo';
-
Avoid using IIFE. Although we have a lot of examples of files which wrap their contents in IIFEs (immediately-invoked function expressions), this is no longer necessary after the transition from Sprockets to webpack. Do not use them anymore and feel free to remove them when refactoring legacy code.
-
Avoid adding to the global namespace.
// bad
window.MyClass = class { /* ... */ };
// good
export default class MyClass { /* ... */ }
- Side effects are forbidden in any script which contains exports
// bad
export default class MyClass { /* ... */ }
document.addEventListener("DOMContentLoaded", function(event) {
new MyClass();
}
Data Mutation and Pure functions
- Strive to write many small pure functions, and minimize where mutations occur.
// bad
const values = {foo: 1};
function impureFunction(items) {
const bar = 1;
items.foo = items.a * bar + 2;
return items.a;
}
const c = impureFunction(values);
// good
var values = {foo: 1};
function pureFunction (foo) {
var bar = 1;
foo = foo * bar + 2;
return foo;
}
var c = pureFunction(values.foo);
-
Avoid constructors with side-effects
-
Prefer
.map
,.reduce
or.filter
over.forEach
A forEach will cause side effects, it will be mutating the array being iterated. Prefer using.map
,.reduce
or.filter
const users = [ { name: 'Foo' }, { name: 'Bar' } ];
// bad
users.forEach((user, index) => {
user.id = index;
});
// good
const usersWithId = users.map((user, index) => {
return Object.assign({}, user, { id: index });
});
Parse Strings into Numbers
parseInt()
is preferable overNumber()
or+
// bad
+'10' // 10
// good
Number('10') // 10
// better
parseInt('10', 10);
CSS classes used for JavaScript
- If the class is being used in Javascript it needs to be prepend with
js-
// bad
<button class="add-user">
Add User
</button>
// good
<button class="js-add-user">
Add User
</button>
Vue.js
Basic Rules
- The service has it's own file
- The store has it's own file
- Use a function in the bundle file to instantiate the Vue component:
// bad
class {
init() {
new Component({})
}
}
// good
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#element',
components: {
componentName
},
render: createElement => createElement('component-name'),
}));
- Don not use a singleton for the service or the store
// bad
class Store {
constructor() {
if (!this.prototype.singleton) {
// do something
}
}
}
// good
class Store {
constructor() {
// do something
}
}
Naming
- Extensions: Use
.vue
extension for Vue components. - Reference Naming: Use camelCase for their instances:
// good
import cardBoard from 'cardBoard'
components: {
cardBoard:
};
- Props Naming: Avoid using DOM component prop names.
- Props Naming: Use kebab-case instead of camelCase to provide props in templates.
// bad
<component class="btn">
// good
<component css-class="btn">
// bad
<component myProp="prop" />
// good
<component my-prop="prop" />
Alignment
- Follow these alignment styles for the template method:
// bad
<component v-if="bar"
param="baz" />
<button class="btn">Click me</button>
// good
<component
v-if="bar"
param="baz"
/>
<button class="btn">
Click me
</button>
// if props fit in one line then keep it on the same line
<component bar="bar" />
Quotes
- Always use double quotes
"
inside templates and single quotes'
for all other JS.
// bad
template: `
<button :class='style'>Button</button>
`
// good
template: `
<button :class="style">Button</button>
`
Props
- Props should be declared as an object
// bad
props: ['foo']
// good
props: {
foo: {
type: String,
required: false,
default: 'bar'
}
}
- Required key should always be provided when declaring a prop
// bad
props: {
foo: {
type: String,
}
}
// good
props: {
foo: {
type: String,
required: false,
default: 'bar'
}
}
- Default key should always be provided if the prop is not required:
// bad
props: {
foo: {
type: String,
required: false,
}
}
// good
props: {
foo: {
type: String,
required: false,
default: 'bar'
}
}
// good
props: {
foo: {
type: String,
required: true
}
}
Data
data
method should always be a function
// bad
data: {
foo: 'foo'
}
// good
data() {
return {
foo: 'foo'
};
}
Directives
- Shorthand
@
is preferable overv-on
// bad
<component v-on:click="eventHandler"/>
// good
<component @click="eventHandler"/>
- Shorthand
:
is preferable overv-bind
// bad
<component v-bind:class="btn"/>
// good
<component :class="btn"/>
Closing tags
- Prefer self closing component tags
// bad
<component></component>
// good
<component />
Ordering
- Order for a Vue Component:
name
props
mixins
directives
data
components
computedProps
methods
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
Vue and Boostrap
- Tooltips: Do not rely on
has-tooltip
class name for Vue components
// bad
<span
class="has-tooltip"
title="Some tooltip text">
Text
</span>
// good
<span
v-tooltip
title="Some tooltip text">
Text
</span>
-
Tooltips: When using a tooltip, include the tooltip directive,
./app/assets/javascripts/vue_shared/directives/tooltip.js
-
Don't change
data-original-title
.
// bad
<span data-original-title="tooltip text">Foo</span>
// good
<span title="tooltip text">Foo</span>
$('span').tooltip('fixTitle');