\o/

Lint all the things

\o/

 

Reasons to lint

Standardize the code

  • Makes it easier to read
  • Looks like one person wrote the entire code base

Improve code quality

  • Limit cyclomatic complexity
  • Limit function nesting
  • Limit file size

Improve code reviews

  • Reviewer can focus on code instead of style
  • Some things (code duplication, perf, naming*...) are not linted with common tools

Easy to use

  • Does not take long to configure
  • Runs quickly even on a large code base (can be cached)

Riddle me this

const person = {
  age: 27,
  name: 'Alex'
}

function sayHi (person) {
  switch (person.name) {
    case 'Alex':
      const age = person.age
      console.log(`Hello Alex, you are ${age} yo., yet your prez is terrible.`)
      break
    default:
      const age = person.age
      console.log(`Hi ${person.name}. ${age} years old, right?`)
      break
  }
}

sayHi(person)
 const age = person.age;
                         ^^^
SyntaxError: Identifier 'age' has already been declared
    at Object.exports.runInThisContext (vm.js:76:16)
    at Module._compile (module.js:528:28)
    at Object.Module._extensions..js (module.js:565:10)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load (module.js:424:3)
    at Module.runMain (module.js:590:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
    at bootstrap_node.js:509:3

Linters in JS

Configurable

Opinionated

ESLint

  • Works well with modern JS 
  • .eslintrc config file linting files inside folders
  • Multiples style guides available
    • https://github.com/google/eslint-config-google
    • https://github.com/airbnb/javascript
    • https://github.com/Meetic/eslint-config-meetic
  • Override specific rules from a config
  • Extend multiple configs

 

 

 

 

 

 

  • Change errors into warning to highlight particular files to refactor
/* eslint eqeqeq: "off", curly: "warn" */
{
  "extends": ["eslint:recommended", "google"],
  "rules": {
    // Additional, per-project rules...
  }
}
/*eslint complexity: ["error", 2]*/

function a(x) {
    if (true) {
        return x;
    } else if (false) {
        return x+1;
    } else {
        return 4; // 3rd path
    }
}

// recommended in eslint-config-meetic :  complexity: ["error", 6]
  const formIsValid = () => {
    if (!this.firstName) {
      this.firstNameIsError = true;
      return false;
    }
    this.firstNameIsError = false;
    if (!this.lastName) {
      this.lastNameIsError = true;
      return false;
    }
    this.lastNameIsError = false;
    if (!this.address) {
      this.addressIsError = true;
      return false;
    }
    this.addressIsError = false;
    if (!this.city) {
      this.cityIsError = true;
      return false;
    }
    this.cityIsError = false;
    if (!this.postalCode) {
      this.postalCodeIsError = true;
      return false;
    }
    this.postalCodeIsError = false;
    if (!this.country) {
      this.countryIsError = true;
      return false;
    }
    this.countryIsError = false;
    if (!this.phone) {
      this.phoneIsError = true;
      return false;
    }
    this.phoneIsError = false;
    return true;
  };

Fresh example from a MR

/*eslint id-blacklist: ["error", "data", "err", "e", "cb", "callback"] */

var data = {...};

function callback() {
    // ...
}

element.callback = function() {
    // ...
};

var itemSet = {
    data: [...]
};
/*eslint max-nested-callbacks: ["error", 3]*/

foo1(function() {
    foo2(function() {
        foo3(function() {
            foo4(function() {
                // Do something
            });
        });
    });
});

Standard

npm install standard --save-dev
  • No semicolons
  • Other rules to protect you because you don't use semicolons
  • Rest of eslint config

WTF?!

Riddle me this

const works = 'it works'
console.log(works)

(function () {
  console.log('...or does it')
})()
/Users/alexandrebarbier/riddles/riddle-semicolons.js:4
(function () {
^

TypeError: console.log(...) is not a function
    at Object.<anonymous> (/Users/alexandrebarbier/riddles/riddle-semicolons.js:4:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
    at bootstrap_node.js:509:3

Linters in CSS

Wait, CSS is not code ?

 

... right ?

- 6 months after -

I can't maintain this... I'm out

Many linters

  • CSSLint
  • SassLint
  • SCSSLint
  • stylelint (PostCSS)

 

https://github.com/Meetic/stylelint-config-meetic

/* Bad color examples */

.foo {
  color: #FFF; /* use lowercase for hex colors */
}

.bar {
  color: #ffffff; ; /* use short notation for hex colors */
}

.baz {
  color: #fffffz; /* invalid hex are disallowed */
}

.qux {
  color: rebeccapurple; /* named color are disallowed */
}

/* Good color example (short notation, lowercase, not a named color) */

.foo {
  color: #fff;
}
/* Bad number */

.foo {
  line-height: .5; /* use a leading zero for fractional numbers less than 1 */
}

.bar {
  top: 3.1234567px; /* number of decimal places is limited to 6 */
}

.baz {
  top: 1.0px; /* no trailing zeros allowed */
}

.qux {
  top: 0px; /* don't use units for zero lengths */
}

/* Good number */

.foo {
  line-height: 0.5; /* use a leading zero for fractional numbers less than 1 */
}

.bar {
  top: 3.123456px; /* number of decimal places is limited to 6 */
}

.baz {
  top: 1px; /* no trailing zeros allowed */
}

.qux {
  top: 0; /* don't use units for zero lengths */
}

Linters in HTML

What could possibly go wrong ?

HTML is simple, right?

  • Let's run htmlhint in our beautiful project
         <meetic-icon
          icon-name="'apple'"
          class="funnel-node-finish__icon">
+        </meetic-icon>
       </div>
     </a>
     <a
@@ -48,6 +49,7 @@
         <meetic-icon
          icon-name="'windows'"
          class="funnel-node-finish__icon">
+        </meetic-icon>
       </div>
     </a>

Oops

    ng-class="'account-nav__interaction--' + $index"
    ui-sref="dating.profile.page(::{aboid: member.get('aboid'), state: 'interactions', path: $ctrl.statePathToAboids})"
    ng-style="::{backgroundImage: 'url(' + $ctrl.getMemberProfilePictureUrl(member, 'profile_list_card') + ')'}">
-  <a/>
+  </a>

Really ?

         <span
          class="account__summary-city"
-         class="account__summary-city"
          ng-bind="::$ctrl.city">
         </span>

You should stop cmd + shift + d 

ead9d684 (n.labyt          1) <form
ead9d684 (n.labyt          2)  class="inbox-thread-inputs"
ead9d684 (n.labyt          3)  name="messageForm"
ead9d684 (n.labyt          4)  novalidate
6c530eb5 (Matthieu Kluj    5)  ng-submit="$ctrl.sendMessage()">
64a07c3b (Antoine Jackson  6)   <div class="inbox-thread-inputs__input-wrapper">
64a07c3b (Antoine Jackson  7)     <input
64a07c3b (Antoine Jackson  8)      class="inbox-thread-inputs__input"
64a07c3b (Antoine Jackson  9)      type="text"
ead9d684 (n.labyt         10)      required
fcfca375 (Jenkins         11)      placeholder="{{::'dating.inbox.thread.input-placeholder' | translate:{ nickname: $ctrl.nickname} }}"
d121c391 (Matthieu Kluj   12)      meetic-auto-focus
ead9d684 (n.labyt         13)      ng-model="$ctrl.inboxInputMessage">
64a07c3b (Antoine Jackson 14)    </meetic-icon>
c4310873 (Antoine Jackson 15)   </div>
64a07c3b (Antoine Jackson 16)   <button
64a07c3b (Antoine Jackson 17)    type="submit"
7d527283 (Oleg SKLYANCHUK 18)    class="inbox-thread-inputs__send-button"
7d527283 (Oleg SKLYANCHUK 19)    ng-disabled="messageForm.$invalid">
7d527283 (Oleg SKLYANCHUK 20)     <meetic-icon
7d527283 (Oleg SKLYANCHUK 21)      class="inbox-thread-inputs__send-icon"
7d527283 (Oleg SKLYANCHUK 22)      icon-name="'send'">
7d527283 (Oleg SKLYANCHUK 23)     </meetic-icon>
64a07c3b (Antoine Jackson 24)   </button>
c4310873 (Antoine Jackson 25) </form>

git blame it on the alcohol

JSX 

https://github.com/jkroso/eslint-plugin-jsx

Lint workflow 

The sooner, the better

  • Install lint extension in your code editor (atom, vim...)
  • Use lint with githooks (pre-commit)
  • Lint on CI only as a safety net (--no-verify)
#!/usr/bin/env bash

windows() { [[ -n "$WINDIR" ]]; }

DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
ESLINT="$DIR/../../node_modules/.bin/eslint"
# Running ES lint before committing
if windows; then
  JS_COUNT=$(git diff --name-only --cached | findstr -i ".js");
else
  JS_COUNT=$(git diff --name-only --cached | grep -i "\.js$");
fi
if [[ "$JS_COUNT" ]]; then
  echo "Linting JavaScript..."

  "$ESLINT" '*.js' '{app,config,gulp}/**/*.js' --cache --cache-location 'cache/.eslintcache'
  if [[ $? = 0 ]]; then
    echo "✔ ES lint passed"
    exit 0
  else
    echo "✘ ES lint failed. Please check your code."
    exit 1
  fi
fi

Scary githook script

Overcommit

 

Written in ruby, library of githooks configurable by .yml

 

https://github.com/brigade/overcommit

PreCommit:
  EsLint:
    enabled: true
    command: ['eslint', 'app/scripts']
    flags: ['--fix', '--format=compact']
    include:
      - '**/*.js'
      - '.eslintrc'

ghooks

Written on JS, configure inside your package.json

https://github.com/gtramontina/ghooks

{
  …
  "config": {
    "ghooks": {
      "pre-commit": "gulp lint",
      "commit-msg": "validate-commit-msg",
      "pre-push": "make test",
      "post-merge": "npm install",
      "post-rewrite": "npm install",
      …
    }
  }
  …
}

deck

By Alexandre BARBIER