KEMBAR78
Enforcing coding standards in a JS project | PDF
ENFORCING

CODING 

STANDARDS

in a JS PROJECT

Sebastiano Armeli
@sebarmeli 14/05/2015 - JSConf BD
to enforce
verb (used with object), enforced, enforcing.
to put or keep in force; to compel obedience to:

“to enforce a rule; Traffic laws will be strictly
enforced.”
standard
noun
a rule or principle that is used as a basis for judgment:

“They tried to establish standards for a new approach.”
commit 111111
Author: Sebastiano Armeli
Date: Sun Dec 21 22:08:00
2014 -0500
adding something
commit 2222222
Author: Sebastiano Armeli
Date: Thu Dec 18 15:35:39
2014 -0500
it will work, trust me
myProject
|
|— module1.js
|— module2.js
|— module_3.js
|— module_4.js
|— module5.js
|— package.json
defined by your team
Automate
http://facilitationjapan.com/wp-content/uploads/2013/09/consensus_building.jpg
AD LIBRARY
! IDE (Editorconfig)
! Quality & Style tools (JSHint, JSCS, ESLint)
! Git commits standards
! Build tools (Grunt, Gulp)
! Language - Transpiler (ES6 - Babel)
! Complexity tool (Plato)
Summary 1/2
! Testing (Mocha, Karma)
! Automated Release Flow (Jenkins, NPM)
! Setup script
! Documentation
Summary 2/2
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
.editorconfig
Coding Style
& Quality Tools
Prevent bugs
Improve code maintainability 

