Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Enforcing coding standards in a JS project

Enforcing coding standards in a JS project

Talk given at JSConf Budapest (Budapest, Hungary) - May 2015

Transcript

  1. ENFORCING 
 CODING 
 STANDARDS 
 in a JS PROJECT

    Sebastiano Armeli @sebarmeli 14/05/2015 - JSConf BD
  2. 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.”
  3. None
  4. None
  5. standard noun a rule or principle that is used as

    a basis for judgment: “They tried to establish standards for a new approach.”
  6. None
  7. 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
  8. myProject | |— module1.js |— module2.js |— module_3.js |— module_4.js

    |— module5.js |— package.json
  9. None
  10. defined by your team Automate

  11. http://facilitationjapan.com/wp-content/uploads/2013/09/consensus_building.jpg

  12. AD LIBRARY

  13. None
  14. None
  15. ! 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
  16. ! Testing (Mocha, Karma) ! Automated Release Flow (Jenkins, NPM)

    ! Setup script ! Documentation Summary 2/2
  17. None
  18. root = true [*] indent_style = space indent_size = 2

    end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true .editorconfig
  19. Coding Style & Quality Tools

  20. Prevent bugs Improve code maintainability 
 & readability Easy to

    use
  21. function increment(a) { return a + 1; } increment(1); //

    undefined BUG!!
  22. var x = y = z = "example"; Leaking Variables!!

  23. None
  24. { "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
  25. { "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
  26. { "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
  27. JSCS

  28. { "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
  29. { "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
  30. { "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
  31. None
  32. --- parser: babel-eslint env: browser: true node: true mocha: true

    es6: true .eslintrc
  33. 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 …
  34. 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 …
  35. 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
  36. no-trailing-spaces: 2 no-mixed-spaces-and-tabs: 2 quotes: [2, ‘single’] indent: [2, 2]

  37. "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 = {};
  38. "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 = {};
  39. None
  40. Git Commits (feat | fix | docs | style |

    refactor | test | chore)(<scope>): <description> E.g. doc(readme): update with additional links.
  41. 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
  42. CHANGELOG.md

  43. Build tool gulp test / gulp dev

  44. 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
  45. None
  46. gulp es6 /src /dist ES6 ES5

  47. Plato gulp plato

  48. Testing

  49. 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; }); });
  50. module.exports = function(config) { config.set({ basePath: '../', frameworks: ['mocha', 'fixture'],

    files: [ …. ], browsers: ‘Chrome’, singleRun: false, preprocessors: { '**/*.html': ['html2js'], '**/*.json': ['html2js'] }, sauceLabs: { … } }); }; karma.conf.js
  51. module.exports = function(config) { config.set({ basePath: '../', frameworks: ['mocha', 'fixture'],

    files: [ …. ], browsers: ‘Chrome’, singleRun: false, preprocessors: { '**/*.html': ['html2js'], '**/*.json': ['html2js'] }, sauceLabs: { … } }); }; karma.conf.js
  52. module.exports = function(config) { config.set({ basePath: '../', frameworks: ['mocha', 'fixture'],

    files: [ …. ], browsers: ‘Chrome’, singleRun: false, preprocessors: { '**/*.html': ['html2js'], '**/*.json': ['html2js'] }, sauceLabs: { … } }); }; karma.conf.js
  53. Automated Release flow gulp test:ci gulp bump:path gulp bump:minor gulp

    bump:major Changelog.md
  54. ./setup.sh pre-commit hook + npm i && gulp test

  55. Documentation README.md CONTRIBUTING.md

  56. Documentation README.md CONTRIBUTING.md

  57. /doc

  58. 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
  59. None
  60. Sebastiano Armeli @sebarmeli