一款檢測代碼中TODO的eslint插件


一款檢測代碼中TODO的eslint插件

前言

看了我標題進來的同學應該也知道我做的是個啥東西

沒錯是一個eslint插件,前端魔法師們日常所使用的工具之一

什么?你不知道eslint是干嘛的--吃鯨.jpg

  • ESLint 是一個開源的 JavaScript 代碼檢查工具
  • 能在多人協作項目中幫助統一代碼風格

百聞不如一見,先帶大家看看插件效果

圖片

因為在工作中臨時會插入許多其它的事,或者有些代碼,接口是有時效性的需要手動下線

插件功能簡單講就是為不完善的代碼做標記,提醒自己如期修改完善bug

想親自嘗試一下的同學戳這里---順手Star

下面開始介紹為什么會寫這個插件,以及過程中遇到的問題,和插件的一些核心方法

背景

前端魔法師們,經常不經意間就會在代碼中下毒,坑害同事,甚至直接使"線上爆炸"

當時遇到一個線上問題就是如此:

  • 一個有實效性的活動入口,需要到點下線,一個需要常駐的活動入口,不需要下線
  • 由於實效性活動下線時間還有大半年

coder為了圖方便,直接把實效性活動業務組件拿着復用,想着到時候改,反正時間還長

半年后的一天,客述就來了,說xx入口怎么不見了,彼時開發此頁面的同事已經調走,目前的項目組基本都是后來的

第一反應是不是昨晚提交了什么代碼,把入口嚇掉了,然后就開始翻發布記錄,發現並沒有發版,就奇怪了,好好地怎么就不見了

然后同事們就開始排查代碼了,不一會兒就發現了問題所在,然后快速的改掉上線,避免影響擴大

事后復盤

  • Leader: 咱能不能用啥工具避免此類問題發生
  • 菜雞: 阿巴阿巴一堆話
  • Leader: 那行你說了這么多,這個任務就交給你了,下周給個方案
  • 菜雞: ...哦豁,要被卷鋪蓋走人了

個人思考

在不完善的代碼或有時效性的代碼處搞一個flag,通過工具檢測這個flag然后提醒此項目的所有開發者

開發者打開這個項目就能知道xx flag處有隱患

調研-可行方案

cli工具

項目啟動后,跑一個npm script

掃描目標文件,然后通過正則匹配flag關鍵字基於提示

vs code插件

前端魔法師基本都用的Vs Code

項目啟動后,自動掃描目標文件

通過正則匹配flag,然后基於提示

這個有現成的todo-tree

eslint規則

前端項目都會引入eslint 來統一團隊成員的編碼風格

只要編寫一個自定義的規則,通過分析eslint 提供的AST,就能輕易的拿到標記的flag

對比

方案 優點 缺點 學習成本
cli工具 不限制語言/文件類型,通過正則就能進行輕易的匹配 需要為每個項目單獨編寫一個指令,通過hook或者人工觸發 ❤❤❤
vs code插件 只需要用戶為編輯器安裝一個插件,即可在打開的時候進行提示 魔法師們可能用sublime/webStorm ,需要重復開發插件 ❤❤❤❤
eslint規則 只需要在項目的配置文件中加入一行規則代碼,即可實時的提示 需要安裝依賴,首次需要手動引入 ❤❤

最終選擇

基於eslint編寫一個eslint-plugin來實現自定義校驗規則

原因

  • 前端項目都會引入eslint
  • 不限制編輯器
  • 理論上不限制前端開發常用的語言/框架的檢測
  • 標准化團隊統一了所有倉庫的lint規則,只需在標准化規則中加入即可
    • 其余倉庫,只要執行依賴安裝(yarn/npm i) 就會將最新的規則引入項目,使用者能0配置接入

實現過程

下面會展開介紹插件的實現原理,會涉及到一些代碼的展示

首先eslint會將響應的文件用AST描述出來,並且提供了一些簡單的API進行操作

create(context) {
    // 取得AST
    const sourceCode = context.getSourceCode()
    // 獲取所有的注釋節點
    let comments = sourceCode.getAllComments()
}

注釋節點有兩種

// 注釋
/** 
 * Block
 **/

從注釋節點中過濾出以flag關鍵字開始的注釋節點

// 過濾出包含關鍵詞的注釋節點
comments = comments.filter(comment => {
    let { value, type } = comment
    // 展平塊狀注釋
    if (type === 'Block') {
        value = value.replace(/\*|\n/g, '')
    }
    value = value.toLowerCase().trim()
    // 保存格式化后的字符串
    comment.newValue = value
    for (const flag of dFlag) {
        // 檢測是否一關鍵字開頭
        if (value.startsWith(flag)) {
            // 保存上flag
            comment.flag = flag
            return true
        }
    }
    return false
})

