Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vueの独自カスタムブロックのフォーマット整形にはeslintがいいのかPrettierがいいのか #7

Closed
tyankatsu0105 opened this issue Nov 9, 2018 · 18 comments
Labels

Comments

@tyankatsu0105
Copy link

はじめまして。
vueファイルの整形に関して質問です。

最近こちらのフレームワークで開発を進めていまして、
https://github.com/gridsome/gridsome
vueファイルの中に<page-query></page-query><static-query></static-query>といったカスタムブロックにGraphQLが書けるというものが魅力的なフレームワークです。

Prettierではparserにgraphqlがあるので、もしかしたらカスタムブロックの整形ができるかもと思っています。(現状Prettierでは独自なブロックなのでフォーマット整形が無視されます)
しかし、eslint-plugin-gridsome的なものでも実現できるかもしれない???と思っており、

  • eslintのpluginを作れば良いのか
  • PrettierのAPIを駆使して独自ファイルを作れば良いのか

この二択で悩んでいます。
どうかお知恵をお貸しください🙇‍♂️

@mysticatea
Copy link
Member

Thank you for this issue!

ふむ。
Prettier で簡単にできそうなら、その方が良いかもしれません (私はそっちの知識がないです...)。
eslint-plugin-prettier を使うと、ESLint 側からも Prettier で vue ファイルを整形できます。

ESLint を使うのであれば、Plugin を作って整形処理を描くことになります。
eslint-plugin-vue と同様に vue-eslint-parser を使い、得られた AST からカスタムブロックの文字列を取り出して処理することになります。
ただ、vue-eslint-parser は今のところカスタム ブロックを正式にサポートしておりませんので、取り出し方が少しトリッキーになってしまいます。

// ルール定義.js
"use strict"

module.exports = {
    meta: {
        description: ""
    },
    create(context) {
        const sourceCode = context.getSourceCode()
        return {
            // Program ノード (AST のルート) で呼ばれる
            Program(node) {
                if (!node.templateBody) {
                    return
                }

                // <template>の兄弟タグから <page-query> を探す
                const topLevelNodes = node.templateBody.parent.children
                for (const node of topLevelNodes) {
                    if (node.type === "VElement" && node.name === "page-query") {
                         // Do checking on the node.
                    }
                }
            }
        }
    }
}

こんな感じ

@tyankatsu0105
Copy link
Author

ご回答有り難うございます。
eslint-plugin-prettierの中身を眺めていたのですが、このプラグイン中にもvue-eslint-parserが使われているようですね🤔
https://github.com/prettier/eslint-plugin-prettier/blob/master/test/prettier.js#L89

僕のやりたいことはどちらかというと、eslint-plugin-prettierでprettierが適用される箇所の拡張みたいな感じだと思うので、mysticateaさんに紹介していただいたコードをイジイジしてみようと思います。
ありがとうございます👍

@mysticatea
Copy link
Member

ほえ? と思ったら、それはユニットテスト (というか Integration Test) ですね。

eslint-plugin-prettier プラグインは *.vue ファイルでもちゃんと動くよ、というのをテストしています。そのために、テスト用に走らせる ESLint が *.vue ファイルを読めるようにする設定が必要で、そこに vue-eslint-parser が使われています。

eslint-plugin-prettier プラグイン自体は、ESLint に与えられたソースコードまるごとそのまま Prettier に渡して、得た結果を報告するだけのアダプターです。

@tyankatsu0105
Copy link
Author

お久しぶりです。
https://github.com/tyankatsu0105/eslint-gridsome
現在こちらでruleの作成を進めていまして、質問させていただきたいです。

https://github.com/tyankatsu0105/eslint-gridsome/blob/master/rules/page-query.js
こちらのルールなんですが、用途としては、
.vue<page-query>でインデント、改行がずれていたら、fixでprettierのapiを使って整形し直す
というものです。
そういったときに、

  • fix前提のルール、テストの書き方が全く分からず・・・
  • ESlint的にfixしかさせない(警告文は出さない)ruleはNGなのか

というところで悩んでおります。
@mysticatea さんのお考えをお聞きしたいです。

@mysticatea
Copy link
Member

おお。
実装お疲れ様です!


fix前提のルール、テストの書き方が全く分からず・・・

こちらは簡単で、テストパターンに output プロパティを追加するだけです。

tester.run(/*略*/, {
    invalid: [
        {
            code: "チェックするコード",
            output: "fixされたコード",
            errors: ["fix前に表示されるエラーメッセージ"]
        }
    ],
})

