Help us understand the problem. What is going on with this article?

VueファイルのStylusをstylelintしたかった話

stylelintはCSSだけでなくSCSSやSassLessもサポートしており、Vueファイルの<style>タグ内部のスタイルシートも検証できるリンターです。
しかし、どうやらStylusはサポートしていないようです。

次のissueを見るとstylelintチームだけでは解決できない様子でcloseされていますね。

これはそもそもPostCSSのパーサープラグインにStylusをサポートしたものが無いからのようです。
さらにStylusPostCSSパーサープラグインはどうなっているのか調べたところ、誰も作る気はなさそうです。

しかし、このissueを読んでみるとStylus自身のParserは公開されているし、AndreyさんもStylusでパースしたASTをPostCSSのASTに変換すれば良いんじゃないか?的なこと言ってるし、「これ簡単にできるんじゃないか?」と思って作り始めてみたのですが、色々と闇が深かったので、この思い出をQiitaに書いて供養しようと思います。

できたもの

先にできたものを書いておきます。
postcss-stylというPostCSSパーサープラグインを作りました。
ただし、現状はvscode-stylelintとは連携できません(たぶん)。stylelintと使うには、CLIかNode.jsから実行することになります。

stylelintに連携する

stylelintにはcustomSyntax(--custom-syntax)というオプションがあるのでこれを使います。

準備

stylelintインストールや、設定ファイル等はあらかじめ用意しておいてください。

あと、先ほど紹介したpostcss-stylもインストールしておいてください。

カスタムパーサーを準備

もしリントの対象にしたいファイルがStylusのファイルだけであれば、customSyntaxオプションに直接postcss-stylを渡しても良いのですが、プロジェクトでCSSとStylusファイルが混ざっていたり、Vueファイルの中の<style lang="stylus">にもリントをかけたい場合は、この準備が必要です。

次のファイルを自分のプロジェクトのどこかに作成してください。
ファイル名はなんでも良いですが、説明の便宜上custom-syntax.jsとします。

custom-syntax.js
const syntax = require("postcss-syntax");
const postcssStyl = require("postcss-styl");

module.exports = syntax({
  stylus: postcssStyl
});

postcss-syntaxというパッケージは、ファイル拡張子から、良い感じのPostCSSパーサープラグインを自動的に選択してくれるPostCSSパーサープラグインです。
また、VueファイルやHTMLファイルの中の<style>タグのlang属性から中のコンテンツをどのPostCSSパーサープラグインでパースするかを選択するのにも使われます。

postcss-syntaxは、デフォルトでは、StylusのパーサーにPostCSSの標準パーサーを選択しますが、上記のファイルで、Stylusのパーサーにpostcss-stylのパーサーを選択させるように拡張します。

CLIから実行

--custom-syntaxオプションで、上記で作成したjsファイルを指定します。

.styl.vueファイルを対象にしたい場合以下のようなコマンドで実行できると思います。

stylelint "**/*.(vue|styl|css)" --custom-syntax ./path/to/custom-syntax.js

Node.jsから実行

あまりやる人いないと思うので、postcss-stylのREADMEを参照してください。

VSCodeと連携

残念ながら(たぶん)今はできません。

使用上の注意

当然ですがstylelintの持っている静的検証ルールたちはStylusの構文を考慮していません。
よって、Stylusでは利用できないルールがいくつかあります。
しかし、どのルールがStylusで利用できるかできないかの情報は当然ないので、
一つ一つ結果を確認して、ルールをoffにするかどうか確認していくしかないです。

自動修正

一応自動修正にも対応していると思います。
注意として、上記の使用上の注意で書いたように、stylelintの持っている静的検証ルールたちはStylusの構文を考慮していないため、ファイルを壊す可能性があります。
これも一つ一つ結果を確認して、ルールをoffにするかどうか確認していくしかないです。

試した感じでは、インデントのルールはうまく動いている印象があります。


StylusPostCSSパーサープラグインを作る上でハマったこと

色々とハマったので、postcss-stylを作る過程でつまずいたことなど書いて供養します。

StylusのParserが返すASTの各出現位置がおかしい

PostCSSのASTノードには次のようなそのノードの出現位置の情報が必要です。

"source": {
  "start": {
    "line": 1,
    "column": 1
  },
  "end": {
    "line": 1,
    "column": 10
  }
}

もちろん、Stylus自身のParserの結果ASTノードも出現位置を持っているのでこれをそのまま使えば良いかと思いきや、全然出現位置が合いません。

原因① バグ

