L
inting
MFAO

Redfoo singing LMFAO's hit, Sexy and I Know It
(Sexy Code and I Know It!)
Modern Web (Triangle)- 4/18/2017
Justin Ray
@thejustinaray

Following Along?

What's a Linter?

Using a lint roller on a cat

Software that checks and cleans your code

Formatting/Preferences

  • Spacing
  • Semis
  • Vars on top

Syntactic Errors

  • Missing Braces/Parens
  • Undefined Variables/Functions

Stylistic Preferences

  • Object shorthand
  • Template literals
  • Yoda Conditionals
But wait
There's More
Linters do so much more
They improve your code!
  • Remove Debugging Code
    • Console, Debugger statements
  • Find Dead Code
    • Unused vars, Unreachable code
  • Fix Runtime Errors

meh, I don't need it

Ron Burgandy saying 'Really'

                        // one-equals-two.js
                        let one = 1;
                        let two = 2;
                        if (one = two) {
                            console.log('This should never print!');
                        } else {
                            console.log('Phew, the world makes sense');
                            // You know you're writing javascript, right?
                        }
                    

                        [.../LintingMFAO-examples]$ node one-equals-two.js
                        This should never print!
                    
Screenshot of Atom easily identifying the bug via the ESLint plugin

                        // print-one.js
                        // Example from ESLint.org
                        console.log('printOne:', printOne());

                        function printOne() {
                            try {
                                return 1;
                            } catch (err) {
                                return 2;
                            } finally {
                                return 3;
                            }
                        }
                    

                        [.../LintingMFAO-examples]$ node printOne.js
                        printOne: 3
                    
Screenshot of Atom easily identifying the bug via the ESLint plugin

And the list goes on

  • eqeqeq
  • no-script-url
  • new-cap

Styling is just the beginning

  • js is not compiled!
  • Collections of best practices
  • No one knows it all, use the community

ESLint Setup

Michael Buffer saying his trademark 'Let's Get Ready to Rumble'

ESLint site is Awesome!

Screenshot of ESLint Homepage with Getting Started,
                            User Guide, and Search Annotated

Installation


                        # Local Install (Recommended)
                        npm init
                        npm install eslint --save-dev
                        # -or-
                        yarn init
                        yarn add eslint --dev

                        # Global Install
                        npm install -g eslint
                        # -or-
                        yarn global add eslint
                    

Initialization


                        node_modules/.bin/eslint --init
                        # -or-
                        eslint --init
                    

                        [.../LintingMFAO-examples]$ node_modules/.bin/eslint --init

                        ? How would you like to configure ESLint?
                        ❯ Answer questions about your style
                          Use a popular style guide
                          Inspect your JavaScript file(s)

                        ? How would you like to configure ESLint? Answer questions about your style
                        ? Are you using ECMAScript 6 features? Yes
                        ? Are you using ES6 modules? No
                        ? Where will your code run? Browser
                        ? Do you use CommonJS? No
                        ? Do you use JSX? No
                        ? What style of indentation do you use? Spaces
                        ? What quotes do you use for strings? Single
                        ? What line endings do you use? Unix
                        ? Do you require semicolons? Yes
                        ? What format do you want your config file to be in? (Use arrow keys)
                        ❯ JavaScript
                          YAML
                          JSON

                    

Config File Formats

  • .eslintrc.js
  • .eslintrc.yaml
  • .eslintrc.yml
  • .eslintrc.json
  • .eslintrc (Deprecated)
  • package.json

                        module.exports = {
                            "env": {
                                "browser": true,
                                "es6": true
                            },
                            "extends": "eslint:recommended",
                            "parserOptions": {
                                "sourceType": "module"
                            },
                            "rules": {
                                "indent": [
                                    "error",
                                    4
                                ],
                                "linebreak-style": [
                                    "error",
                                    "unix"
                                ],
                                "quotes": [
                                    "error",
                                    "single"
                                ],
                                "semi": [
                                    "error",
                                    "always"
                                ]
                            }
                        };
                    

Running ESLint


                        // welcome-to-eslint.js
                        var foo = bar;
                    

                        [.../LintingMFAO-examples]$ node_modules/.bin/eslint welcome-to-eslint.js
                    

                        .../LintingMFAO-examples/welcome-to-eslint.js
                          1:5   error  'foo' is assigned a value but never used  no-unused-vars
                          1:11  error  'bar' is not defined                      no-undef

                        ✖ 2 problems (2 errors, 0 warnings)
                    

How does it work?

Mechanic slides out from under car on creeper

