ズボラでも綺麗なSCSSを書きたい！(Stylelint v15)

## はじめに

:::note
2023/3/28 追記 v15対応するように加筆修正しました。
:::

私は、コードを書く上で、

- 半角スペースがない！:angry:
- ここは空行入れないと！:angry:
- ここだけインデント違うんですけど！:angry:

とか、割と細かいところが気になります。

CSSに関して言えば、Hexは小文字にしたいし、0に単位はつけたくないし、1未満の値は`0`を頭につけたいし、プロパティの前には空行を入れたいし、プロパティ記述順も気にしたい。。。
メンテナブルなコードを常に書きたい（願望）

しかし、特にプロパティ記述順なんかは実際に書いているとなんだかんだブレるもので、なかなか統一するのは難しいものです。

そこで、私はStylelintを使っているのですが、最近Visual Studio Codeさんがversion 14に上げろ上げろ言うので、調べつつまとめてみました。

## 成し遂げたいこと

- Stylelint ~~v14~~ v15を導入する
  - v13使ってたらVSCodeに怒られたのでアップデートする
  - プロパティ記述順も揃える
- Visual Studio CodeでSCSSを書く際、リアルタイムでエラーを出力して、保存時に直す

### Stylelint v14の破壊的変更

https://stylelint.io/migration-guide/to-14/

開発環境にStylelint v13が導入されていましたが、何やらセキュリティ上の問題があったらしく、v14ではv13系から大きな変更が加わったようです。

> - the syntax option and automatic inferral of syntax were removed
> syntaxオプションとsyntax自動判定を削除
> - Node.js 10 support was dropped
> Node.js 10のサポートを終了
> - the rules deprecated in 13.7.0 were removed
> 13.7.0で非推奨となったルールを削除
> - the configOverrides option was removed
> configOverridesオプションを削除
> - the function-calc-no-invalid rule was removed
> function-calc-no-invalidルールを削除

だそうです。

#### syntaxオプションとsyntax自動判定を削除

SCSSやSassなどの純粋なCSS以外に対して使用したい場合は、それらに対応するためのプラグインやカスタムsyntaxをインストールしなければならなくなりました。

#### Node.js 10のサポートを終了

Nodeは以下のバージョン以上を使います。

- 12.20.0
- 14.13.1
- 16.0.0

#### 13.7.0で非推奨となったルールを削除

