力扣笔试题-解析报错堆栈为JSON格式
Reading Time:The full text has 1272 words, estimated reading time: 7 minutes
Creation Date:2020-04-29
Previous Article:项目规范化1-lint配置与使用
Next Article:力扣笔试题-Webpack的代码切割和长效化缓存(3版和4版)
BEGIN
前言
题目定位是基础编程体, 考验受试者基础编程能力, 点击访问题目地址 🔗
这个题目的附加加分项(除单元测试)通过其它文章分析!
题目
前端可以使用try catch
捕捉报错堆栈信息, 一般会解析堆栈信息给后端处理, 传给后端的一般就是堆栈信息的JSON化格式
- 如在Chrome浏览器中报错堆栈信息可能是:
const fixtureStack = `TypeError: Error raised
at bar http://192.168.31.8:8000/c.js:2:9
at foo http://192.168.31.8:8000/b.js:4:15
at calc http://192.168.31.8:8000/a.js:4:3
at <anonymous>:1:11
at http://192.168.31.8:8000/a.js:22:3
`;
- 在Firefox浏览器中报错信息可能是:
const fixtureFirefoxStack = `
bar@http://192.168.31.8:8000/c.js:2:9
foo@http://192.168.31.8:8000/b.js:4:15
calc@http://192.168.31.8:8000/a.js:4:3
<anonymous>:1:11
http://192.168.31.8:8000/a.js:22:3
`;
如果 stack 中某一行不带文件路径,则忽略掉这行信息
堆栈报错信息解析成JSON后的格式为:
interface ErrorMessage {
message: string
stack: Array<{
line: number
column: number
filename: string
}>
}
示例:
// 堆栈信息
TypeError: Error raised
at bar http://192.168.31.8:8000/c.js:2:9
// 转换后输出
{
message: 'Error raised',
stack: [
{
line: 2,
column: 9,
filename: 'http://192.168.31.8:8000/c.js'
}
]
}
分析题目
- 首先想到这道题肯定就是解析文本生成指定数据结构, 这种A解析成B的有熟悉AST的肯定会想到用AST的方式, 做个解释器, 字符串不存在运算符和优先级, 这道题很明显用AST做肯定是不太合适的
- 第直接使用正则表达式的exec方式实现分组解析.
- 第三种就是直接逐行强制解析, 分析Chrome和Firefox的格式区别发现存在很大的共性, 因此第二种方法是将两种格式通过共性解析成一种格式, 再然后逐行解析成JSON格式
通过分析发现:
- 合法的stack格式都会包含
path:line:col
的 - 且message要么不出现, 要么就是第一行
正则表达式解析
这里运用分析题目的第2种方案进行问题的解答
使用正则表达式解析主体代码才17行, 非常精简但功能也是非常强大, 完美的解决了这个问题, 完整代码:
// 文件名为 index.js
interface ErrorMessage {
message: string
stack: Array<{
line: number
column: number
filename: string
}>
}
module.exports.stack2json = function stack2json(errStr: string): ErrorMessage {
/* *********************************************
* 传入Chrome或者FireFix的报错信息解析成JSON格式数据
*
* @param {string} errStr 报错信息
* @returns {errorMessage} 解析后的JSON格式堆栈报错数据
* *********************************************/
const errorMessage: ErrorMessage = {
message: '',
stack: [],
}
const parseStack = /([A-Za-z0-9:\/\.]+):(\d+):(\d+)/g;
const parseMsg = /^\s*\w+:\s?([^<>:\n]+)/;
let ans = parseMsg.exec(errStr);
errorMessage.message = ans? ans[1]: '';
while(ans = parseStack.exec(errStr)) {
errorMessage.stack.push({
line: Number(ans[2]),
column: Number(ans[3]),
filename: ans[1],
});
}
return errorMessage
}
逐行解析
这里运用分析题目的第2种方案进行问题的解答
完整代码如下(此处代码经过prettier格式化):
// 文件名为 index.js
/**
* 通过实验及分析可知: 合法报错堆栈信息总是URL:line<number>:column<number>同时出现并结尾
*/
interface ErrorMessage {
message: string
stack: Array<{
line: number
column: number
filename: string
}>
}
function getFilepathIdx(text: string): number {
/* *********************************************
* 传入行文本, 匹配url:line:column格式的下标
*
* @param {string} text 字符串
* @returns {number} 返回匹配到的下标, 未匹配到返回-1
* *********************************************/
return text.search(/http(s)?:\/\/.*:\d+:\d+$/g)
}
function isMessage(text: string): boolean {
/* *********************************************
* 传入字符串, 判断是否为报错信息
*
* @param {string} text 字符串
* @returns {boolean} 返回是否为message的布尔值
* *********************************************/
return getFilepathIdx(text) === -1 && !/^<\w+>:\d+:\d+$/.test(text) && text.indexOf(':') > -1
}
module.exports.stack2json = function stack2json(errStr: string): ErrorMessage {
/* *********************************************
* 传入Chrome或者FireFix的报错信息解析成JSON格式数据
*
* @param {string} errStr 报错信息
* @returns {errorMessage} 解析后的JSON格式堆栈报错数据
* *********************************************/
const errList: string[] = errStr
.trim()
.split('\n')
.map((text) => text.trim())
const errorMessage: ErrorMessage = {
message: '',
stack: [],
}
if (isMessage(errList[0])) {
errorMessage.message = errList[0]
.split(':')
.slice(1)
.join(':')
.trim()
errList.shift()
}
errList.reduce((ans: ErrorMessage, txt: string): ErrorMessage => {
const reIdx = getFilepathIdx(txt)
if (reIdx === -1) {
return ans
} else {
const words: string[] = txt.slice(reIdx).split(':')
const column: string | undefined = words.pop()
const line: string | undefined = words.pop()
ans.stack.push({
line: Number(line),
column: Number(column),
filename: words.join(':').trim(),
})
return ans
}
}, errorMessage)
return errorMessage
}
单元测试
最后我们定义单元测试脚本来测试下是否通过:
定义单元测试的mock数据
// 文件名为 mockdata.js
module.exports.chromeStack = `TypeError: Error raised
at bar http://192.168.31.8:8000/c.js:2:9
at foo http://192.168.31.8:8000/b.js:4:15
at calc http://192.168.31.8:8000/a.js:4:3
at <anonymous>:1:11
at http://192.168.31.8:8000/a.js:22:3
`;
module.exports.chromeStackJson = {
message: 'Error raised',
stack: [
{
line: 2,
column: 9,
filename: 'http://192.168.31.8:8000/c.js',
},
{
line: 4,
column: 15,
filename: 'http://192.168.31.8:8000/b.js',
},
{
line: 4,
column: 3,
filename: 'http://192.168.31.8:8000/a.js',
},
{
line: 22,
column: 3,
filename: 'http://192.168.31.8:8000/a.js',
},
],
};
module.exports.firefoxStack = `
bar@http://192.168.31.8:8000/c.js:2:9
foo@http://192.168.31.8:8000/b.js:4:15
calc@http://192.168.31.8:8000/a.js:4:3
<anonymous>:1:11
http://192.168.31.8:8000/a.js:22:3
`;
module.exports.firefoxStackJson = {
message: '',
stack: [
{
line: 2,
column: 9,
filename: 'http://192.168.31.8:8000/c.js',
},
{
line: 4,
column: 15,
filename: 'http://192.168.31.8:8000/b.js',
},
{
line: 4,
column: 3,
filename: 'http://192.168.31.8:8000/a.js',
},
{
line: 22,
column: 3,
filename: 'http://192.168.31.8:8000/a.js',
},
],
};
module.exports.simpleStack = `
TypeError: Error raised
at bar http://192.168.31.8:8000/c.js:2:9
`;
module.exports.simpleStackJson = {
message: 'Error raised',
stack: [
{
line: 2,
column: 9,
filename: 'http://192.168.31.8:8000/c.js',
},
],
};
定义单元测试代码
// 文件名 index.test.js
const {
chromeStack,
chromeStackJson,
firefoxStack,
firefoxStackJson,
simpleStack,
simpleStackJson,
} = require('./mockdata')
const { stack2json } = require('./index')
test('hire Chrome报错格式测试', () => {
expect(stack2json(chromeStack)).toEqual(chromeStackJson)
})
test('hire Firefox报错格式测试', () => {
expect(stack2json(firefoxStack)).toEqual(firefoxStackJson)
})
test('hire 简单样例测试', () => {
expect(stack2json(simpleStack)).toEqual(simpleStackJson)
})
执行单元测试结果:
PASS src/index.test.ts
✓ hire Chrome报错格式测试 (3ms)
✓ hire Firefox报错格式测试 (1ms)
✓ hire 简单样例测试
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 2.341s, estimated 3s
Ran all test suites.
Done in 3.40s.
测试通过符合预期!
FINISH
Previous Article:项目规范化1-lint配置与使用
Next Article:力扣笔试题-Webpack的代码切割和长效化缓存(3版和4版)