问题
// 测试时使用,后面需要删除
let url = 'ceshi/dasdadaadsa'
// TODO 未完成逻辑,后续优化
for(a of b) {
...
}
我们提交代码的时候,有时候只是想做临时提交,或者是配合测试修改了某些字段,甚至我们已经加上备注提醒自己后续需要修改,但是依旧有些情况会忘记
那么,我们在提交代码的时候,可不可以通过eslint来自定义规则去校验代码呢
方案
方案1:查找eslint有没有现成的API
'no-warning-comments': [{terms: ['todo', '张三'], location: 'anywhere'}] //不能有警告备注
当备注中含有某些字符串时,警告提示
属性 | 值 | 含义 |
---|---|---|
terms | ["todo", "fixme", "xxx"] | 负责校验的字段(不区分大小写) |
location | start/anywhere | 校验的位置 |
当前示例
'no-warning-comments': [{terms: ['todo', 'ceshi'], location: 'start'}] //不能有警告备注
不正确的代码示例
/*eslint no-warning-comments: "error"*/
function callback(err, results) {
if (err) {
console.error(err);
return;
}
// TODO
// 张三
}
因为出现了todo
和张三
字段,所以报错
/*eslint no-warning-comments: "error"*/
function callback(err, results) {
if (err) {
console.error(err);
return;
}
// xyb TODO
}
no-warning-comments
校验时,会把多个单词拆开校验,所以,todo
依旧会命中
正确的代码示例
/*eslint no-warning-comments: "error"*/
function callback(err, results) {
if (err) {
console.error(err);
return;
}
// mnbdjklmbasdbjkTODO
}
这里location
的值是 start
即从字符串开始开始校验,而todo字段在结尾才出现,所以是正确的通过
如果想改成错误,那么就需要将localtion
修改为anywhere
pass:只能做备注的校验,与实际需求,有一定差距
方案2:自定义eslint插件
eslint
自定义规则可以通过npm
引入或者自定义
在项目中新建目录rule
,新建ybxu.js
内部添加自己要的校验逻辑
再在package.json
中修改lint
增加--rulesdir ./rules src
官方定义的新增自定义方法的写法,指向自己的文件夹
再在.eslintrc.js
文件中,的rules
里增加一条属性ybxu: 1
,注意与自己新增的js
同名
pass:执行时候需要执行 yarn lint
与现在执行的一套脚本差距过大(check.js
),且上面拿到的node
节点为AST树,上图代码中使用的node
节点中根本没有直接对应的完整代码,反构建成代码太麻烦了。
方案3:知难而退,选择放弃
只要我放弃的足够快 那失败就永远追不上我
方案4:基于现有的check.js
现在的check.js
逻辑
package.json
文件中
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "npm run push-lint",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,css,less,sass,scss,json,md,vue}": [
"prettier --write",
"node check.js",
"git add"
]
}
可以看到我们的git commit
真正执行的是node check.js
check.js
文件中
// check.js
const exec = require('child_process').exec;
const CLIEngine = require('eslint').CLIEngine;
const cli = new CLIEngine({});
function getErrorLevel(number) {
switch (number) {
case 2:
return 'error';
case 1:
return 'warn';
default:
}
return 'undefined';
}
let pass = 0;
// 只针对src目录下的js和vue代码进行校验
exec('git diff --cached --name-only src| grep -E ".js$|.vue$"', (error, stdout) => {
if (stdout.length) {
const array = stdout.split('\n');
array.pop();
let index = array.indexOf('.eslintrc.js');
const results = cli.executeOnFiles(array).results;
let errorCount = 0;
let warningCount = 0;
results.forEach(result => {
errorCount += result.errorCount;
warningCount += result.warningCount;
if (result.messages.length > 0) {
console.log('\n');
console.log(result.filePath);
result.messages.forEach(obj => {
const level = getErrorLevel(obj.severity);
console.log(
` ${obj.line}:${obj.column} ${level} ${obj.message} ${obj.ruleId}`
);
pass = 1;
});
}
});
if (warningCount > 0 || errorCount > 0) {
console.log(
`\n ${errorCount +
warningCount} problems (${errorCount} ${'errors'} ${warningCount} warnings)`
);
}
process.exit(pass);
}
if (error !== null) {
console.log(`exec error: ${error}`);
}
});
check.js
中的
const CLIEngine = require('eslint').CLIEngine;
const cli = new CLIEngine({});
通过Node API的方式直接使用
最终在下方将我们修改文件的目录数据,抛入,即可获得校验结果results
// array 是修改文件的地址数组
const results = cli.executeOnFiles(array).results;
那么我们能不能在new CLIEngine({})
这个过程中塞入我们自定义的方法呢?
官方文档没看懂,放弃了
转念一想,我们在node
环境中,也知道了修改代码的路径,那么我们自己可以把代码取出来,自己去做校验
直接贴代码
// check.js
const exec = require('child_process').exec;
const rf = require('fs');
const path = require('path');
const CLIEngine = require('eslint').CLIEngine;
const cli = new CLIEngine({});
function getErrorLevel(number) {
switch (number) {
case 2:
return 'error';
case 1:
return 'warn';
default:
}
return 'undefined';
}
let pass = 0;
// 封装的获取文件内容方法
function read(dir) {
return new Promise((resolve, reject) => {
rf.readFile(dir, 'utf-8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
// 只针对src目录下的js和vue代码进行校验
exec('git diff --cached --name-only src| grep -E ".js$|.vue$"', async (error, stdout) => {
let errorCount = 0;
let warningCount = 0;
// 需要提示警告的单词
const warnWordArray = ['todo'];
if (stdout.length) {
const array = stdout.split('\n');
array.pop();
const publicPath = path.resolve(__dirname); // 获取public路径
let eslintData = [];
for (let srcItem of array) {
// 采用path.join来拼接路径,做win和ios兼容问题
let eslintPath = path.join(publicPath, srcItem);
// 这里采用异步处理,防止文件没拿到被底部的 process.exit(pass); 直接退出了
await read(eslintPath).then(data => {
// 将内容和路径拼接好放进 eslintData 中
eslintData.push({
code: String(data).split('\n'), // 切成数组,每个值就是代码中的每一行,下标+1为行数
path: eslintPath
});
});
}
for (let codeData of eslintData) {
console.log('\n');
// 保证一个模块只显示一次path
let isShowPath = false;
// 保证一个模块只显示一次warn
let isWarn = false;
let beforeItem = ''
codeData.code.forEach((item, line) => {
// 如果在对应行找到了这个特殊字段
for (let warnItem of warnWordArray) {
// column 为匹配到有问题的字符串的位置
let column = item.toLowerCase().indexOf(warnItem.toLowerCase());
// 是否需要跳过
let isPass = beforeItem.includes('// pass warnWord');
// 如果命中字段,并且不需要跳过,就打印错误提示
if (column > -1 && !isPass) {
pass = 1;
isWarn = true;
if (!isShowPath) {
console.log(codeData.path);
isShowPath = true;
}
// 打印错误提示,将代码行数,代码位置,和具体哪个字段的报错打印出来
console.log(` ${line + 1}:${column + 1} warn ${warnItem} 字段未处理`);
warningCount += 1;
}
}
beforeItem = item
});
if(isShowPath && isWarn) {
console.log(`👿 如果此模块代码需要上传,在对应校验行顶部添加 // pass warnWord`);
}
}
// let index = array.indexOf('.eslintrc.js');
const results = cli.executeOnFiles(array).results;
results.forEach(result => {
errorCount += result.errorCount;
warningCount += result.warningCount;
if (result.messages.length > 0) {
console.log('\n');
console.log(result.filePath);
result.messages.forEach(obj => {
const level = getErrorLevel(obj.severity);
console.log(
` ${obj.line}:${obj.column} ${level} ${obj.message} ${obj.ruleId}`
);
pass = 1;
});
}
});
if (warningCount > 0 || errorCount > 0) {
console.log(
`\n ${errorCount +
warningCount} problems (${errorCount} ${'errors'} ${warningCount} warnings)`
);
}
process.exit(pass);
}
if (error !== null) {
console.log(`exec error: ${error}`);
}
});
接下来我们直接执行node check.js
(上面说过走git commit
最终也会到执行 node check.js
中)
添加跳过检验的备注
完美