ESCheck工具原理解析及增强实现(2):https://developer.aliyun.com/article/1394836?spm=a2c6h.13148508.setting.15.50c84f0e0ugg2G
完整demo3代码
如有一些边界情况也是在 catch err
部分根据 message
做一下过滤即可
比如下代码
var { boolean:hello } = {}
完整demo4代码
做一下过滤,catch message
添加过滤逻辑
const filterMessage = [/^The keyword /] if (filterMessage.find((r) => r.test(error.message))) { return }
调整后的报错信息就是解构赋值
的语法错误了
完整demo5代码
至此基本能完成了单文件的多次es-check检测
,虽然不像mpx-es-check
那样用直白的语言直接说面是什么语法。但还有改进空间嘛,后面再单独写个文章做个工具检测目标代码用了哪些ES6+
特性。就不再这里赘述了
sourcemap解析
这个主要针对检测资源是build产物
的一项优化,通过source-map
解析报错信息对应的源码
前面的代码我们只获取了问题源码
的起止字符位置start
,end
通过source-map解析,首先要获取报错代码在资源中的行列信息
这里通过acorn.getLineInfo
方法可直接获取行列信息
// 省略了重复代码 const codeErrorList: any[] = [] acornWalk.full(ast, (node, _state, _type) => { // 节点对应的源码 const codeSnippet = code.slice(node.start, node.end) try { acorn.parse(codeSnippet, { ecmaVersion: '5' } as any) } catch (error) { const locStart = acorn.getLineInfo(code, node.start) const locEnd = acorn.getLineInfo(code, node.end) codeErrorList.push({ loc: { start: locStart, end: locEnd } }) } }) console.dir(codeErrorList, { depth: 3 })
结果如下,完整demo1代码
有了行列号,我们就可以根据*.map
文件进行源码的解析
默认map
文件由原文件名加.map
后缀
function getSourcemapFileContent(file: string) { const sourceMapFile = `${file}.map` if (fs.existsSync(sourceMapFile)) { return fs.readFileSync(sourceMapFile, 'utf-8') } return '' }
解析map
文件直接使用 sourceMap.SourceMapConsumer
,返回的实例是1个Promise
,使用时需注意
function parseSourceMap(code: string) { const consumer = new sourceMap.SourceMapConsumer(code) return consumer }
根据前面source-map
解析的例子,把这块逻辑放到checkCode
之后即可
const code = fs.readFileSync(file, 'utf-8') // ps: checkCode 即为上一小节实现代码检测能力的封装 const codeErrorList = checkCode(code) const sourceMapContent = getSourcemapFileContent(file) if (sourceMapContent) { const consumer = await parseSourceMap(sourceMapContent) codeErrorList.forEach((v) => { // 解析获取原文件信息 const smStart = consumer.originalPositionFor({ line: v.loc.start.line, column: v.loc.start.column }) const smEnd = consumer.originalPositionFor({ line: v.loc.end.line, column: v.loc.end.column }) // start对应源码所在行的代码 const sourceStartCode = consumer .sourceContentFor(smStart.source!) ?.split(/\r?\n/g)[smStart.line! - 1] const sourceEndCode = consumer .sourceContentFor(smEnd.source!) ?.split(/\r?\n/g)[smEnd.line! - 1] // 省略 console 打印代码 }) }
完整demo2代码
这块就对齐了mpx-es-check
的source-map
解析能力
HTML支持
这个就比较好办了,只需要将script
里的内容提取出来,调用上述的checkCode
方法,然后对结果进行一个行列号的优化即可
这里提取的方法很多,可以
正则匹配
- cheerio:像jQuery一样操作
- parse5:生成AST,递归遍历需要的节点
- htmlparser2:生成AST,相比
parse5
更加,解析策略更加”包容“
小试对比了一下,最后发现是用parse5
更符合这个场景(编写代码更少)
import * as parse5 from 'parse5' const htmlAST = parse5.parse(code, { sourceCodeLocationInfo: true })
下面是生成的AST示例: astexplorer.net/#/gist/0372…
通过nodeName
或者tagName
就可以区分节点类型,这里简单写个遍历方法
节点可以通过childNodes
属性区分是否包含子节点
function traverse(ast: any, traverseSchema: Record<string, any>) { traverseSchema?.[ast?.nodeName]?.(ast) if (ast?.nodeName !== ast?.tagName) { traverseSchema?.[ast?.tagName]?.(ast) } ast?.childNodes?.forEach((n) => { traverse(n, traverseSchema) }) }
这里遍历一下demo代码生成的ast
traverse(htmlAST, { script(node: any) { const code = `${node.childNodes.map((n) => n.value)}` const loc = node.sourceCodeLocation if (code) { console.log(code) console.log(loc) } } })
完整demo1代码
获得对应的源码后就可以调用之前的checkCode
方法,对错误行号做一个拼接即可得到错误信息
traverse(htmlAST, { script(node: any) { const code = `${node.childNodes.map((n) => n.value)}` const loc = node.sourceCodeLocation if (code) { const errList = checkCode(code) errList.forEach((err) => { console.log( 'line:', loc.startLine + err.loc.start.line - 1, 'column:', err.loc.start.column ) console.log(err.source) console.log() }) } } })
完整demo2代码
组建CLI能力
这里就不再赘述CLI过程代码,核心的已在前面阐述,这里直接上最终成品的使用演示,参数同es-check
保持一致
npm i @sugarat/es-check -g
检测目标文件
escheck es5 testProject/**/*.js testProject/**/*.html
日志输出到文件
escheck es5 testProject/**/*.js testProject/**/*.html --out
最终对比
取了2者的优点相结合然后做了一定的增强
最后
当然这个工具可能存在bug,遗漏部分场景等情况,读者试用可以评论区给反馈,或者库里直接提issues
有其它功能上的建议也可评论区留言交流
完整源码移步=>Github
参考
- es-check:社区出品
- mpx-es-check:滴滴出品 MPX 框架的配套工具