Stylelint

Why and How to Lint CSS

Andrey Sitnik
Evil Martians

Our Projects

Russia

Our Open Source

Linters History

Source code

Parser

Analysis

Errors list

Linter

1978: First Linter, the Lint

1995: JavaScript

// true
1 == "1"

// easy to forget var
count = 1

Linters

CoffeeScript

vs

CoffeeScript :-(

JS Linters Evolution

JSLint

JSHint

ESLint

ESLint: Whitelist

module.exports = {
  'rules': {
    'space-before-function-paren': [2],
    'no-shadow-restricted-names':  [2],
    'computed-property-spacing':   [2],
    'no-empty-character-class':    [2],
    'no-irregular-whitespace':     [2],
    'no-unexpected-multiline':     [2],
    'no-multiple-empty-lines':     [2],
    'no-constant-condition':       [2],
    …

ESLint: Plugins

Source code

Parser

Plugin 1

Errors list

Plugin 2

ESLint: Fixing

if(foo) {
bar()}
if (foo) {
  bar();
}
eslint --fix *.js

The Most Popular Dependencies

  1. mocha
  2. chai
  3. lodash
  4. grunt
  5. gulp
  6. eslint
  7. babel-preset-es2015
  8. request
  9. async
  10. should

CSS Linters

Syntax Support

SCSS

CSSLint

SCSS Lint

CSSComb

CSS

Less

Features

Whitelist

CSSLint

SCSS Lint

CSSComb

Plugins

Fixing

Rules Count

CSSLint

SCSS Lint

CSSComb

38

26

62

Stylelint

Source code

Parser

Plugin 1

Errors list

Plugin 2

ESLint Plugins Look Like …

One Framework to Rule Them All

cssnext

cssnano

Stylelint

PreCSS

CSS Modules

Changeable Parsers

CSS

SCSS

Less

SugarSS

Broken CSS

postcss-browser-reporter

Stylelint Rule is a PostCSS Plugin

const ruleName = "comment-no-empty"

(root, result) => {
  root.walkComments(comment => {
    if ( comment.text && comment.text.length === 0 ) {
      report({ … })
    }
  }
}

Rules White List

module.exports = {
  'rules': {
    'at-rule-name-case': 'lower',
    'at-rule-semicolon-newline-after': 'always',
    'block-closing-brace-newline-after': 'always',
    'color-hex-case': 'lower',
    'color-hex-length': 'short',
    'color-hex-length': 'short',
    'color-no-invalid-hex': true,
    'indentation': 2,
    …

Fixing

.foo {
color:black}
.foo {
  color:black;
}
stylelint --fix *.css

SCSS

CSSLint

SCSS Lint

CSSComb

CSS

Less

Stylelint

Whitelist

CSSLint

SCSS Lint

CSSComb

Plugins

Fixing

Stylelint

CSSLint

SCSS Lint

CSSComb

38

26

62

Stylelint

176

Easy to Write Rules

Stylelint Users

The Most Popular in npm

Why

Senior

Junior

Code Review

Senior

Junior

1. Less Time for Code Review

Stylelint

2. Robot’s Criticism is Less Agressive

3. Sharing Best Practices

module.exports = {
  'extend': 'stylelint-config-wordpress'
}

4. Preventing Errors

'color-no-invalid-hex',
'function-linear-gradient-no-nonstandard-direction',
'time-no-imperceptible',
'property-no-unknown',
'property-no-vendor-prefix',
'declaration-block-no-duplicate-properties',
'declaration-block-no-ignored-properties',
'declaration-block-no-shorthand-property-overrides',
'selector-class-pattern',
'selector-max-compound-selectors',
'selector-pseudo-element-no-unknown',
'selector-type-no-unknown',
'media-feature-no-missing-punctuation',
'no-unsupported-browser-features',
'no-unknown-animations',
'no-indistinguishable-colors',
'no-descending-specificity',
'no-browser-hacks'
#container .foo {
  top: 10px;
}

…

.foo {
/* Expected selector .foo to come before #container .foo */
  top: 0;
}

.foo {
  opacity: 1;
  /* Unsupported in IE 8 */
}
.foo {
  display: inline;
  width: 100px;
  /* Unexpected width with display: inline */
}
.foo {
  margin-top: 10px;
  margin: 0 auto;
  /* Unexpected shorthand margin after margin-top */
}
.foo {
  color: black;
}

…

.bar {
  background: #010101;
  /* Unexpected indistinguishable colors #010101 and black */
}

How

1. Linter in Text Editor

2. lint-staged in Pre-Commit

"scripts": {
  "lint-staged": "lint-staged",
},
"lint-staged": {
  "*.css": "stylelint"
},
"pre-commit": ["lint-staged"]

3. Linting in CI

4. Start with Popular Config

module.exports = {
  'extend': 'stylelint-config-standard'
}

5. Use Third-Party Plugins

module.exports = {
  'plugins': [
    'stylelint-scss'
  ],
  'rules': {
    'scss/selector-no-redundant-nesting-selector': true
  }
}

dustinspecker/awesome-eslint

6. Write Custom Rules

import { utils } from "stylelint"

export const ruleName = namespace("NAME")
export const messages = utils.ruleMessages(ruleName, {
  expected: "ERROR DESCRIPTION",
})

export default function () {
  return (root, result) => {
    /* BODY */
  }
}

Repeating Colors to Variables

colors = { }
return (root, result) => {
  root.walkDecls(decl => {
    decl.value.match(/#[0-9a-f]{3,6}/, color => {
      if ( colors[color] ) {
        utils.report({ … })
      } else {
        colors[color] = true
      }
    })
  })
}

Facebook Custom Rules

  • slow-css-properties
  • filters-with-svg-files
  • use-variables
  • mobile-flexbox

7. Happened Twice — Write a Rule

8. Lint Everything

gulp.task('default', ['lint',
                      'test',
                      'all-translated',
                      'spell-check',
                      'security-audit',
                      'file-size'])

postcss-sorting

div {
  -moz-box-sizing: border-box;
  width: 100%;
  box-sizing: border-box;
  position: absolute;
  -webkit-box-sizing: border-box;
}
div {
  position: absolute;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  width: 100%;
}

yaspeller

$ yaspeller /home/ai/Dev/postcss/README.md
✗ /home/ai/Dev/postcss/README.md 453 ms
-----
Typos: 1
1. transorming (suggest: transforming, transporting)
-----

Node Security Platform

> nsp check

(+) 1 vulnerabilities found
┌───────────────┬───────────────────────────────────────────────────────┐
│               │ ReDoS via long string of semicolons                   │
├───────────────┼───────────────────────────────────────────────────────┤
│ Name          │ tough-cookie                                          │
├───────────────┼───────────────────────────────────────────────────────┤
│ Installed     │ 2.2.2                                                 │
├───────────────┼───────────────────────────────────────────────────────┤
│ Vulnerable    │ >=0.9.7 <=2.2.2                                       │
├───────────────┼───────────────────────────────────────────────────────┤
│ Patched       │ >=2.3.0                                               │
├───────────────┼───────────────────────────────────────────────────────┤
│ Path          │ my-test-project@undefined > honeybadger@1.1.2 > requ… │
├───────────────┼───────────────────────────────────────────────────────┤
│ More Info     │ https://nodesecurity.io/advisories/130                │
└───────────────┴───────────────────────────────────────────────────────┘

Links