Basically, it

  • Parses your code into an Abstract Syntax Tree
  • AST nodes are linted by a set of configurable rules

                        module.exports = {
                            // ...
                            "rules": {
                                "indent": [    // Rule Name
                                    "error",   // Warning Type: off (0), warn (1), error (2)
                                    4          // Options
                                ],
                                "new-cap": ["error", {  // Object Options
                                    "capIsNew": false
                                }],
                                "quotes": ["error",
                                    "single ", {        // Multiple Option Params
                                    "allowTemplateLiterals": true
                                }],
                            }
                        };
                    
  • Rules are well documented (eslint.org)
  • All rules are similarly configured
    (I'm looking at you jshint!)

Rules are defined

  • ESLint core
  • In-repo custom rules
  • Plugins (npm packages)

Plugins?

Tell me more

Plugins Can Provide

  • List(s) of rules and configuration
  • Custom Rules
  • Custom environments (e.g. testing)
  • Processors (Custom handlers)

Generally, you'll start with a plug-in and customize


                        module.exports = {
                            // env, etc

                            // Angular
                            extends: 'plugin:angular/johnpapa',
                            extends: 'plugin:angular/bestpractices',

                            // Ember
                            extends: [
                                'eslint:recommended',
                                'plugin:ember-suave/recommended'
                            ],

                            // Node
                            extends: 'plugin:node/recommended',

                            // React
                            extends: 'plugin:react/recommended',

                            // Nightmare-mode 😰
                            extends: 'nightmare-mode',

                            // Many More ...

                            rules: {
                                // project-specific settings
                                // plugin-overrides!
                            }
                        };
                    

Linting Hat Trick

(Photo: James Guillory, USA TODAY Sports)

Same linting results

  • Command Line
  • Editor
  • Build/CI (build, test, or pre-push)

Command Line Linting


                        [.../LintingMFAO-examples]$ node_modules/.bin/eslint index.js                                                                                                          *[master]

                        .../LintingMFAO-examples/index.js
                          1:5   error  'foo' is assigned a value but never used  no-unused-vars
                          1:11  error  'bar' is not defined                      no-undef

                        ✖ 2 problems (2 errors, 0 warnings)
                    

Editor Linting

Screenshot of ESLint editor integrations list
(http://eslint.org/docs/user-guide/integrations#editors)

Atom ESLint Package

  • Easily recognizable errors/warnings
  • Deep link to eslint.org rule docs
  • Auto-fix on save
  • Supports eslint plugins
  • Supports multiple versions of ESLint

Atom ESLint Screenshots

Screenshot of Atom ESLint Plugin - Editor Popover Linter Error Screenshot of Atom ESLint Plugin - Linter Error Pane

Build/CI Linting

Different based on your environment and framework

All approaches can benefit from pre-push hook

Git pre-push hook

  • Run your linter (and tests) before any git push
  • Keep your baseline consistent (Esp in teams)
  • npm package writes a local hook on install

pre-push setup


                        # Local Install (Recommended)
                        npm init
                        npm install pre-push --save-dev
                        # -or-
                        yarn init
                        yarn add pre-push --dev
                    

pre-push setup cont.


                        {
                          // ...
                          "scripts": {
                            "pretest": "npm run lint",
                            "test": "echo \"Unit tests go here\"",
                            "lint": "eslint *.js",
                            "lint-pretty": "eslint --format table *.js"
                          },
                          "pre-push": ["test"],
                          // ...
                        }
                    

Not just for JS!

Ron Burgandy saying 'I don\'t believe you'

Syntax/Formatting

  • Indentation
  • Mis-matched quotes

Better templates

  • Require alt attributes
  • No hard-coded strings (i18n)
  • invalid-interactive

Security

  • triple-curlies
  • rel noopener
  • style-concatenation

Our Journey

  • Consistent baseline
  • Community approach to best practices
  • cli tooling
  • Editor feedback
  • CI linting (tests, builds, etc.)
  • Linting for js, templates, etc.

Your Code is Sexy

Outro sceen of LMFAO's video, Sexy and I Know It
(and you know it)
Justin Ray
@thejustinaray

Extra Credit

Linter History

A screenshot of wikipedia in Internet Explorer 6
(Photo: Microsoft/ Wikipedia)
  • jslint
  • jshint
  • jscs
  • eslint

Configuring ESLint

Fingers sliding a toggle switch back and forth

Config Hierarchy

  1. Inline (In-Code) Config
  2. Command Line Options
  3. Config Files
  4. Parent Directories Config
    • Opt out
      root: true
  5. ~/.eslintrc.js
    • (If no other config is found)

Hierarchy makes it simple to support

  • Isomorphic Javascript Projects
  • Different styles within a repo

Project Flexbility

  • your-app/
    • .eslintrc.js (Base Setup/Plugins)
    • app/
      • .eslintrc.js (Browser env/Strict rules)
    • server/
      • .eslintrc.js (node env/CommonJS)
    • tests/
      • .eslintrc.js (lenient/globals)

Configuration Tips

  • Use js (or a format that supports comments)
  • Only use ignores at root (Can be non-determinalistic otherwise)
  • Keep the number of configs/cascades managable

More Tooling

Tim 'the tool man' Taylor with a power tool

Autofix

  • Automatically fix errors
  • Some errors are not auto-fixable
  • Great for individual file(s)
  • YMMV as a migration tool

                        [.../LintingMFAO-examples]$ node_modules/.bin/eslint welcome-to-eslint.js

                        .../LintingMFAO-examples/welcome-to-eslint.js
                          5:5   error  'foo' is assigned a value but never used      no-unused-vars
                          5:11  error  'bar' is not defined                          no-undef
                          7:10  error  'soMuchLint' is defined but never used        no-unused-vars
                          8:3   error  Expected indentation of 4 spaces but found 2  indent
                          8:16  error  Infix operators must be spaced                space-infix-ops
                          8:17  error  Strings must use singlequote                  quotes
                          8:24  error  Strings must use singlequote                  quotes
                          8:32  error  Missing semicolon                             semi

                        ✖ 8 problems (8 errors, 0 warnings)

                        [.../LintingMFAO-examples]$ tail -3 welcome-to-eslint.js
                        function soMuchLint(choice) {
                          return choice?"tabs":"spaces"
                        }
                    

                        [.../LintingMFAO-examples]$ node_modules/.bin/eslint --fix welcome-to-eslint.js

                        .../LintingMFAO-examples/welcome-to-eslint.js
                          5:5   error  'foo' is assigned a value but never used  no-unused-vars
                          5:11  error  'bar' is not defined                      no-undef
                          7:10  error  'soMuchLint' is defined but never used    no-unused-vars

                        ✖ 3 problems (3 errors, 0 warnings)

                        [.../LintingMFAO-examples]$ tail -3 welcome-to-eslint.js
                        function soMuchLint(choice) {
                            return choice ? 'tabs' : 'spaces';
                        }
                    

eslint-nibble

  • Categorize errors
  • Tackle rules one at a time

                        [../LintingMFAO-examples]$ node_modules/.bin/eslint-nibble *.js
                        no-constant-condition: 1|
                        no-cond-assign:        1|
                        no-unsafe-finally:     1|
                        no-unused-vars:        1|
                        no-undef:              1|


                        3 files checked.  0 passed.  3 failed.  0 warnings.  5 errors.

                        ? Type in the rule you want to focus on no-undef

                          ✘  http://eslint.org/docs/rules/no-undef  'bar' is not defined
                          .../LintingMFAO-examples/welcome-to-eslint.js:4:11
                          var foo = bar;
                                     ^


                        ✘ 1 problem (1 error, 0 warnings)


                        Errors:
                          1  http://eslint.org/docs/rules/no-undef
                    

npm-scripts

  • Define scripts in package.json
  • Easy to run
  • Customize eslint cli (format, rules, etc.)

npm-scripts - ESLint format


                        {
                          "name": "lmfao-examples",
                          // ...
                          "main": "index.js",
                          "scripts": {
                            "pretest": "npm run lint",
                            "test": "echo \"Error: no test specified\" && exit 1",
                            "lint": "eslint *.js",
                            "lint-pretty": "eslint --format table *.js"
                          },
                          // ...
                        }
                    

npm-scripts - ESLint format output


                        [.../LintingMFAO-examples]$ npm run lint-pretty                                                                                                      *[master]

                        > lmfao-examples@0.0.1 lint-pretty .../LintingMFAO-examples
                        > eslint --format table *.js


                        .../LintingMFAO-examples/index.js

                        ║ Line     │ Column   │ Type     │ Message                                                │ Rule ID              ║
                        ╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢
                        ║ 1        │ 5        │ error    │ 'foo' is assigned a value but never used.              │ no-unused-vars       ║
                        ║ 1        │ 11       │ error    │ 'bar' is not defined.                                  │ no-undef             ║

                        ╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
                        ║ 2 Errors                                                                                                       ║
                        ╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢
                        ║ 0 Warnings                                                                                                     ║
                        ╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