目錄:
  1. 前言
    1. 题目
      1. 分析题目
        1. 正则表达式解析
          1. 逐行解析
            1. 单元测试
              1. 定义单元测试的mock数据
              2. 定义单元测试代码

          力扣笔试题-解析报错堆栈为JSON格式

          閱讀時間:全文 1272 字,預估用時 7 分鐘
          創作日期:2020-04-29
          文章標籤:
           
          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'
              }
            ]
          }

          分析题目

          1. 首先想到这道题肯定就是解析文本生成指定数据结构, 这种A解析成B的有熟悉AST的肯定会想到用AST的方式, 做个解释器, 字符串不存在运算符和优先级, 这道题很明显用AST做肯定是不太合适的
          2. 第直接使用正则表达式的exec方式实现分组解析.
          3. 第三种就是直接逐行强制解析, 分析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

          隨機文章
          人生倒計時
          default