& readability
Easy to use
function increment(a) {
return
a + 1;
}
increment(1); // undefined
BUG!!
var x = y = z = "example";
Leaking Variables!!
{
"curly": true,
"eqeqeq": false,
"latedef": true,
"newcap": true,
"noarg": true,
"sub": true,
"boss": true,
"indent": 2,
"noempty": true,
"expr": true,
"eqnull": true,
"esnext": true,
"browser": true,
"white": true,
"undef": true,
"predef": [ “require”, "module", “exports", "CustomEvent"]
}
.jshintrc
{
"curly": true,
"eqeqeq": false,
"latedef": true,
"newcap": true,
"noarg": true,
"sub": true,
"boss": true,
"indent": 2,
"noempty": true,
"expr": true,
"eqnull": true,
"esnext": true,
"browser": true,
"white": true,
"undef": true,
"predef": [ “require”, "module", “exports", "CustomEvent"]
}
.jshintrc
{
"curly": true,
"eqeqeq": false,
"latedef": true,
"newcap": true,
"noarg": true,
"sub": true,
"boss": true,
"indent": 2,
"noempty": true,
"expr": true,
"eqnull": true,
"esnext": true,
"browser": true,
"white": true,
"undef": true,
"predef": [ “require”, "module", “exports", "CustomEvent"]
}
.jshintrc
JSCS
{
"requireSpaceAfterKeywords": ["if", "else",
"for", "while", "do", "switch", "return", "try"],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowMixedSpacesAndTabs": true,
"disallowKeywords": ["with"],
"disallowMultipleVarDecl": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
"maximumLineLength": 80,
"esnext": true
}
.jscsrc
{
"requireSpaceAfterKeywords": ["if", "else",
"for", "while", "do", "switch", "return", "try"],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowMixedSpacesAndTabs": true,
"disallowKeywords": ["with"],
"disallowMultipleVarDecl": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
"maximumLineLength": 80,
"esnext": true
}
.jscsrc
{
"requireSpaceAfterKeywords": ["if", "else",
"for", "while", "do", "switch", "return", "try"],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowMixedSpacesAndTabs": true,
"disallowKeywords": ["with"],
"disallowMultipleVarDecl": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
"maximumLineLength": 80,
"esnext": true
}
.jscsrc
---
parser: babel-eslint
env:
browser: true
node: true
mocha: true
es6: true
.eslintrc
rules:
space-before-blocks: 2
eqeqeq: [2, 'smart']
curly: [2, 'multi-line']
quotes: [2, 'single']
space-after-keywords: 2
no-unused-vars: [2, args: none]
no-comma-dangle: 2
no-unused-expressions: 0
no-multi-spaces: 2
…
rules:
space-before-blocks: 2
eqeqeq: [2, 'smart']
curly: [2, 'multi-line']
quotes: [2, 'single']
space-after-keywords: 2
no-unused-vars: [2, args: none]
no-comma-dangle: 2
no-unused-expressions: 0
no-multi-spaces: 2
…
no-unused-vars: [2, args: none]
function test1(a, b) {
var c, d = 2;
return a + d;
}
test1(1, 2);
Error!!
function test2(a, b, c) {
return a + b;
}
test2(1, 2);
Ok
no-trailing-spaces: 2
no-mixed-spaces-and-tabs: 2
quotes: [2, ‘single’]
indent: [2, 2]
"use strict”;
module.exports = function(context) {
return {
"NewExpression": function(node) {
if (node.callee.name === "Object") {
context.report(node, “Error …”);
}
}
};
};
no-new-object.js
let obj = new Object(); let obj = {};
"use strict”;
module.exports = function(context) {
return {
"NewExpression": function(node) {
if (node.callee.name === "Object") {
context.report(node, “Error …”);
}
}
};
};
no-new-object.js
let obj = {};
Git Commits
(feat | fix | docs | style | refactor | test | chore)(<scope>):

<description>
E.g.

doc(readme): update with additional links.
Changelog
conventional-changelog Changelog.md
commit 7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Author: Sebastiano Armeli <xxx@yyy.com>
Date: Tue Jan 6 11:48:59 2015 -0500
refactor(BaseAd): Removed addToStreamTime method from
BaseAd
commit 7bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Author: Sebastiano Armeli <xxx@yyy.com>
Date: Tue Jan 6 00:04:49 2015 -0500
style(gpt): rearrange for better readability
CHANGELOG.md
Build tool
gulp test / gulp dev
var gulp = require('gulp');
var plugins = require(‘gulp-load-plugins')();
…
gulp.task('eslint', function() {
return gulp.src(['src/**/*.js'])
.pipe(plugins.eslint())
.pipe(plugins.eslint.format())
.pipe(plugins.eslint.failOnError());
});
…
gulpfile.js
gulp es6/src /dist
ES6 ES5
Plato
gulp plato
Testing
describe('#_onContainerResume', function() {
it('should call play when container resumes', function() {
videoAd.views.set(mockedVideoAdMetadata.id, {
play: function() {},
hasBeenPlayed: true
});
sinon.stub(videoAd.views.get(mockedVideoAdMetadata.id), 'play');
videoAd._onContainerResume();
expect(videoAd.views.get(mockedVideoAdMetadata.id).play).to.have
.been.called;
});
});
module.exports = function(config) {
config.set({
basePath: '../',
frameworks: ['mocha', 'fixture'],
files: [
….
],
browsers: ‘Chrome’,
singleRun: false,
preprocessors: {
'**/*.html': ['html2js'],
'**/*.json': ['html2js']
},
sauceLabs: {
…
}
});
};
karma.conf.js
module.exports = function(config) {
config.set({
basePath: '../',
frameworks: ['mocha', 'fixture'],
files: [
….
],
browsers: ‘Chrome’,
singleRun: false,
preprocessors: {
'**/*.html': ['html2js'],
'**/*.json': ['html2js']
},
sauceLabs: {
…
}
});
};
karma.conf.js
module.exports = function(config) {
config.set({
basePath: '../',
frameworks: ['mocha', 'fixture'],
files: [
….
],
browsers: ‘Chrome’,
singleRun: false,
preprocessors: {
'**/*.html': ['html2js'],
'**/*.json': ['html2js']
},
sauceLabs: {
…
}
});
};
karma.conf.js
Automated Release flow
gulp test:ci gulp bump:path
gulp bump:minor
gulp bump:major
Changelog.md
./setup.sh
pre-commit hook
+
npm i && gulp test
Documentation
README.md
CONTRIBUTING.md
Documentation
README.md
CONTRIBUTING.md
/doc
Sebastiano Armeli <…@spotify.com>
Brice Lin <..@spotify.com> <…@gmail.com>
Jason Palmer <..@spotify.com> <..@spotify.com>
Joseph Werle <..@spotify.com> <…@gmail.com>
Olof Kihlberg <…@spotify.com>
Sigfrido Chirinos <…@spotify.com>
.mailmap
Sebastiano Armeli
@sebarmeli

Enforcing coding standards in a JS project