LoginSignup
2
3

More than 3 years have passed since last update.

vue-class-componentでeslint-plugin-vueのno-unused-componentsを使えるようにする

Posted at

要約

eslint-plugin-vue の no-unused-componentsクラススタイル Vue コンポーネント でも動くようにした。

今回の成果物

こちらに置いてあります
negibouze/customize-no-unused-components-rule

詳細

1. vue/no-unused-components を入れる

Vue CLI で ESLint を有効にしてプロジェクトを作成している場合は、ここは飛ばして良いです。

1-1. eslint 関連を入れる

yarn add -D @vue/cli-plugin-eslint @vue/eslint-config-typescript babel-eslint eslint eslint-plugin-vue

1-2. 設定ファイルを追加

.eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential'
  ],
  rules: {},
  parserOptions: {
    parser: '@typescript-eslint/parser'
  }
}

1-3. 動作確認

no-unused.png

no-unused-components の動作が確認できたので、カスタムルールを作成していきます。

2. カスタムルールの準備

2-1. EsLint ディレクトリの作成

カスタムルールを入れるディレクトリを作成します(今回はプロジェクト直下に作成)。

eslint
   ├── rules
   └── tests

2-2. rule、test ファイルの追加

各ディレクトリ配下に必要なファイルを追加します。

eslint
   ├── rules
   │   └── no-unused-components.js
   └── tests
       └── no-unused-components.js

2.3. 初期状態の作成

eslint-plugin-vue のルールをベースにさせてもらうので、まずはそのままコピーします。
元ルールの動きを壊していないことを確認するために、テストは2ファイル用意。

eslint
   ├── rules
   │   └── no-unused-components.js
   └── tests
       ├── no-unused-components-original.js <= eslint-plugin-vue のファイル
       └── no-unused-components.js

2.4. ルールの修正

utils, casing は使わせてもらうのでパスを変更します。

// 変更前
const utils = require('../utils')
const casing = require('../utils/casing')
// 変更後
const utils = require('eslint-plugin-vue/lib/utils')
const casing = require('eslint-plugin-vue/lib/utils/casing')

2.5. 設定変更

自作ルールを使うように設定を変更します。

.eslintrc.js
rules: {
  'vue/no-unused-components': 'off',
  'no-unused-components': 'error'
},
package.json
{
  "scripts": {
    "lint": "vue-cli-service lint --rulesdir eslint/rules"
  }
}

2.6. 動作確認

yarn run lint
error: The "HelloWorld" component has been registered but not used (no-unused-components) at src/components/Example2.vue:10:5:
   8 | export default {
   9 |   components: {
> 10 |     HelloWorld
     |     ^
  11 |   }
  12 | }
  13 | </script>

カスタムルールで動くことが確認できたので、いよいよルールを変更していきます。

3. カスタムルールの追加

3-1. 調査

少し調べると registeredComponents と usedComponents あたりが見つかります。
もう少し調べると、

  • クラススタイルの場合は登録しても registeredComponents に入っていない
  • そもそも utils.executeOnVue が呼ばれない

あたりが分かりますので、そこを解決すれば良さそうです。

no-unused-components.js
  create (context) {
    // 中略
    return utils.defineTemplateBodyVisitor(context, {
      "VElement[name='template']:exit" (rootNode) {
     // 中略
        registeredComponents
          .filter(({ name }) => {
            if (casing.pascalCase(name) === name || casing.camelCase(name) === name) {
              return ![...usedComponents].some(n => {
                return n.indexOf('_') === -1 && (name === casing.pascalCase(n) || casing.camelCase(n) === name)
              })
            } else {
              return !usedComponents.has(name)
            }
          })
          .forEach(({ node, name }) => context.report({
            node,
            message: 'The "{{name}}" component has been registered but not used.',
            data: {
              name
            }
          }))
      }
    }, utils.executeOnVue(context, (obj) => {
      // クラススタイルだとここまで来ない。
      registeredComponents = utils.getRegisteredComponents(obj)
    }))
  }

がんばって調べると、isVueComponentFile あたりに手を入れれば良さそうなことが分かります。
executeOnVue -> executeOnVueComponent -> isVueComponentFile

eslint-plugin-vue/utils/index.js
isVueComponentFile (node, path) {
  return this.isVueFile(path) &&
    node.type === 'ExportDefaultDeclaration' &&
    node.declaration.type === 'ObjectExpression'
},

3-2. ルールの編集

調査の結果、isVueComponentFile を変更すれば良さそうなことが分かりましたが、
utils は使わせてもらいたいので、こんな感じに変更します。

eslint/rules/no-unused-components.js
const cloneDeep = require('lodash/cloneDeep')

const myUtils = cloneDeep(utils)
const originalIsVueComponentFile = myUtils.isVueComponentFile
myUtils.isVueComponentFile = (node, path) => {
  return (
    originalIsVueComponentFile.call(myUtils, node, path) ||
    node.declaration.type === 'ClassDeclaration'
  )
}

// 以下、utils を使っているところを myUtils に変更

上記の変更で executeOnVue が呼ばれるようになりましたが、下記のエラーが発生しました。

TypeError: Cannot read property 'find' of undefined

ただ、ここは分岐を追加するだけで良さそうです。

eslint/no-unused-components.js
// 変更
myUtils.executeOnVue(context, (obj) => {
  if (obj.type === 'ClassDeclaration') {
    registeredComponents = getRegisteredComponentsFromClassDeclaration(obj)
  } else {
    registeredComponents = myUtils.getRegisteredComponents(obj)
  }
}

// 追加
const getRegisteredComponentsFromClassDeclaration = obj => {
  const decorators = obj.decorators
  if (!decorators) return []
  const expression = decorators[0].expression
  const hasArguments = expression && expression.arguments && expression.arguments.length >= 1
  return hasArguments ? myUtils.getRegisteredComponents(expression.arguments[0]) : []
}

3-3. 動作確認

yarn run lint
error: The "HelloWorld" component has been registered but not used (no-unused-components) at src/components/Example.vue:11:5:
   9 | @Component({
  10 |   components: {
> 11 |     HelloWorld
     |     ^
  12 |   }
  13 | })
  14 | export default class Example extends Vue { }


error: The "HelloWorld" component has been registered but not used (no-unused-components) at src/components/Example3.vue:10:5:
   8 | export default {
   9 |   components: {
> 10 |     HelloWorld
     |     ^
  11 |   }
  12 | }
  13 | </script>

両方検知できました!:thumbsup:

4. Visual Studio Code で動くようにする

4-1. eslint-plugin-rulesdir を入れる

yarn add -D eslint-plugin-rulesdir

4-2. 設定変更

.eslintrc.js
// 追加
const rulesDirPlugin = require('eslint-plugin-rulesdir')
rulesDirPlugin.RULES_DIR = 'eslint/rules'

module.exports = {
  // 追加
  plugins: [
    'rulesdir'
  ],
  rules: {
    'vue/no-unused-components': 'off',
    'rulesdir/no-unused-components': 'error' // <= 変更
  },
}

4-3. 動作確認

no-unused-components-error.png

検知できました!:tada:
!動かない場合は VSCode を再起動すると直るかも

5. 積み残し

1.この書き方だと AST の構造が変わるため動きません。

<template>
  <div />
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import HelloWorld from './HelloWorld.vue'

@Component({
  components: {
    HelloWorld
  }
})
class Example extends Vue { }
export default Example
</script>

2.現在の eslint-plugin-vue の master ブランチ だと動きません

vue-eslint-parser のバージョンアップにより AST の構造が変わっているため動かなくなります。

eslint-plugin-vue/package.json
"dependencies": {
  "vue-eslint-parser": "^6.0.4"
}

rule を下記のように変更すると動くようになります。

eslint/no-unused-components.js
// 修正前
"VAttribute[directive=true][key.name='bind'][key.argument='is']" (node) {
}
// 修正後
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='is']" (node) {
}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3