eslint
eslint9
Configuration File
eslint.config.js eslint.config.mjs eslint.config.cjs eslint.config.ts (requires additional setup) eslint.config.mts (requires additional setup) eslint.config.cts (requires additional setup)
eslint Configure Language Options
使用的 JavaScript 语言特性和环境 它影响 ESLint 支持的语法版本、模块类型和全局变量定义
js
//eslint.config.js
// eslint.config.js
import { defineConfig } from "eslint/config";
export default defineConfig([
{
languageOptions: {
ecmaVersion: 2020, // 支持 ES2020 语法
sourceType: "module", // 代码作为 ES 模块处理
parserOptions: {
ecmaFeatures: {
jsx: true // 支持 JSX 语法
}
},
globals: {
// languageOptions.globals 是告诉 ESLint 语言解析层面有哪些全局变量,比如 window、process 等,
// 重点是影响规则和解析器逻辑,避免对全局变量报未定义错误
myGlobalVar: "readonly"
}
}
},
]);
基本概念
- 规则 rules
- 配置文件
- 可共享配置 extends
- 指定一个或多个现成的规则集(配置文件)作为基础配置。plugins和rule的集成品
- 以 eslint-config- 开头:eslint-config-myconfig
- 如果是 npm 范围模块,则只需将模块以 @scope/eslint-config 命名或以此为前缀命名即可,如 @scope/eslint-config 和 @scope/eslint-config-myconfig。
- 使用
js{ "extends": "eslint-config-myconfig" // or "extends": "myconfig" // or "extends": ["eslint:recommended", "plugin:react/recommended"] }
- 插件 plugins
- plugin 定义自己的规则(自己可以配置)
js{ plugins: [ 'eslint-plugin-react' ], rules: { 'react/jsx-boolean-value': 2 } }
- 解析器
- settings
- 所有规则中共享的信息(全局)
- 主要是给规则插件提供额外的配置信息或全局上下文
js// eslint.config.js export default [ { settings: { react: { version: "detect", }, "import/resolver": { alias: { map: [["@", "./src"]], extensions: [".js", ".jsx", ".ts", ".tsx"], }, }, }, }, ];
基本操作
- 给项目初始化eslint
bash
npm init @eslint/config@latest
# or
yarn create @eslint/config
# or
pnpm create @eslint/config@latest
bash
eslint ./src/**/*.{js,jsx} #对文件进行检查校验
eslint . --no-cache # 清除缓存
json
{
"scripts": {
"lint": "eslint ./src/**/*.{js,jsx}",
},
}
安装
- 现在pnpm create @eslint/config@latest执行以后
bash
√ How would you like to use ESLint? · problems
√ What type of modules does your project use? · esm
√ Which framework does your project use? · react
√ Does your project use TypeScript? · javascript
√ Where does your code run? · browser
The config that you've selected requires the following dependencies:
eslint, globals, @eslint/js, eslint-plugin-react
√ Would you like to install them now? · No / Yes
√ Which package manager do you want to use? · pnpm
- 默认会安装"eslint": "^9.15.0",v9已经使用新的配置系统:
平面配置文件
- 1、文件 .eslintrc.js/.eslintrc.json -> eslint.config.js
js
import globals from "globals";
import pluginJs from "@eslint/js";
import pluginReact from "eslint-plugin-react";
/** @type {import('eslint').Linter.Config[]} */
export default [
{files: ["**/*.{js,mjs,cjs,jsx}"]},
{languageOptions: { globals: globals.browser }},
pluginJs.configs.recommended,
pluginReact.configs.flat.recommended,
];
- 2、原来的配置会有兼容的问题
- pluginsjs
// .eslintrc.js module.exports = { // ...other config plugins: ["jsdoc"], rules: { "jsdoc/require-description": "error", "jsdoc/check-values": "error" } // ...other config };
js// eslint.config.js import jsdoc from "eslint-plugin-jsdoc"; export default [ { files: ["**/*.js"], plugins: { jsdoc: jsdoc }, rules: { "jsdoc/require-description": "error", "jsdoc/check-values": "error" } } ];
- 2、可共享配置
- 你可能会发现有一个你依赖的可共享配置尚未更新为平面配置格式。
- 在这种情况下,你可以使用 FlatCompat 工具将 eslintrc 格式转换为平面配置格式。
- 首先,安装 @eslint/eslintrc 包:
npm install @eslint/eslintrc --save-dev
jsimport { FlatCompat } from "@eslint/eslintrc"; import path from "path"; import { fileURLToPath } from "url"; // mimic CommonJS variables -- not needed if using CommonJS const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compat = new FlatCompat({ baseDirectory: __dirname }); export default [ // mimic ESLintRC-style extends ...compat.extends("eslint-config-my-config"), ];
js/** 解决eslint-plugin-react-hooks加载问题, eslint-plugin-react-hooks是react官网的eslint插件,并没有实现平面配置格式 https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks { "extends": [ // ... "plugin:react-hooks/recommended" ] } */ import globals from "globals"; import pluginJs from "@eslint/js"; import pluginReact from "eslint-plugin-react"; //https://github.com/jsx-eslint/eslint-plugin-react#configuration import { FlatCompat } from "@eslint/eslintrc"; import path from "path"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compat = new FlatCompat({ baseDirectory: __dirname }); /** @type {import('eslint').Linter.Config[]} */ export default [ { files: ["**/*.{js,mjs,cjs,jsx}"], rules: { "no-unused-vars": ["error", {"args": "none",}], // int.org/docs/latest/rules/no-unused-vars // 'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['evt'] }], 'react/prop-types': ['error', { skipUndeclared: true }], 'react/no-unknown-property': ['error', { ignore: ['css'] }], }, }, ...compat.extends("plugin:react-hooks/recommended"), {languageOptions: { globals: globals.browser }}, pluginJs.configs.recommended, pluginReact.configs.flat.recommended, ];
- 参考:
rules
js
module.exports = {
root: true,
extends: [''],
settings: {
'import/resolver': {
'custom-alias': {
alias: {
'@': './src',
},
},
},
},
rules: {
'react/prop-types': ['error', { skipUndeclared: true }],
'react/no-unknown-property': ['error', { ignore: ['css'] }],
"no-unused-vars": ["error", {"args": "none",}], // int.org/docs/latest/rules/no-unused-vars
// https://github.com/import-js/eslint-plugin-import/blob/v2.31.0/docs/rules/no-unresolved.md
"import/no-unresolved": [0] // import tailwindcss from '@tailwindcss/vite' 报错
"import/no-unresolved": [2, { caseSensitive: true }] // 严格 区分大小写
},
}
eslint 自定义插件
自定义插件eslint-plugin-lodash.js
js
const plugin = {
meta: {
name: 'eslint-plugin-lodash',
version: '1.0.0',
},
configs: {},
rules: {
'scope-test':{
meta: {
type: 'problem',
docs: {
description: `
根据name不是"_"节点,获取当前节点 node,
再根据node.parent 获取其作用域,有要求:node 是 function(普通变量获取不到)等,sourceCode.scopeManager.acquire(node.parent)才可以获取作用域
获取作用域中变量,是否有变量名为"_",没有则报错`,
category: 'Best Practices',
recommended: true,
},
schema: [], // no options
},
create(context){
let globalScope, globalVariables;
const sourceCode = context.getSourceCode()
return {
Identifier(node) { // 标识符表达式(变量名、函数名等)
// if (node.name !== '_') return
// sourceCode.scopeManager.acquire(...) 是 ESLint 内部用于获取当前 AST 节点作用域的方法。
// Identifier 等普通节点(变量名、属性名)不是作用域创建节点,因此传给 acquire() 会返回 null。
const scope = sourceCode.scopeManager.acquire(node.parent) // 是当前标识符的父节点,目的是获取标识符所在的实际作用域
// console.log("scope",scope)
if (!scope) {
// 没作用域,无法判断,安全起见不报错
return;
}
// 查找当前作用域中是否声明了名为 _ 的变量
const variable = scope.variables.find((v) => v.name === '_')
// console.log("variable",variable)
// 沒有声明"_",那反过来判断是全局有了,就提示,一种很不严谨的处理,仅当熟悉插件api写法,与我想写的插件本意相去甚远
if (!variable || variable.defs.length === 0) {
context.report({
node,
message: 'Usage of global lodash `_` is forbidden. Please import lodash methods explicitly.',
})
}
},
Program(node) {
// 分析全局作用域变量 Program 节点 程序入口,只调用一次,效率最高
globalScope = context.getSourceCode().scopeManager.globalScope;
globalVariables = new Set(globalScope.variables.map(v => v.name));
console.log(globalVariables)
// 在这里分析顶层变量,进行标识符等批量或者条件过滤逻辑
}
}
},
},
'no-global-import-lodash': {
meta: {
type: 'problem',
docs: {
description: 'Disallow global import of entire lodash',
category: 'Best Practices',
recommended: true,
},
schema: [], // no options
messages: {
noGlobalLodashImport: "禁止从 'lodash' 导入整个库,请按需导入。"
}
},
create(context) {
return {
ImportDeclaration(node) {
// 判断导入源是否是 'lodash'
if (node.source.value === "lodash") {
// 检查是否是默认导入(import _ from 'lodash')
const hasDefaultImport = node.specifiers.some(
specifier => specifier.type === "ImportDefaultSpecifier"
);
if (hasDefaultImport) {
// 报告错误提醒
context.report({
node,
messageId: "noGlobalLodashImport"
});
}
}
}
}
},
},
// 'no-import-global-lodash': {
// meta: {
// type: 'problem',
// docs: {
// description: 'Disallow usage of global lodash `_` identifier',
// category: 'Best Practices',
// recommended: true,
// },
// schema: [], // no options
// },
// }
},
processors: {},
}
// for ESM
export default plugin
// OR for CommonJS
// module.exports = plugin;
自定义插件的 eslint.config.js
js
// eslint.config.js
import { defineConfig } from "eslint/config";
import lodash from "./eslint-plugin-lodash.js";
export default defineConfig([
{
files: ["src/**/*.js"],
/*
ignorePatterns 旧版 eslintrc 配置里使用的
v9.27.0
ignorePatterns 弃用
.eslintignore 也不被支持了( ESLintIgnoreWarning: The ".eslintignore" file is no longer supported.)
*/
ignores: ["eslint-plugin-*.js"],// ignores配置
// 参考:https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores
// 参考:https://eslint.org/docs/latest/use/configure/ignore
plugins: {
lodash,
},
rules: {
"lodash/scope-test": "off",
"lodash/no-global-import-lodash": "error",
},
},
]);
eslint-plugin-no-global-lodash 插件实现参考
js
/**
* @fileoverview Lodash should not be loaded globally. If a lodash method is needed only the package for that method should be loaded in the file
* @author Adalberto Teixeira
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "Lodash should not be loaded globally. If a lodash method is needed only the package for that method should be loaded in the file",
category: "Fill me in",
recommended: false
},
fixable: 'code',
schema: []
},
create: function(context) {
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
const getLocation = (node, defs) => {
const loc = {
line: node.loc.end.line,
column: node.loc.start.column,
}
if (!defs || !defs.length) return loc;
loc.line = defs[0].name.loc.start.line;
loc.column = defs[0].name.loc.start.column;
return loc;
};
const getErrorMessage = (declaredVariableName) => {
if (!declaredVariableName || declaredVariableName === '_') {
return 'Expected global "lodash" not to be imported.';
}
return `Expected ${declaredVariableName} to be imported as "import ${declaredVariableName} from 'lodash.${declaredVariableName.toLowerCase()}'".`
};
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
ImportDeclaration: function (node) {
if (node.source.value === 'lodash') {
const declaredVariables = context.getDeclaredVariables(node);
if (declaredVariables && declaredVariables.length) {
declaredVariables.forEach(declaredVariable => {
const loc = getLocation(node, declaredVariable.defs);
const errorMessage = getErrorMessage(declaredVariable.name);
context.report({
node,
loc,
message: errorMessage,
})
})
}
}
}
};
}
};
自定义 processor 实现
SLint 自定义插件中的 processors 处理器主要用于处理非标准 JavaScript 文件,例如 .vue、.html、.md 等, 或者在 ESLint 解析 JavaScript 代码之前对代码进行预处理。 它允许您从其他类型的文件中提取 JavaScript 代码,然后让 ESLint 对提取出的 JavaScript 代码进行 lint 检查。
processors 处理器包含两个主要方法:preprocess 和 postprocess。
preprocess(text, filename):
- 接收文件内容和文件名作为参数,返回一个代码块数组。
- 每个代码块都包含 text(代码内容)和 filename(文件名)两个属性。
- filename 属性可以是任意值,但应包含文件扩展名,以便 ESLint 知道如何处理代码块。
- ESLint 会分别检查每个代码块,但它们仍然与原始文件名关联。
postprocess(messages, filename):
- 接收一个二维数组 messages 和文件名作为参数。
- messages 数组中的每个元素对应于 preprocess 方法返回的代码块中的 lint 信息。
- postprocess 方法必须调整所有错误的位置,使其与原始的、未处理的代码中的位置对应,并将它们聚合成一个一维数组并返回。
js
module.exports = {
processors: {
".md": {
preprocess: (text, filename) => {
return text.split("```javascript")
.slice(1) // 去除第一个元素(Markdown 文件 JavaScript 代码块之前的部分)
.map((section) => {
const code = section.split("```")[0]; // 提取 JavaScript 代码
return { text: code, filename: "temp.js" };
});
},
postprocess: (messages, filename) => {
return messages.flat(); // 将二维数组扁平化成一维数组
},
},
},
};
json
{
"plugins": ["./processor"], // 假设 processor.js 与配置文件在同一目录
"overrides": [
{
"files": ["*.md"],
"processor": ".md"
}
]
}
自定义 configs 实现
ESLint 自定义插件中的 configs 主要用于定义一组规则配置的集合,方便用户通过配置扩展直接引用这组预设规则,而不必一条一条单独启用
js
const plugin = {
meta: {
name: "eslint-plugin-example",
version: "1.2.3",
},
configs: {},
rules: {
"dollar-sign": {
create(context) {
// rule implementation ...
},
},
},
};
// assign configs here so we can reference `plugin`
Object.assign(plugin.configs, {
recommended: [
{
plugins: {
example: plugin,
},
rules: {
"example/dollar-sign": "error",
},
languageOptions: {
globals: {
myGlobal: "readonly",
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
},
],
});
// for ESM
export default plugin;
// OR for CommonJS
module.exports = plugin;
參考:
官方指导
参考
根据 ESLint 的作用域分析规则,常见的作用域创建节点包括:
节点类型 | 说明 |
---|---|
Program | 整个脚本或模块的顶层作用域 |
FunctionDeclaration | 函数声明,创建函数作用域 |
FunctionExpression | 函数字面量,创建函数作用域 |
ArrowFunctionExpression | 箭头函数,创建函数作用域 |
ClassDeclaration | 类声明,创建类的块级作用域 |
ClassExpression | 类表达式,同上 |
CatchClause | try-catch 的 catch 子句,创建作用域 |
BlockStatement | ES6 及之后的块级作用域(let/const 所在块) |
SwitchStatement | switch 语句,创建块级作用域 |
ForStatement | for 循环语句,创建块级作用域 |
ForInStatement | for..in 循环,创建块级作用域 |
ForOfStatement | for..of 循环,创建块级作用域 |