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');