此時過濾后的注釋基本格式如下

// flag ddl:time  xxxxx

接下來從中解析出ddl和提示信息

首先ddl也是一個可配置的關鍵字

需要檢測的日期格式如下

format1 demo format2 demo format3 demo
yyyy-mm-dd 2020-06-01 yyyy/mm/dd 2020/06/01 yyyymmdd 20200601
2020-06-1 2020/06/1 200601
2020-6-01 2020/6/01
20-06-01 20/06/01
20-6-1 20/6/1
20-6-01 20/6/01

所以代碼中做了許多判斷

  • 代碼可能有點shi,看官們可以給點建議優化下
// 匹配日期的正則
const rDate = [{
    reg: /((\d{4})|(\d{2}))(-((0\d)|(\d{2})|(\d{1}))){2}/,
    flag: '-' // yyyy-mm-dd|yy-mm-dd
},
{
    reg: /((\d{4})|(\d{2}))(\/((0\d)|(\d{2})|(\d{1}))){2}/,
    flag: '/'// yyyy/mm/dd|yy/mm/dd
},
{
    reg: /(\d{8})|(\d{6})/,
    flag: 'number'// yyyymmdd|yymmdd
}]
/**
 * 獲取TODO注釋中的DDL,是則返回日期值及其todo內容
 * @param {String} value 待操作字符串
 * @param {String[]} ddlSymbol 截止時間標識符
 * @param {STring} todoSymbol
 * @return {Object} 
 */
function getDDLAndText(value, ddlSymbol, todoSymbol) {
    let text = value.slice(value.indexOf(ddlSymbol) + ddlSymbol.length),
        date = ''
    for (const rdate of rDate) {
        const { reg, flag } = rdate
        const res = text.match(reg)
        if (res) {
            const [dateStr] = res
            // 再次校驗匹配的日期日期是否合法
            if (reg.test(dateStr)) {
                let year, month, day
                if (flag !== 'number') {
                    let ymd = dateStr.split(flag)
                    ymd = ymd.map(v => {
                        return v.length === 1 ? `0${v}` : v
                    })
                    year = ymd[0]
                    month = ymd[1]
                    day = ymd[2]
                } else {
                    const { length } = dateStr
                    day = dateStr.slice(length - 2)
                    month = dateStr.slice(length - 4, length - 2)
                    year = dateStr.slice(0, length - 4)
                }
                if (year.length === 2) {
                    year = new Date().getFullYear().toString().slice(0, 2) + year
                }
                text = text.slice(text.indexOf(dateStr) + dateStr.length)
                date = `${year}-${month}-${day}`
                // 日期不合格也pass掉
                if (month > 12 || day > 31) {
                    date = ''
                }
                break
            }
        }
    }
    return {
        text,
        date
    }
}

這樣就拿到了flag的截止日期內容

接下來只需要根據設置的時間警戒線進行提示即可

// 未設置DDL或者DDL不合法情況
if (!date) {
    errMsg = '沒有設置有效的Deadline,設置方法(https://github.com/ATQQ/eslint-plugin-todo-ddl)'
} else {
    const TODODate = new Date(date).getTime()
    const interval = TODODate - Date.now()
    // 如果已經到期
    if (interval < 0 || interval < oneDay) {
        errMsg = '已經過截止日期,請立即修改'
    } else {
        // 剩余天數(向下取整)
        const theRestDays = ~~(interval / oneDay)
        errMsg = theRestDays <= dWarnLine ? `還有${theRestDays}天截止,請盡快修改` : ''
    }
}
if (errMsg) {
    context.report({
        node: comment,
        message: `TODO WARN: ${errMsg} --> ${text}`
    })
}

到此就大工搞成了,可以發包上線了

總結思考

不足之處

  1. 代碼還有可改進之處
  2. 插件上線后並沒大改過,功能感覺還可以增強,需要朋友們多給點意見

收獲

  1. leader還是評價不錯,畢竟那時候實習生身份剛入職幾天
  2. 自己也有所提升,對eslint的工作原理有了新的認識
  3. 也為團隊日后各種花式校驗規則提供了技術積累

未來

  1. 找時間根據反饋,迭代一下插件,提高其可玩性

其它

本文正在參與「掘金 2021 春招闖關活動」, 點擊查看 活動詳情


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM