Skip to content

插件的命名有以下几种方式:

  1. eslint-plugin-<plugin-name>
  2. @scope/eslint-plugin-<plugin-name>
  3. @scope/eslint-plugin

1.plugins属性

在开始介绍自定义 plugin 之前,先说明下 Eslint 配置文件(如 .eslintrc.*)中的 plugins 属性。

eslint-plugin-vue 中的 configs/essential 核心代码为例(省略了大部分 rules):

js
// configs/essential
module.exports = {
  parser: require.resolve('vue-eslint-parser'),
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module'
  },
  env: {
    browser: true,
    es6: true
  },
  plugins: ['vue'],
  rules: {
    'vue/require-v-for-key': 'error'
  }
}

可以发现,在 configs/essential 中已经默认声明了 plugins: ['vue']

该配置可通过 extends 自动引入到 .eslintrc.* 相关配置文件中。

那么在 .eslintrc.js 中引入 eslint-plugin-vue 时:

js
// .eslintrc.js
module.exports = {
  root: true,
  // 引入configs/essential
  extends: [
    'plugin:vue/essential'
  ],
  // 由于configs/essential已经默认声明,所以这里其实可以省略
  plugins: [
    'vue'
  ],
  rules: {
    // 可以覆盖设置rules
    'vue/require-v-for-key': ['warn']
  }
}

如果 configs/essential 中没有默认声明 plugins: ['vue']

而且 .eslintrc.* 等配置文件中也没有声明 plugins: ['vue']

那么此时检测 .vue 文件时,Eslint 对于 vue/<rule-name>rules 会定位失败。

可想而知,plugins: ['vue'] 声明,相当于对应 rulesBase 设置。

2.插件中的规则

插件中可以声明 rules 属性以实现为 Eslint 提供额外的规则。

插件中的 rules 定义规则,需要在 .eslintrc.* 引入时来设置等级,进而启用。

这里以 eslint-plugin-vue 举例:

js
// eslint-plugin-vue
module.exports = {
  configs: {},
  processors: {
    '.vue': require('./processor')
  }
  rules: {
    // 插件中,声明了该规则
    "require-v-for-key": {
      create (context) { ... }
    }
  },
  environments: {
    'setup-compiler-macros': {
      globals: {
        defineProps: 'readonly',
        defineEmits: 'readonly',
        defineExpose: 'readonly',
        withDefaults: 'readonly'
      }
    }
  }
}

那么此时,在 .eslintrc.js 中,可以在 rules 中设置 <plugin-name>/<rule-name> 以对该规则进行具体设置:

js
// .eslintrc.js
module.exports = {
  root: true,
  // plugins引入自定义规则、处理器、环境
  plugins: ['vue'],
  // 规则解析,需要使用AST,vue相关文件有特有的解析器
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module'
  },
  // 配置自定义规则 off、warn、error或者其他options
  rules: {
    'vue/require-v-for-key': ['error']
  }
}

3.插件中的配置

如果我们想要自定义插件本身就携带配置好的规则,则可以利用 configs 属性声明多个 config

要注意的一点,通过 plugins: [] 来引入插件时,并不能设置 configs 默认配置

必须手动在 extends 中引入 plugin:<plugin-name>/<config-name>,配置才会生效

eslint-plugin-vue 中的 baseessential 为例:

js
// eslint-plugin-vue
module.exports = {
  rules: { ... },
  configs: {
    base: require('./configs/base'),
    essential: require('./configs/essential'),
  },
  processors: {
    '.vue': require('./processor')
  },
  environments: { ... }
}

plugin:vue/base 的代码如下:

js
module.exports = {
  parser: require.resolve('vue-eslint-parser'),
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module'
  },
  env: {
    browser: true,
    es6: true
  },
  plugins: ['vue'],
  rules: {
    'vue/comment-directive': 'error',
    'vue/jsx-uses-vars': 'error'
  }
}

plugin:vue/essential 的代码如下(忽略了部分 rules):

js
module.exports = {
  extends: require.resolve('./base'),
  rules: {
    'vue/multi-word-component-names': 'error',
    'vue/no-arrow-functions-in-watch': 'error',
    'vue/no-async-in-computed-properties': 'error',
    'vue/no-child-content': 'error',
    'vue/no-computed-properties-in-data': 'error',
    'vue/no-custom-modifiers-on-v-model': 'error',
    'vue/no-dupe-keys': 'error',
    'vue/no-dupe-v-else-if': 'error'
  }
}

4.插件中的环境

插件中可以定义具体的环境,以供 Eslint 使用。

需要声明 environments 属性。

js
module.exports = {
  environments: {
    jquery: {
      globals: {
        // false表示不允许重写
        $: false
      },
      parserOptions: {
        ecmaVersion: 2020
      }
    }
  }
}

environments 中可声明的配置有两部分:

  1. globals 全局变量。
  2. parserOptions 解析器配置。

这俩个属性的设置方法与 .eslintrc.* 配置文件相同。

假设我们的插件名为 eslint-plugin-myPlugin,当使用上述插件环境时,需要利用 env 属性

js
// .eslintrc.js
module.exports = {
  root: true,
  env: {
    'myPlugin/jquery': true
  }
}

5.插件中的处理器

Eslint 本身只支持处理 JavaScript 文件。

markdown 或者 vue 这种类型的文件,必须提供额外的处理器,Eslint 才能处理。

处理 markdown 类型文件的插件可以使用eslint-plugin-markdown

处理 vue 类型文件的插件则可以使用eslin-plugin-vue

5-1.定义处理器

我们可以通过设置 processors 属性来定义插件的处理器。

js
module.exports = {
  processors: {
    "processor-name": {
      // takes text of the file and filename
      preprocess: function(text, filename) {
        // here, you can strip out any non-JS content
        // and split into multiple strings to lint
        return [ // return an array of code blocks to lint
            { text: code1, filename: "0.js" },
            { text: code2, filename: "1.js" },
        ]
      },
      // takes a Message[][] and filename
      postprocess: function(messages, filename) {
        // `messages` argument contains two-dimensional array of Message objects
        // where each top-level array item contains array of lint messages related
        // to the text that was returned in array from preprocess() method

        // you need to return a one-dimensional array of the messages you want to keep
        return [].concat(...messages);
      },

      supportsAutofix: true // (optional, defaults to false)
    }
  }
}

可以看出,定义处理器的属性有:

  1. preprocess 前置处理器。
  2. postprocess 后置处理器。
  3. supportsAutofix 是否支持自动修复。

值得一提的是,Eslint v7 及以上版本对于 processors 的定义和使用方式有过一次升级。

具体文章可参考2018-processors-improvements

markdown 文件中通常会有代码块,如 ````js` 等。

eslint-plugin-markdown 会将这些代码块抽离出来,形成虚拟文件。

譬如 README.md 中的 \```js 对应的是 README.md/*.js

preprocess 方法,就是负责将源代码,抽离成可供 Eslint 检测的自定义代码块。

然后 postprocess 方法中,会接收到 Eslint 的检测结果,以供插件进行二次处理。

5-2.配置文件中使用处理器

假设,已经使用上一节中的方式定义了插件 eslint-plugin-myPlugin

那么在 .eslintrc.* 等配置文件中,就可以这样引入处理器:

js
// .eslintrc.js
module.exports = {
  processor: 'myPlugin/processor-name'
}

当然,在实际开发中,JavaScript 文件是不用处理器的。

而像 markdown 或者 vue 这类特殊文件则需要单独处理,那么我们可以使用 overrides 属性来定义特殊文件的特有处理方式:

这里,以 eslint-plugin-markdown 为例:

js
module.exports = {
  root: true,
  extends: [
    'eslint:recommended'
  ],
  plugins: ['markdown'],
  overrides: [
    {
      files: ['**/*.md'],
      processor: 'markdown/markdown'
    },
    {
      files: ['**/*.md/*.js'],
      rules: {
        // 对于md文档中的js代码块,自定义规则
        semi: ['error']
      }
    }
  ]
}

但是一般插件作者,为了方便用户使用,会将这些配置直接定义到 configs 中,然后用户直接引入到 extends 中即可。

譬如 eslint-plugin-markdown 的实际定义为:

js
const processor = require("./processor")

module.exports = {
  configs: {
    recommended: {
      plugins: ['markdown'],
      overrides: [
        {
          files: ['*.md'],
          processor: 'markdown/markdown'
        },
        {
          files: ['**/*.md/**'],
          parserOptions: {
            ecmaFeatures: {
              impliedStrict: true
            }
          },
          rules: {}
        }
      ]
    }
  },
  processors: {
    markdown: processor
  }
}

这样,当我们使用 eslint-plugin-markdown 时,只需要:

js
module.exports = {
  root: true,
  extends: [
    'eslint:recommended'
  ],
  overrides: [
    {
      files: ['**/*.md/*.js'],
      rules: {
        // 对于md文档中的js代码块,自定义规则
        semi: ['error']
      }
    }
  ]
}

5-3.文件扩展名处理器

上几节中,都是显式的提供处理器名称,如 processor-name

这种方式,在实际使用的时候,都是通过定义 processorplugin/processor-name 来引入处理器。

除此以外,有一种更方便的方式:

将文件扩展名作为处理器的定义键

这种形式的处理器,在检测到对应符合类型的文件时,就会自动应用该处理器。

譬如eslint-plugin-vue中的处理器定义:

js
// 此处只描述核心代码,详细内容可见上方链接
module.exports = {
  processors: {
    '.vue': require('./processor')
  }
}