また、自動修正をサポートするルールは、meta オブジェクトに fixable プロパティが必要です (output 付きのテストケースを書くと最初にこれで怒られると思います)。

module.exports = {
    meta: {
        fixable: null, // スペースのみ修正する場合は "whitespace", それ以外は "code".
    },
    create(context) {/*略*/}
}

その他 meta の内容はこちらに: https://eslint.org/docs/developer-guide/working-with-rules#rule-basics


ESlint的にfixしかさせない(警告文は出さない)ruleはNGなのか

これは、NG ですね。
自動修正とはその名の通り、エラーを自動修正するものなので、エラーがなければ自動修正できません。今回のケースでは、現在のコードと Prettier が成形したコードが異なっていればエラーとすれば良いでしょう。

こんな感じ?

// <page-query>の中のstringの取得 ... 開始タグの終端から終了タグの始端まで。
const codeRange = [
    node.startTag.range[1],
    node.endTag ? node.endTag.range[0] : node.range[1],
]
const code = sourceCode.text.slice(...codeRange).trim()

// 整形する
const formattedCode = prettier.format(code, { parser: prettierParser });

// 整形した結果が現在と異なっていれば報告する
if (formattedCode !== code) {
    context.report({
        loc: node.loc,
        message: `format is incorrect`,

        fix(fixer) {
            return fixer.replaceTextRange(codeRange, `\n${formattedCode}\n`)
        }
    })
}

@tyankatsu0105
Copy link
Author

@mysticatea
お返事ありがとうございます。
早速参考にさせていただきました!!!(mysticateaさんのAST自由に操ってる感すごい・・・!!!)

testも書いてみました。
https://github.com/tyankatsu0105/eslint-gridsome/blob/master/tests/page-query.js#L17

認識が違うかもしれないので確認させてください。

  • invalidとvalid両方書く必要がある。片方だけでは駄目

  • valid code・・・正しいコードを書く
  • invalid code・・・間違っているコードを書く
  • invalid output・・・fixで整形された後であろうコードを書く
  • invalid errors・・・invalid codeのerror内容に沿った文を書く(文はruleのcontext.reportのmessageに書いてあるはず)

という認識です。

質問が二点あります。

  • valid codeとinvalid outputは同じコード内容になると思うのですが、どっちも書かないといけないのでしょうか??(省いてみたらエラーになったので、たぶん両方書かないといけないのでしょうが、念の為)
  • invalid codeにインデントめちゃくちゃ、invalid outputにvalid codeと同じ内容を書いたにもかかわらず、AssertionError [ERR_ASSERTION]: Should have 1 error but had 0: []と出ているのはなぜでしょう。

助けてください🙏

@mysticatea
Copy link
Member

valid codeとinvalid outputは同じコード内容になると思うのですが、どっちも書かないといけないのでしょうか??(省いてみたらエラーになったので、たぶん両方書かないといけないのでしょうが、念の為)

Yes.
validの配列はエラーにならないテストケース、invalidの配列はエラーになるテストケースのリストです。配列のインデックスによって対応しているわけではなく、独立しています。

invalid codeにインデントめちゃくちゃ、invalid outputにvalid codeと同じ内容を書いたにもかかわらず、AssertionError [ERR_ASSERTION]: Should have 1 error but had 0: []と出ているのはなぜでしょう。

これは、errorsの配列では1個のエラーが報告されると定義してあるのに、0このエラーが報告された (つまりエラーが1個もなかった) と言われています。

んー、ああ。テストケースのコードに<template></template>を入れてみてください。
申し訳ありませんが、vue-eslint-parserがカスタムブロックを直接サポートしていないので、<template>タグの兄弟を探しているので....。

@tyankatsu0105
Copy link
Author

templateいれるとエラーがたくさん出てきましたね・・・・
https://github.com/tyankatsu0105/eslint-gridsome/blob/master/tests/page-query.js

@tyankatsu0105
Copy link
Author

@mysticatea
テスト修正しました。
https://github.com/tyankatsu0105/eslint-gridsome/blob/master/tests/page-query.js

invalidのテストが通ったんですが、僕の予期していないコードの形になっていて、なぜ通ったのかよくわかっていません(ログでインデント少しずつ修正していたらこうなってしまいました・・・)

validのテストなのですが、