非推奨ルールってなんやねんと思った方は[こちら](https://stylelint.io/changelog/#1370)を参照ください。

#### `configOverrides`オプションを削除

代わりに[`overrides`プロパティ](https://stylelint.io/user-guide/configure/#overrides)を使います。

#### `function-calc-no-invalid`ルールを削除

configファイルから上記ルールを削除する必要があります。

### vscode-stylelintも変わった

むしろ知らなかったのですが、vscode-stylelint 0.x系はStylelintがバンドルされていたようで、これが無くなります。

また、1.x系は、最低でもStylelint 14を使用することが想定されているため、それ以前のバージョンのStylelintの使用はサポートしていません。
Stylelintはさっさと14系にした方が良さそうです。

### Stylelint v15の変更

https://stylelint.io/migration-guide/to-15/

v15では以下の重要な変更がなされました。

- スタイルルールの廃止
- `declaration-property-value-no-unknown`ルールの追加
- `processors`設定プロパティを削除
- Node.js 12のサポート終了
- `override.extends`の振る舞いを変更

#### スタイルルールの廃止

文体に関する[76個のルール](https://stylelint.io/user-guide/rules/#deprecated)が非推奨となりました。

これにより、文体を整える機能はPrettierなどに任せて、Stylelintでは、文体以外のエラー回避と規約遵守を目的とするルールに重きを置いて開発が進められていく、とのことです。

v15では**非推奨であることの警告が表示される**だけで、実際に使えなくなるわけではありませんが、次のメジャーリリース（恐らくv16のこと）で本格的に削除され、使えなくなるようです。
そのため、今のうちに文体に関するルールは削除しておくと良さそうです。

#### `declaration-property-value-no-unknown`ルールの追加

このルール、CSSの仕様上存在しない不明なプロパティと値のペアをチェックするものです。
例えば、以下のようなものに対して反応します。
`top`プロパティには、`<length>`、`<percentage>`、`auto`が許可されており、仕様上`<color>`を指定することはできないためです。

```scss
a {
  top: red;
}
```

#### `processors`設定プロパティを削除

CSS-in-JSなどのCSSコンテナをサポートするための`processors`設定プロパティが削除されました。

#### Node.js 12のサポート終了

Node.js 12のサポートが終了したため、Stylelintでもサポートが終了しました。

#### `overrides.extends`の動作変更

`overrides.plugins`と同様に、`overrides.extends`の動きがリプレースからマージに変更されました。

## Stylelintの導入

### インストール

#### まとめ

```bash
$ yarn add --dev stylelint stylelint-config-recommended-scss stylelint-order postcss@8
```

#### それぞれの説明

Stylelintを導入します。

```bash
$ yarn add --dev stylelint
```

SCSSを使っているので、**stylelint-config-recommended-scss**を導入します。
これは、[avoid errors](https://stylelint.io/user-guide/rules/#avoid-errors)のための最低限のルールをまとめたものです。

```bash
$ yarn add --dev stylelint-config-recommended-scss
```

プロパティ記述順をソートするため、**stylelint-order**を導入します。
Bootstrapなどを参考にソート順を予め決められるものもありますが、気に入らなかったのでソートするためのモジュールだけインストールします。

```bash
$ yarn add --dev stylelint-order
```

**postcss@8**を導入します。
なんでやねんと思った方はこちらをご参照ください。

https://github.com/stylelint-scss/stylelint-config-standard-scss/issues/5

```bash
$ yarn add --dev postcss@8
```

syntaxとして登録する必要があるはずの**postcss-scss**は、後述の拡張モジュールに含まれているためインストールしなくてOKです。

インストールが終わったら、Stylelintのconfigを記述します。

```json:.stylelintrc
{
  "extends": [
    "stylelint-config-recommended-scss"
  ],
  "plugins": [
    "stylelint-order"
  ]
}
```

### ルールの設定

筆者のこだわりも多く含むため、万人受けはしないかもしれませんが、一応記載します。

#### Stylelintのルール

`"rules"`に追記して、ちょっとカスタムします。

##### `"alpha-value-notation": "number"`

アルファ値を小数で表します。

##### `"color-hex-length": "short"`

16進数で短記法が使える場合は使います。

##### `"length-zero-no-unit": true`

値が0のとき、length単位は省略します。

##### `"rule-empty-line-before": ["always-multi-line", {"ignore": ["after-comment", "first-nested"]}]`

ルールの前に1行空けます。
ただし、コメントの後やネストの一番最初の行の場合は無視します。

##### `"selector-class-pattern": "^(ly|bl|el|hp|un|js|is|)(_[a-zA-Z0-9]+)+(__[a-zA-Z0-9]+)?$"`

セレクタクラスのパターンを指定します。

PRECSS向けの正規表現です。
※PRECSSは拡張性が売りなので、正規表現でガチガチに固めるのは良くないかも知れませんが一例として記載します。

FLOCSSだとこんな感じかと。

`^(l|c|p|u)(-[a-z0-9]+)+(__[a-z0-9]+(-[a-z0-9]+)+)?(--[a-z0-9]+(-[a-z0-9]+)+)?$`

##### `"selector-pseudo-element-colon-notation": "double"`

疑似要素セレクタにダブルコロンを用います。

##### `"shorthand-property-no-redundant-values": true`

ショートハンドプロパティ内の冗長な値を禁止します。

#### Order

<details><summary>rules</summary>

```json
{
  "rules": {
    "order/properties-order": [
      "content",

      "display",
      "visibility",
      "overflow",
      "overflow-x",
      "overflow-y",
      "opacity",
      "clip-path",

      "flex",
      "flex-grow",
      "flex-shrink",
      "flex-basis",
      "flex-flow",
      "flex-direction",
      "flex-wrap",

      "grid",
      "grid-area",
      "grid-template",
      "grid-template-areas",
      "grid-template-rows",
      "grid-template-columns",
      "grid-row",
      "grid-row-start",
      "grid-row-end",
      "grid-column",
      "grid-column-start",
      "grid-column-end",
      "grid-auto-rows",
      "grid-auto-columns",
      "grid-auto-flow",
      "grid-gap",
      "grid-row-gap",
      "grid-column-gap",

      "gap",
      "row-gap",
      "column-gap",

      "place-content",
      "place-items",
      "place-self",
      "justify-content",
      "justify-items",
      "justify-self",
      "align-content",
      "align-items",
      "align-self",

      "order",

      "box-sizing",
      "position",
      "z-index",
      "top",
      "right",
      "bottom",
      "left",

      "transform",
      "transform-origin",

      "float",
      "clear",

      "margin",
      "margin-top",
      "margin-right",
      "margin-bottom",
      "margin-left",
      "padding",
      "padding-top",
      "padding-right",
      "padding-bottom",
      "padding-left",
      "width",
      "min-width",
      "max-width",
      "height",
      "min-height",
      "max-height",

      "border",
      "border-width",
      "border-style",
      "border-color",
      "border-top",
      "border-top-width",
      "border-top-style",
      "border-top-color",
      "border-right",
      "border-right-width",
      "border-right-style",
      "border-right-color",
      "border-bottom",
      "border-bottom-width",
      "border-bottom-style",
      "border-bottom-color",
      "border-left",
      "border-left-width",
      "border-left-style",
      "border-left-color",
      "border-radius",
      "border-top-left-radius",
      "border-top-right-radius",
      "border-bottom-right-radius",
      "border-bottom-left-radius",
      "border-image",
      "border-image-source",
      "border-image-slice",
      "border-image-width",
      "border-image-outset",
      "border-image-repeat",

      "outline",
      "outline-width",
      "outline-style",
      "outline-color",
      "outline-offset",

      "box-decoration-break",
      "box-shadow",

      "list-style",
      "list-style-position",
      "list-style-type",
      "list-style-image",

      "background",
      "background-color",
      "background-image",
      "background-repeat",
      "background-attachment",
      "background-position",
      "background-position-x",
      "background-position-y",
      "background-clip",
      "background-origin",
      "background-size",

      "filter",
      "mix-blend-mode",

      "text-align",
      "text-align-last",
      "vertical-align",
      "white-space",
      "text-decoration",
      "text-emphasis",
      "text-emphasis-color",
      "text-emphasis-style",
      "text-emphasis-position",
      "text-indent",
      "text-justify",
      "text-transform",
      "letter-spacing",
      "word-spacing",
      "text-outline",
      "text-transform",
      "text-wrap",
      "text-overflow",
      "text-overflow-ellipsis",
      "text-overflow-mode",
      "word-wrap",
      "word-break",
      "tab-size",
      "hyphens",

      "color",
      "text-shadow",
      "font",
      "font-family",
      "font-size",
      "font-weight",
      "font-style",
      "font-variant",
      "font-size-adjust",
      "font-stretch",
      "font-effect",
      "font-emphasize",
      "font-emphasize-position",
      "font-emphasize-style",
      "font-smooth",
      "line-height",
      "alignment-baseline",
      "baseline-shift",
      "dominant-baseline",
      "text-anchor",
      "word-spacing",
      "writing-mode",

      "fill",
      "fill-opacity",
      "fill-rule",
      "stroke",
      "stroke-dasharray",
      "stroke-dashoffset",
      "stroke-linecap",
      "stroke-linejoin",
      "stroke-miterlimit",
      "stroke-opacity",
      "stroke-width",
      "color-interpolation",
      "color-interpolation-filters",
      "color-profile",
      "color-rendering",
      "flood-color",
      "flood-opacity",
      "image-rendering",
      "lighting-color",
      "marker-start",
      "marker-mid",
      "marker-end",
      "mask",
      "shape-rendering",
      "stop-color",
      "stop-opacity",
      
      "table-layout",
      "empty-cells",
      "caption-side",
      "border-spacing",
      "border-collapse",

      "pointer-events",
      "quotes",
      "counter-reset",
      "counter-increment",
      "resize",
      "cursor",
      "nav-index",
      "nav-up",
      "nav-right",
      "nav-down",
      "nav-left",

      "transition",
      "transition-delay",
      "transition-timing-function",
      "transition-duration",
      "transition-property",
      "animation",
      "animation-name",
      "animation-duration",
      "animation-play-state",
      "animation-timing-function",
      "animation-delay",
      "animation-iteration-count",
      "animation-iteration-count",
      "animation-direction"
    ]
  }
}
```

</details>

## Visual Studio Codeの設定

### vscode-stylelintの設定

Stylelintの拡張機能をインストールします。

https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint

VSCodeのsetting.jsonを編集します。

- デフォルトのVSCode組み込みのバリデーションを切る
- stylelintの方を有効にする
- `stylelint.validate`でSCSSも指定する
- 保存時に自動で`stylelint --fix`

```json
{
    "css.validate": false,
    "scss.validate": false,
    "stylelint.validate": ["css", "scss"],
    "stylelint.configFile": "./.stylelintrc",
    "editor.codeActionsOnSave": {
        "source.fixAll.stylelint": true
    },
}
```

## まとめ

成し遂げたいことを成し遂げられました。ハッピー:relaxed:

「Stylelint v14が破壊的変更がある」ということを聞いていただけだったので、調べるきっかけになりました。
また、VSCodeの右下にひょこっと出てくる通知はちゃんと見ずに消しちゃうこともあるので、気をつけないとなと戒めました。