Stylelint

Как и зачем линтить CSS

Андрей Ситник,
Злые марсиане

Наш опенсорс

История человечества

10К

40К

250К

Неолитическая революция

История линтеров

Исходный код

Парсер

Анализ

Список ошибок

Линтер

1978 — первый линтер Lint

1995 — JavaScript

// true
1 == "1"

// забыт var
count = 1

Линтеры

CoffeeScript

или

CoffeeScript :-(

Эволюция линтеров

JSLint

JSHint

ESLint

ESLint: белый список

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: модульность

Исходный код

Парсер

Плагин 1

Список ошибок

Плагин 2

ESLint: автоисправление

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

Самые популярные зависимости

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

Линтеры CSS

Поддержка синтаксисов

SCSS

CSSLint

SCSS Lint

CSSComb

CSS

Less

Функции

Белый список

CSSLint

SCSS Lint

CSSComb

Модульность

Исправление

Количество правил

CSSLint

SCSS Lint

CSSComb

38

26

62

Stylelint

Исходный код

Парсер

Плагин 1

Список ошибок

Плагин 2

На что похожа модульность?

Единый фреймворк

Полифилы

Минификация

Линтинг

Примеси

Изоляция

Сменные парсеры

CSS

SCSS

Less

SugarSS

Битый CSS

Правило Stylelint — плагин PostCSS

const ruleName = "comment-no-empty"

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

Белый список правил

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,
    …

Исправление

.foo {
color:black}
.foo {
  color:black;
}
stylefmt *.css

postcss-browser-reporter

SCSS

CSSLint

SCSS Lint

CSSComb

CSS

Less

Stylelint

Белый список

CSSLint

SCSS Lint

CSSComb

Модульность

Исправление

Stylelint

CSSLint

SCSS Lint

CSSComb

38

26

62

Stylelint

168

Легко писать новые правила

Пользователи Stylelint

Популярность

Зачем линтить

Cеньор

Юниор

Код-ревью

Cеньор

Юниор

1. Меньше времени на код-ревью

Stylelint

«Я понимаю, что ты думаешь , но »

2. Критика роботов приятнее

3. Обмен практиками

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

4. Поиск ошибок

'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 come before #container .foo */
  top: 0;
}

.foo {
  opacity: 1;
  /* Features like this, which is 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 */
}

Как линтить

1. Линтер в текстовом редакторе

2. lint-staged — перед коммитом

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

3. Линтер в CI

4. Начните с популярного конфига

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

5. Добавляйте плагины

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

dustinspecker/awesome-eslint

6. Линтер — стайл-гайд

«В стайл-гайде не должно быть правил, которые нельзя описать в алгоритме»

7. Делайте исключения

.foo.is-started {
  /* stylelint-disable no-unknown-animations */
  /* Animation will be generated in JS*/
  animation-name: js-generated-path;
}  
.foo.is-started {
  /* stylelint-disable no-unknown-animations */
  animation-name: js-generated-path;
  /* Expected comment reason after `stylelint-disable` comment */
}

8. Пишите свои правила

import { utils } from "stylelint"

export const ruleName = namespace("ИМЯ")
export const messages = utils.ruleMessages(ruleName, {
  expected: "ТЕКСТ ОШИБКИ",
})

export default function () {
  return (root, result) => {
    /* ЛОГИКА */
  }
}

Цвета повторяются — в переменные

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
      }
    })
  })
}

Правила Фейсбука

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

9. Два раза ошиблись — в правило

10. Линтите всё

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                │
└───────────────┴───────────────────────────────────────────────────────┘

Ссылки

Stylelint — как и зачем линтить CSS

By Andrey Sitnik

Stylelint — как и зачем линтить CSS

  • 9,703