AssertionError [ERR_ASSERTION]: Should have no errors but had 1: [ { ruleId: 'page-query',
    severity: 1,
    message: 'format is incorrect',
    line: 3,
    column: 9,
    nodeType: null,
    endLine: 14,
    endColumn: 22,
    fix:
     { range: [Array],
       text:
        '\nquery Blog {\n  allWordPressPost(limit: 5) {\n    edges {\n      node {\n        id\n        title\n      }\n    }\n  }\n}\n\n' } } ]
      + expected - actual

      -1
      +0

と出ていて、エラーの見方がよくわかりません。
どういう意味なのでしょうか??

@mysticatea
Copy link
Member

mysticatea commented Nov 29, 2018

そのオブジェクトは実際に報告されたエラーのオブジェクトですね。
fix の中身が、今回は fixer.replaceText なので、置換された文字列です。

テストに利用しているコードのインデントが原因ではないでしょうか?
すべてのテストコードが、各行に 8 文字のスペースを持っているので

@tyankatsu0105
Copy link
Author

@mysticatea
おっしゃるとおりでインデントが関係していました!!!!

validの箇所が相変わらず通っていないのですが、エラーのオブジェクトのfix.textを見る感じ、
<page-query></page-query>の中を見ているっぽかったので、そこだけ書き出しました。
エラーは減ったのですが、

AssertionError [ERR_ASSERTION]: Should have no errors but had 1: [ { ruleId: null,
    fatal: true,
    severity: 2,
    message: 'Parsing error: Unexpected token Blog',
    line: 2,
    column: 7 } ]
      + expected - actual

      -1
      +0

Parsing error: Unexpected token Blog'と出てしまいました。
parseがうまく行ってないのでしょうか???

@mysticatea
Copy link
Member

{{ があったりすると、そこから JavaScript の parse になっちゃうので、それかもしれません?

@mysticatea
Copy link
Member

ああ、や。 valid のテストがタグで囲まれてませんね。
先頭の文字が < でなかった場合、それを JavaScript として parse します。

@tyankatsu0105
Copy link
Author

@mysticatea
ありがとうございます!!!
成功しました!!!👏

2 passing (89ms)

これから試したいことが2つあるのですが、不明な点があります。

  • lintを試してみたい
  • pluginとして公開して、作成したルールでfixが使えるか確認したい

lintを試してみたい

こちらで方法が紹介されていたので

"lint": "eslint . --rulesdir lib/rules",

というコマンドを作成したのですが、fixture.vueにlintが適用されないっぽく・・・原因が不明です。

pluginとして公開して、作成したルールでfixが使えるか確認したい

こちらに関しては、このページを参考にすればいいのかなと思っていましたが、なんだが違うような気がしてます。合ってますでしょうか??
それとも他に読むべきページがあれば教えていただけると助かります!!!!

@tyankatsu0105
Copy link
Author

ここでルールをrequireして、
https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/index.js

ここで各種設定をしてるっぽいですね。
https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/processor.js

@mysticatea
Copy link
Member

lintを試してみたい

ESLint は対象にディレクトリを指定した場合、その中にある *.js ファイルをチェックします。つまり、eslint .eslint ./**/*.js と同じ意味になります。このディレクトリ指定時にチェックするファイルの拡張子を増やすには --ext オプションを使います。

eslint . --ext .vue --rulesdir lib/rules

もしくは、直接 Glob で指定します。

eslint "./**/*.{js,vue}" --rulesdir lib/rules

(ちなみに、ドットで始まるファイルと node_modules 内は無視されます)


pluginとして公開して、作成したルールでfixが使えるか確認したい

はい、公式ドキュメントはそれです: https://eslint.org/docs/developer-guide/working-with-plugins

  1. 名前を eslint-plugin-gridsome にします。
  2. index.js を作成し、rules プロパティを公開します。キーはルール名、値はルール定義です。
  3. npm publish

利用側では

  1. npm install -D eslint-plugin-gridsome vue-eslint-parser
  2. .eslintrc.* ファイルに設定を書きます。
    • plugins: ["gridsome"]
    • parser: "vue-eslint-parser"
    • rules: { "gridsome/page-query": "error" }

@tyankatsu0105
Copy link
Author

@mysticatea
ありがとうございます!!
試せました!!
pluginへの公開はeslint-plugin-vueを見ながらやってみようと思います!!
Gridsomeの作者の方からも反応がありました!!!
gridsome/gridsome#30 (comment)

おせわになりました。ありがとうございます!!

@mysticatea
Copy link
Member

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants