ESLint
npm i --save-dev eslint
> eslint --init
? How would you like to configure ESLint? Answer questions about your style
// 是否校验 Es6 语法
? Are you using ECMAScript 6 features? Yes
// 是否校验 Es6 模块语法
? Are you using ES6 modules? Yes
// 代码运行环境,Browser 指浏览器
? Where will your code run? Browser
// 是否校验 CommonJs 语法
? Do you use CommonJS? Yes
// 是否校验 JSX 语法
? Do you use JSX? Yes
// 是否校验 React 语法
? Do you use React? Yes
// 首行空白选择 Tab 键还是 Space
? What style of indentation do you use? Tabs
// 字符串使用单引号 'string' 还是双引号 "string"
? What quotes do you use for strings? Double
// 操作系统
? What line endings do you use? Windows
// 每行代码结尾是否校验加分号
? Do you require semicolons? Yes
// 以 .js 格式生成配置文件
? What format do you want your config file to be in? JavaScript
// 因为要校验 Reac 语法,所以这里需要下载一个 React 语法规则的包
Installing eslint-plugin-react@latestoff” 或 0 - 关闭规则
“warn” 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
“error” 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
// http://eslint.org/docs/user-guide/configuring
// .eslintrc.js
module.exports = {
//此项是用来告诉eslint找当前配置文件不能往父级查找
root: true,
//此项是用来指定eslint解析器的,解析器必须符合规则,babel-eslint解析器是对babel解析器的包装使其与ESLint解析
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
// "node": true,
//"commonjs": true,
//"es6": true,
//"amd": true
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
// 此项是用来配置标准的js风格,
extends: 'standard',
// 此项是用来提供插件的,插件名称省略了eslint-plugin-,
plugins: [
'html'
],
// add your custom rules here
'rules': {
'prettier/prettier': ['error'],// [error,wran,off]
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
"no-irregular-whitespace": 0,//不能有不规则的空格
"no-mixed-operators": 0,
"sort-vars": 0,//变量声明时排序
"prefer-const": 0,//首选const
"newline-after-var": 0,//变量声明后是否需要空一行
"quotes": [0, "single"],//引号类型 `` "" ''
"quote-props":[0, "always"],//对象字面量中的属性名是否强制双引号
"array-bracket-spacing": [2, "never"],//是否允许非空数组里面有多余的空格
"arrow-spacing": 0,//=>的前/后括号
"spaced-comment": 0,//注释风格要不要有空格什么的
"eol-last": 0,//文件以单一的换行符结束
"no-extra-semi": 0,//禁止多余的冒号
"no-inline-comments": 0,//禁止行内备注
"no-else-return": 0,//如果if语句里面有return,后面不能跟else语句
"comma-dangle": [0, "never"],//对象字面量项尾不能有逗号
"comma-spacing": 0,//逗号前后的空格
"func-names": 0,//函数表达式必须有名字
"guard-for-in": 0,//for in循环要用if语句过滤
"handle-callback-err": 0,//nodejs 处理错误
"space-after-keywords": [0, "always"],//关键字后面是否要空一格
"space-before-blocks": [0, "always"],//不以新行开始的块{前面要不要有空格
"max-len": [0, 80, 4],//字符串最大长度
"no-unneeded-ternary":0,
"camelcase": 'off',
'@typescript-eslint/camelcase': 0,
"no-self-assign": 0,
"no-delete-var": 1,//不能对var声明的变量使用delete操作符
"no-div-regex": 1,//不能使用看起来像除法的正则表达式/=foo/
"indent": [2, 4],//缩进风格
"semi-spacing": [2, {"before": false, "after": false}],//分号前后空格
"no-dupe-keys": 2,//在创建对象字面量时不允许键重复 {a:1,a:1}
"no-duplicate-case": 2,//switch中的case标签不能重复
"comma-style": [2, "last"],//逗号风格,换行时在行首还是行尾
"no-ex-assign": 2,//禁止给catch语句中的异常参数赋值
"no-extend-native": 2,//禁止扩展native对象
"no-extra-bind": 2,//禁止不必要的函数绑定
"no-extra-boolean-cast": 2,//禁止不必要的bool转换
"no-extra-parens": 2,//禁止非必要的括号
"eqeqeq": 2,//必须使用全等
"no-implied-eval": 2,//禁止使用隐式eval
"no-inner-declarations": [2, "functions"],//禁止在块语句中使用声明(变量或函数)
"no-invalid-regexp": 2,//禁止无效的正则表达式
"no-invalid-this": 2,//禁止无效的this,只能用在构造器,类,对象字面量
"no-dupe-args": 2,//函数参数不能重复
"valid-typeof": 2,//必须使用合法的typeof的值
"no-with": 2,//禁用with
"semi": [2, "always"],//语句强制分号结尾
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],//对象字面量中冒号的前后空格
}
}写一个 eslint 插件
// node_modules/@typescript-eslint/eslint-plugin
// package.json 中的 main 指向了 dist/index.js 文件,因此 index.js 是入口文件
// dist/index.js(部分代码)
const rules_1 = __importDefault(require("./rules"));
module.exports = {
rules: rules_1.default,
};
// dist/rules/index.js(部分代码)
const ban_ts_comment_1 = __importDefault(require("./ban-ts-comment"));
exports.default = {
// 设计一条校验规则 ban-ts-comment
'ban-ts-comment': ban_ts_comment_1.default,
};
// dist/rules/ban-ts-comment.js(部分代码)
// 以下代码不用细看,可以简单理解为从以下注释中匹配,如果能够匹配上,则发布一个 ESLint 错误或者警告
// @ts-ignore
// @ts-nocheck
// @ts-expoect-error
// @ts-check
exports.default = util.createRule({
name: 'ban-ts-comment',
create(context, [options]) {
/*
The regex used are taken from the ones used in the official TypeScript repo -
https://github.com/microsoft/TypeScript/blob/408c760fae66080104bc85c449282c2d207dfe8e/src/compiler/scanner.ts#L288-L296
*/
const commentDirectiveRegExSingleLine = /^/*\s*@ts-(?<directive>expect-error|ignore|check|nocheck)(?<description>.*)/;
const commentDirectiveRegExMultiLine = /^\s*(?:/|*)*\s*@ts-(?<directive>expect-error|ignore|check|nocheck)(?<description>.*)/;
const sourceCode = context.getSourceCode();
const descriptionFormats = new Map();
for (const directive of [
'ts-expect-error',
'ts-ignore',
'ts-nocheck',
'ts-check',
]) {
const option = options[directive];
if (typeof option === 'object' && option.descriptionFormat) {
descriptionFormats.set(directive, new RegExp(option.descriptionFormat));
}
}
return {
Program() {
const comments = sourceCode.getAllComments();
comments.forEach(comment => {
const regExp = comment.type === utils_1.AST_TOKEN_TYPES.Line
? commentDirectiveRegExSingleLine
: commentDirectiveRegExMultiLine;
const match = regExp.exec(comment.value);
if (!match) {
return;
}
const { directive, description } = match.groups;
const fullDirective = ts-${directive};
const option = options[fullDirective];
if (option === true) {
// https://eslint.org/docs/latest/developer-guide/working-with-rules#contextreport
// 一旦匹配上 TypeScript 上述注释,则通过 context.report 在 ESLint 中发布一个警告或者错误
// The main method you’ll use is context.report(), which publishes a warning or error (depending on the configuration being used).
context.report({
// (optional) placeholder data for message.
data: { directive },
// (optional) the AST node related to the problem.
node: comment,
// the problem message.
messageId: 'tsDirectiveComment',
});
}
if (option === 'allow-with-description' ||
(typeof option === 'object' && option.descriptionFormat)) {
const { minimumDescriptionLength = exports.defaultMinimumDescriptionLength, } = options;
const format = descriptionFormats.get(fullDirective);
if (description.trim().length < minimumDescriptionLength) {
context.report({
data: { directive, minimumDescriptionLength },
node: comment,
messageId: 'tsDirectiveCommentRequiresDescription',
});
}
else if (format && !format.test(description)) {
context.report({
data: { directive, format: format.source },
node: comment,
messageId: 'tsDirectiveCommentDescriptionNotMatchPattern',
});
}
}
});
},
};
}
})