まず普通にバグってます。ただGitHubのdevブランチでは修正済みの様子です。
しかし、Stylusはもう3年ほどリリースされていないので、この修正リリースを待つのは期待薄です。
仕方がないので、GitHubに上がっているソースコードをコピーして持ってくるという荒技で解決しました。

追記:2019/8/20
2019/8/20(ごろ)にまさかの3年ぶりのリリースがありました。
https://github.com/stylus/stylus/releases/tag/0.54.6

このあと、postcss-stylはこの0.54.6を取り込んだのでこの荒技は無くなりました:tada:

原因② 渡したソースコード文字列でパースしてない

Stylusここで、渡したソースコードを書き換えています。この結果からパースが始まるためそりゃあ出現位置もずれますね。
仕方がないのでdiffとって元の出現位置を推測するという荒技で解決しました。

原因③ ちょっとずれている

これはStylusバグではありませんが仕様として少し残念かな?って思うような挙動です。
例えば、

@import 'sample.styl' 

のASTは

{
  "nodeName": "import",
  "path": {
    "nodeName": "expression",
    "nodes": [
      {
        "nodeName": "string"
        "val": "sample.styl"
      }
    ]
  }
}

のような3つのノードで構成されるのですが、この全てのノードの出現位置は

{
  "lineno": 1,
  "column": 10
}

と示されます。つまり"nodeName": "import"のノードの出現位置は@importの開始位置かと期待していたのですが、'sample.styl'の開始位置なんですね。これらを調整するためにノードの種類ごとに出現位置を計算するようにして解決しました。

StylusにはPostfixという概念がある

これはバグとか違和感のある挙動とかではなく、Stylusが独自に持っている機能によってPostCSSのノードに変換するのが大変だったという話です。

StylusにはPostfixというなかなかクールな記法がサポートされています。

例えば、

body
  color: red if apply-red

とした時、apply-redフラグがtrueの時はcolor: redを適用するという感じの記法です。

このASTは

{
  "nodeName": "selector",
  "segments": [{"val": "body"}],
  "block": {
    "nodeName": "block",
    "nodes": [
      {
        "nodeName": "if",
        "cond": {"nodeName": "expression", "nodes": [{"name": "apply-red"}]},
        "block": {"nodeName": "property", "segments": [{"name": "color"}], "expr": {"hash": "red"}}
      }
    ]
  }
}

という感じなのですが、当然"if"の下のノードにcolor: redのノードが来ます。
通常のCSSでは子ノードが親ノードの前方に配置されることは無いので、このノードの変換の為にif文たくさん書く羽目になって苦労しました。

StylusのParserが返すASTにインラインコメント(//)が含まれない

StylusSCSSやSassLessと同様にインラインコメントをサポートしています。

// インラインコメント
/* 通常のCSSコメント */

しかし、StylusのParserが返すASTにインラインコメントは含まれません。
Stylus的にはCSSに変換する際に捨てる情報なようなので、この思想を踏襲すれば、PostCSSのプラグインとしてもASTにインラインコメントを含めなくて良いかな?とも考えましたが、stylelint的にはコメントは重要な情報なので、ノードの間を自力でパースしてインラインコメントノードを構築するという荒技で解決しました。

PostCSSのAST操作後の文字列化でインデントが壊れるのを防ぐ

PostCSSのプラグインは、CSSをパースする機能だけではなく、AST操作と文字列化(Stringifier)の機能もセットで提供する必要があります。

PostCSSのAST操作ではやろうと思えば、全てのインデントを無くすことも可能です。

しかしStylus構文はインデントがとても重要な構文(Stylus的には"pythonic"と言う様子)なのでAST操作でインデントが崩れても、文字列化する際にはインデントを調整し直す必要がありました。

この対応で、stylelintautofixによってインデントを破壊されることはなくなったと思われます。

結果

と、当初予想していなかった様々な問題を経て、StylusPostCSSパーサープラグインpostcss-stylが完成しました。
VSCodeの連携はできませんでしたが、stylelintにも利用することができ、Vueの<style lang="stylus">にもリントをかけることができました。

postcss-stylを作る前は、もっと簡単にできると思っていましたが、結局2ヶ月ぐらいかかってしまいましたorz
あと、これが完成したら、最初に紹介した2つのissueで「作ったよ!」って書こうと思っていましたが、いくつかの黒魔術を抱えてしまったのでどうしようか悩んでいます。。

追記:2019/8/20
とりあえず、stylelint側のissueに申し訳程度にコメントしてみました:sweat_smile:
https://github.com/stylelint/stylelint/issues/674#issuecomment-523026556

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away