npmのみでSassのstylelint 〜webpackとPostCSS不要編〜

2回目の投稿です。優しくしてください。

[前回の投稿](https://qiita.com/pprhr/items/4d0272790acf66610409)ではwebpack3について書きましたが、今回はその中で使用していた **stylelint** について掘り下げていきます。
最近リリースされたばかりのwebpack4ではなく、あえてstylelint。

なぜstylelintなのか。
前回の投稿ではstylelintをあまり理解せず良さを出しきれていなかったこと、
webpack？PostCSS？という人でも簡単に導入できるので、積極的に取り入れてほしいという思いから。

# そもそもstylelintってなに？
CSSがコーディング規約に準拠しているかどうか機械的に検知するためのnpm製ツール。
設定ルールを元にリンティングしてくれます。
定義されているルールはおよそ160以上。
パーサーとしてPostCSSが使えるため、もともとPostCSSを取り入れている場合は速やかに導入が可能。
今回はPostCSSを使わずstylelintのみで構築します。

:star: [stylelint 公式サイト](https://stylelint.io/)


# 導入するメリット
- **コードスタイルの一貫性を保つことができる**  
  誰でも嫌でもコーディング規約に準拠したコードを書くことができます。  
  可読性・品質が向上することにより、保守性がぐんと増します。
- **潜在的エラーや非推奨パターンを見分けられる**  
  無効な構文、非推奨プロパティ、宣言の重複等、エラーを検知することが可能です。
- **手間を省くことができる**  
  設定ファイルを元に違反しているスタイルを自動修正してくれます。  
  ※ 現在では一部ルールにのみ適用。
- **レビュー負担軽減**  
  コーディング規約に準拠しているか血眼になってチェックする時間と手間が省けます。


# やりたいこと
- CSSやSass独自の構文、スタイルをチェック
- ブロック内コンテンツやプロパティの順序をチェック
- スタイル違反を自動修正


# 必要なプラグインをインストール
yarnの場合は `npm i` を `yarn add` に読み替えてください。

```shell

$ npm i stylelint stylelint-scss stylelint-order
```


# ディレクトリ階層
```text

┃
┣━ sass
┃   ┣━ common
┃   ┃   ┣━ common.scss
┃   ┃   ┣━ _parts.scss
┃   ┃   ...
┃   ┃
┃   ┣━ main
┃   ┃   ┣━ main.scss
┃   ┃   ┣━ _parts.scss
┃   ┃   ...
┃   ┃
┃   ┣━ sub
┃   ┃   ┣━ sub.scss
┃   ┃   ┣━ _parts.scss
┃   ┃   ...
┃   ┃
┃   ...
┃
┣━ package.json
┗━ .stylelintrc.json

```


# 設定ファイルについて

## package.json
npmプラグインを管理するための設定ファイルです。
stylelintを実行するためのエイリアスコマンドを `"scripts"` に設定します。
オプションに `--fix` を付与することで自動修正をしてくれます。

```json:package.json
{
  "name": "hello-webpack",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "stylelint": "stylelint \"sass/**/*.scss\" --fix",
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "stylelint": "^9.1.1",
    "stylelint-order": "^0.8.1",
    "stylelint-scss": "^2.4.0",
  }
}
```

## .stylelintrc.json
リンターの設定ファイルです。

- ignoreFiles  
  無視したいファイルを設定できます。  
- syntax  
  今回はSCSSファイルをチェックするので `scss` を設定します。  
  PostCSSを使っている場合は、syntaxを設定しないと `// comment` が　`/*comment*/` にされてしまうので注意。  
- plugins
  - stylelint-scss ( `scss/Rule...` )  
    SCSS独自の構文ルールを追加。
  - stylelint-order ( `order/Options...` )  
    スタイル順序を設定。
- rules  
  設定したいルールを指定します。  
  ルールの詳細については[公式サイト](https://stylelint.io/user-guide/rules/)をご参照ください。

```json:.stylelintrc.json
{
  "ignoreFiles": [
    "sass/common/*.scss"
  ],

  "syntax": "scss",

  "plugins": [
    "stylelint-scss",
    "stylelint-order"
  ],

  "rules": {
    "color-no-invalid-hex": true,
    "color-hex-case": "lower",
    "color-hex-length": "short",

    "font-family-name-quotes": "always-where-recommended",
    "font-family-no-duplicate-names": true,

    "function-calc-no-unspaced-operator": true,
    "function-comma-space-after": "always-single-line",
    "function-comma-space-before": "never",
    "function-max-empty-lines": 0,
    "function-name-case": [
      "lower",
      {
        "ignoreFunctions": [
          "DXImageTransform.Microsoft.gradient",
          "Alpha"
        ]
      }
    ],
    "function-parentheses-space-inside": "always",
    "function-url-quotes": "always",
    "function-whitespace-after": "always",

    "number-leading-zero": "never",
    "number-no-trailing-zeros": true,

    "string-no-newline": true,
    "string-quotes": "double",

    "length-zero-no-unit": true,

    "unit-case": "lower",
    "unit-no-unknown": true,

    "shorthand-property-no-redundant-values": true,

    "value-keyword-case": "lower",
    "value-list-comma-newline-after": "always-multi-line",
    "value-list-comma-space-after": "always-single-line",
    "value-list-comma-space-before": "never",
    "value-list-max-empty-lines": 0,

    "property-case": "lower",
    "property-no-unknown": true,

    "declaration-bang-space-after": "never",
    "declaration-bang-space-before": "always",
    "declaration-block-no-duplicate-properties": [
      true,
      {
        "ignore": [
          "consecutive-duplicates"
        ]
      }
    ],
    "declaration-block-no-redundant-longhand-properties": true,
    "declaration-block-no-shorthand-property-overrides": true,
    "declaration-block-semicolon-newline-after": "always",
    "declaration-block-semicolon-space-after": "always-single-line",
    "declaration-block-semicolon-space-before": "never",
    "declaration-block-single-line-max-declarations": 1,
    "declaration-block-trailing-semicolon": "always",
    "declaration-colon-space-after": "always",
    "declaration-colon-space-before": "never",

    "block-closing-brace-empty-line-before": "never",
    "block-closing-brace-newline-before": "always-multi-line",
    "block-no-empty": true,
    "block-opening-brace-space-before": "always",

    "selector-attribute-brackets-space-inside": "never",
    "selector-attribute-operator-space-after": "never",
    "selector-attribute-operator-space-before": "never",
    "selector-attribute-quotes": "always",
    "selector-combinator-space-after": "always",
    "selector-combinator-space-before": "always",
    "selector-descendant-combinator-no-non-space": true,
    "selector-list-comma-newline-after": "always",
    "selector-list-comma-newline-before": "never-multi-line",
    "selector-list-comma-space-before": "never",
    "selector-max-empty-lines": 1,
    "selector-pseudo-class-case": "lower",
    "selector-pseudo-class-parentheses-space-inside": "never",
    "selector-pseudo-element-case": "lower",
    "selector-pseudo-element-colon-notation": "single",
    "selector-type-case": "lower",

    "media-feature-colon-space-after": "always",
    "media-feature-colon-space-before": "never",
    "media-feature-name-case": "lower",
    "media-feature-parentheses-space-inside": "always",
    "media-feature-range-operator-space-after": "always",
    "media-feature-range-operator-space-before": "always",

    "media-query-list-comma-newline-after": "always-multi-line",
    "media-query-list-comma-newline-before": "never-multi-line",
    "media-query-list-comma-space-after": "always-single-line",
    "media-query-list-comma-space-before": "never",

    "at-rule-name-case": "lower",
    "at-rule-name-newline-after": "always-multi-line",
    "at-rule-name-space-after": "always-single-line",
    "at-rule-semicolon-newline-after": "always",

    "indentation": 2,
    "max-empty-lines": 2,
    "no-eol-whitespace": true,
    "no-extra-semicolons": true,
    "no-missing-end-of-source-newline": true,


    "scss/at-if-closing-brace-newline-after": "always-last-in-chain",
    "scss/at-mixin-parentheses-space-before": "never",
    "scss/dollar-variable-colon-space-after": "always",
    "scss/dollar-variable-colon-space-before": "never",


    "order/order": [
      "dollar-variables",
      "custom-properties",
      {
 	type: 'at-rule',
 	name: ' extend'
      },
      {
 	type: 'at-rule',
 	name: 'include'
      },
      "declarations",
      "at-variables",
      {
 	type: 'at-rule',
 	name: 'media',
 	hasBlock: true
      },
      "rules"
    ],

    "order/properties-order": [
      "position",
      "width",  
      "margin",
      "font-size",
      "border",
      "content",
      "animation"
      ... omit ...
    ]
  }
}
```


# stylelint実行
```shell:npm
$ npm run stylelint
```
```shell:yarn
$ yarn stylelint
```

---


```scss:before
.sample {
  @media #{$sp} {
    display: none;
  }
  span {
    font-size: 1.0rem;
  }
  @include box-shadow;
  width: 30.0rem;
  font-size: 0.8rem;
  $color: #fff;
  background: $color;
  position: relative;
  @extend %reset-box;
}
```

```scss:after
.sample {
  $color: #fff;
  @extend %reset-box;
  @include box-shadow;
  position: relative;
  width: 30rem;
  font-size: .8rem;
  background: $color;
  @media #{$sp} {
    display: none;
  }
  span {
    font-size: 1rem;
  }
}
```
  
  

:sparkles: