日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

百度工程师手把手教你实现代码规范检测工具

發布時間:2024/10/12 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 百度工程师手把手教你实现代码规范检测工具 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

01 引言

代碼規范是軟件開發領域經久不衰的話題。在前端領域中,說到代碼規范,我們會很容易想到檢查代碼縮進、尾逗號以及分號等等,除此之外,代碼規范還包括了針對特殊場景定制化的檢查。JavaScript 代碼規范檢查工具包括 JSLint、JSHint、ESLint、FECS 等等,樣式代碼規范檢查工具主要為 StyleLint。

02 背景

san-native 是百度 APP 內部的一套動態 NA 視圖框架,利用 JS 引擎驅動 NA 端渲染,使得 web 前端工程師可以十分方便的編寫原生移動應用,一套代碼多端運行。隨著百度 APP 中越來越多的業務開始接入 san-native,在此過程中,經常遇到 h5 中的一些樣式屬性以及事件在 san-native 中不支持,不按照 san-native 中內置組件嵌套規則的代碼導致渲染結果不符合預期。比如下面一段.san 文件中的代碼存在多處錯誤會導致端上渲染不正常甚至導致 crash:

  • 第 2 行:在 san-native 中不支持行內樣式 flex-basis
  • 第 3 行:在 san-native 中不支持滾動事件 on-scroll
  • 第 6 行:在 san-native 中文本節點 span 不允許嵌套 img
  • 第 7 行:內置組件 lottie-view 必須要有 source 或者 src 屬性
  • 第 23 行:在 san-native 中不支持 display:inline
  • <template><div style="background-color:#fff; flex-basis:100px"><div on-scroll="onScroll" class="{{$style['demolist-wrapper']}}">內容</div><span><img />san-native中span不允許嵌套img</span><lottie-view /></div> </template> <script>export default {components: {},onScroll() {// do something},initData() {return {};}} </script> <style lang="less" module>.demolist-wrapper {display: inline;}

    因此,為了能夠在編碼階段提前發現這些問題,我們需要對代碼進行一些特殊的檢測,包括樣式,事件,以及嵌套規則。為了實現這樣的功能,我們啟動了 san-native-linter 項目,該項目中包含了兩個相互獨立的插件:@baidu/eslint-plugin-san-native 以及 @baidu/stylelint-plugin-san-native,我們將逐一介紹其實現原理。

    03 抽象語法數(AST)

    首先我們需要了解代碼檢測的主角 —— 抽象語法樹 (Abstract Syntax Tree)。在計算機科學中,抽象語法樹簡稱 AST,它是源代碼語法結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每個節點都表示源代碼中的一種結構。

    將字符串源碼轉換成 AST 的工具稱為解析器,常見的 Javascript 解析器有 @babel/parser,espree,acorn 等,樣式解析器有 postcss,cssTree 等。AST 的生成有兩個步驟:

    • 詞法分析(分詞):將整個代碼字符串分割成最小語法單元數組
    • 語法分析:在分詞基礎上建立分析語法單元之間的關系

    我們可以通過在線工具 [1] 查看一段代碼的 AST,比如下圖所示的 AST,圖中用到的解析器為 @babel/eslint-parsre,右側所示的對象為左側代碼對應的 AST,該 AST 的根節點 type 為 Program,其 body 中有兩個子節點,分別為 import 以及 export 對應的語法節點,其 type 分別為:ImportDeclaration 與 ExportDefaultDeclaration。每個節點中 range 表示當前節點對應的代碼在字符串源碼中的開始與結束位置,loc 為開始與結束位置的行列信息。

    04eslint-plugin-san-native

    介紹 eslint-plugin-san-native 插件的實現之前,我們會先介紹 ESlint 中的規則 (rule),ESlint 配置與復用方案以及 ESlint 的運行原理,最后介紹插件如何實現以及關鍵的技術點。

    ESlint 中的規則(rule)

    上文中已經通過簡單的例子介紹了抽象語法樹的結構,并且在引言部分已經簡述了 ESlint 檢測代碼的核心思想,即對 AST 進行處理從而定位不符合規定的代碼,在 ESlint 中對 AST 進行處理的實體就是這里所說的規則(rule),下面給出了一個規則的示例代碼:

    module.exports = {meta: {type: "problem",docs: {...},schema: [],messages: {readonlyMember: "The members of '{{name}}' are read-only."}},create(context) {return {ImportDeclaration(node) {context.report({node: node,messageId: "readonlyMember",data: {name: 'xxx'}});}};} };

    在這樣的一個規則中,我們需要導出一個對象,包括 meta 屬性以及 create 方法,前者用于標記該 rule 的一些信息,后者則用于處理 AST 某個節點,并提供錯誤信息與出錯的節點。

    • meta 中,通過 type 標記規則的類型,docs 包含了規則的文檔鏈接等信息,schema則表示了配置規則應該遵守的約定,messages 包含了錯誤信息。
    • create 函數:
      1.返回的對象中具有一個名為 ImportDeclaration 的方法,我們將該方法稱之為 import 語法節點的訪問器 (visitor),即在 ESlint 對整個 AST 遍歷的過程中,訪問到 import 語法節點的時候會調用所有名稱為 ImportDeclaration 的 visitor。
      2.create 函數接收的參數為 context 對象,該對象上掛載了 ESlint/ 自定義解析器為 rule 提供的方法以及用戶配置文件中的自定義配置信息,更多的屬性與方法見官方文檔。這里我們調用 context.report 將錯誤信息以及對應的語法節點提供給 ESLint。

    ESlint 配置的復用方案

    上文介紹了 ESlint 中的規則,在實際的工程應用中我們可以通過對規則進行定制化的配置來滿足特定的需求,但是如果每啟動一個項目,我們都需要進行相同的配置,勢必會帶來一定的時間成本。ESLint 提供了全面、靈活的配置能力,可以配置解析器、規則、環境、全局變量等等;可以快速引入另一份配置,和當前配置層疊組合為新的配置;還可以將配置好的規則集發布為 npm 包,在工程內快速應用。接下來,我們以 @ecomfe/eslint-config 為例看看如何高效的實現配置的復用,下圖為該代碼庫的目錄結構:

    在 @ecomfe/eslint-config 中,每個 js 文件都是一份 eslint 的配置,根目錄下的入口文件 index.js 為基礎配置 (base),其他文件夾可以看作是對基礎配置的擴展,比如 san 文件夾下是關于 san 的一些規則的配置,在實際的項目中可以通過下面的方式引入:

    module.exports = {extends: ['@ecomfe/eslint-config','@ecomfe/eslint-config/san', // 注意順序// 或者選擇嚴格模式// '@ecomfe/eslint-config/san/strict',], };

    ESlint 會將 extends 字段中的所有配置文件合并起來,每個配置文件包含如下幾個內容:

    module.exports = {parser: 'xxx',parserOptions: {parser: 'yyy',sourceType: 'module'},plugins: [],env: {},rules: {'indent-legacy': 'off',} };

    在這樣一個配置文件中,各個字段的含義如下:

    • parser:用于申明自定義 parser,該 parser 會將文件內容轉換成 AST
    • parserOptions:自定義 parser 的配置項
    • plugins:申明使用的 ESlint 插件,這部分會在后面 ESlint 工作原理介紹
    • env:申明檢測所處的環境,該選項用于引入一組預定義的全局變量
    • rules:對規則的配置

    ESlint 中插件與配置文件的區別

    上文中依次介紹了 ESlint 的規則的實現以及 ESlint 配置的復用,本節我們說明插件與配置文件之間的區別,ESlint 插件的入口文件示例代碼如下:

    module.exports = {rules: {'no-style-float': require('./rules/no-style-float'),// ...},processors: {'.san': require('./processor'),// ...},configs: {always: {plugins: ['@baidu/san-native'],rules: {'@baidu/san-native/no-style-float': 'error',}},// ...} };

    在這個入口文件中,我們向 ESlint 提供了一個對象,該對象中包含的屬性有:

    • rules:包含了該插件所有的規則的具體實現
    • processors:這里我們定義了專門用于處理.san 文件字符串源碼以及檢查信息的 processor
    • configs:包含了一些配置,可以看到與 @ecomfe/eslint-config 中的配置文件類似,具備 plugins選項,以及對 rules 的配置。

    需要說明的是:為了復用上面 configs.always 的配置,我們可以在項目的.eslintrc.* 文件中 extends 選項加上如下代碼:

    module.exports = {extends: ['plugin:@baidu/san-native/always'] };

    因此 ESlint 插件以及配置文件的區別可以總結如下:

    • plugin 插件主要涉及自定義規則的具體實現,同時還能夠提供配置
    • extend 配置主要涉及規則的具體配置

    ESlint 的工作原理

    接下來介紹 ESlint 是如何處理各種配置文件的,以及插件與配置文件中各字段在 ESlint 中的作用。ESlint 提供了命令行的方式來檢測某個文件的代碼,比如,我們想對.san 文件進行檢查,那么可以通過下面的命令來實現:

    eslint --ext .san src/app/component/animate/index.san

    當我們執行命令行的時候,ESlint 的工作原理如下圖所示:

    從上圖當中我們可以看到,文件的字符串內容首先會被插件的 prepocess 處理,然后處理的結果被 parser 解析成對應的 AST,然后遍歷 AST 的同時執行每個規則提供的方法,最后得到的檢測結果會被 postprocess 處理。因此 ESlint 插件中的 processors 屬性給開發者提供了操作字符串源碼以及處理檢測結果的能力。接下來分析 ESlint 配置中的一些字段在檢測過程中的作用:

    *處理.eslintrc.配置文件

  • extends:以【配置的復用方案】中的代碼為例,其中 @ecomfe/eslint-config 以及@ecomfe/eslint-config/san 會被合并,按照在數組中的順序,相同的選項,后者的配置會覆蓋前者。比如,前者的配置中parser: @babel/eslint-parser 會被后者的 parser: 'san-eslint-parser’替換,plugins,rules 都會被收集到一個對象當中,收集的規則為當前配置中的 rules 會被當前配置中extends,plugins 的配置規則覆蓋。
  • root:由于在讀取配置文件的時候,會根據傳入的 filePath 依次往上查找對應的文件,并將配置文件解析出來。當某個配置文件設置了 root 為 true 的時候,會停止繼續向上搜索。
  • plugins:該字段決定了最終處理某類文件的 processor 是哪個插件提供的,舉例來說,當 A 與 B 中都申明了對.san文件處理的 processor,如果按照下面的配置,ESlint 最終會用 B 插件提供的 processor 處理.san文件,如果將 plugins 字段中兩個插件互換位置,則 ESlint 最終會用 A 插件提供的processor。總的來說,ESlint 會從 plugins 字段申明的插件中從后向前找到第一個用于處理.san 文件的processor。
  • module.exports = {plugins: ["A","B"],extends: ["plugin:A/base","plugin:B/base"] };

    將文件內容解析成 AST

  • parser 選項:當使用了自定義的 parser 之后,那么文件的內容將會被自定義 parser 解析成 AST,否則會使用默認的parser,即 eslint 提供的 espree
  • parserOptions:該選項是當前 parser 的 option,每一個 rule 以及自定義 parser都能夠獲取到該選項的值。
  • 收集與執行 rule 生成的 visitor

    當解析器將字符串解析成 AST 之后,在遍歷 AST 的過程中會根據當前的節點類型執行對應的一些列提前注冊好的 vistor。

  • 調用每個 rule 的 create 函數收集 visitor:create 函數必須返回一個 visitor,即 key 為 AST節點類型,value 為一個接收該節點的函數,ESlint 將 create 函數返回的 vistor 注冊到內部維護的訂閱發布器上。
  • 遍歷 AST:遍歷的過程中會通過訂閱發布器執行執行收集過程中注冊的所有 vistor
  • eslint-plugin-san-native 的實現

    經過上述 ESlint 的工作原理的了解之后,我們開始介紹如何實現 eslint-plugin-san-native 來解決我們的問題,以下面的單文件組件為例:

    <template><div></div> </template> <script>import Test from './index.san';export default {} </script> <style lang="less">.a {} </style>

    有以下兩點需要考慮的地方

  • 如何將.san 文件代碼解析成 AST:顯然無法直接使用 @babel/eslint-parser 或者@typescript-eslint/parser 來進行解析,因此我們需要用自定義解析器來處理,這部分的工作san-eslint-parser 已經幫我們處理了,后續在規則實現的時候進行分析。
  • 如何處理 style中的樣式:由于 san-eslint-parser 不會處理.san 文件中 style>部分代碼,因此我們需要單獨處理,這里借助 postcss 對 style部分進行解析。
  • 項目構建

    構建的項目目錄結構如下

    入口文件 eslint-plugin-san-native/lib/index.js 的代碼如下:

    module.exports = {rules: {'no-style-float': require('./rules/no-style-float'),// ...},processors: {'.san': require('./processor'),// ...},configs: {always: {plugins: ['@baidu/san-native'],rules: {'@baidu/san-native/no-style-float': 'error',}},// ...} };

    下面我們分別實現 processor 以及 rule。

    processor 的實現

    根據 ESlint 工作原理可知,ESlint 在獲取到字符串源碼的時候,會先利用插件提供的 preprocess 處理字符串源碼,接著利用 parser 解析成 ast,然后將各個 ast 節點交給 rule 處理,接著處理后的檢測結果交給 postprocess 處理,最后再執行 fix。因此,從 preprocess 到 postprocess 的過程中,處理的文件內容是不變的(ast 會被 Object.freeze 處理),因此,我們可以在 preprocess 中將.san 中的 style 獲取之后,利用 postcss 將其解析成 ast,并存儲起來供后續所有 rule 共享。

    獲取 style對應的 AST

    const postcss = require('postcss'); const syntaxs = {less: require('postcss-less'),sass: require('postcss-sass'),scss: require('postcss-scss') }; const processor = postcss([() => {}]); module.exports = {getAst(syntax, content, plugins) {let ast = null;try {ast = syntax ? processor.process(content, {syntax}).sync(): processor.process(content).sync();} catch (error) {}return ast;},getStyleContentInfo(text) {const lines = text.split('\n');const content = /()([\s\S]*?)&lt;\/style&gt;/gi.exec(text);const langMatch = /\slang\s*=\s*("[^"]*"|'[^']*')/.exec(content[1]);const lang = langMatch[1].replace(/["'\s]/gi, '');const astFn = lang ? this.getAst : this.getAst.bind(null, syntaxs[lang]);return {startLine: lines.indexOf(content[1]),ast: astFn(content[3]),startOffset: text.indexOf(content[3])};} };

    上面的代碼根據 style中的 lang 字段,調用不同的 parser 對樣式內容進行解析,并獲取到代碼塊 style 所在的行數 startLine 以及所處文件的位置 startOffset,這些數據都是用來修正樣式 ast 節點位置的,這樣 eslint 才能在輸出錯誤信息的時候找到樣式在文件中的真實位置。當然這里也可以直接利用 postcss-syntax 提供的 syntax 傳入 postcss(defaultPlugins).process 函數中,該 syntax 可以自動根據文件名稱或者代碼內容自動選擇正確的語法解析器。

    processor

    const {styleAstCache} = require('./utils/cache'); module.exports = {preprocess(code, filename) {// 所有.san 都會處理styleAstCache.storeAst(styleHelper.getStyleContentInfo(code));return [code];},postprocess(messages) {// 清除數據styleAstCache.storeAst(null);return messages[0];} };

    在各個規則中只需要引入 styleAstCache,并調用 styleAstCache.getAst 即可獲取到樣式代碼的 AST,styleAstCache 在這里只是用于存儲 ast 而已。

    規則的實現

    由于規則的實現依賴于自定義 parser 提供的 ast,因此我們需要先對 san-eslint-parser 的原理有一定的了解,那么我們將現分析其原理,然后介紹幾類規則的實現方案。

    san-eslint-parser

    自定義 parser 需要提供 parseForESLint 方法,我們這里只關注該方法返回結果中的部分屬性 (更多屬性見官方):

    • ast:AST 根節點
    • services:自定義 parser 為 rule 提供的服務,每條規則可以通過 context.parserServices 訪問到

    ast

    san-eslint-parser 會將我們.san 的文件內容利用分成三個 block,其中利用 parserOPtions.parser 指定的解析器來處理 script 部分的內容,script 中如果是 JavaScript 代碼則 parserOPtions.parser 為 @babel/eslint-parser,如果是 Typescript 代碼則為 @typescript-eslint/parser。style 部分不會處理,template 部分當作 HTML 來解析。

    上圖所示為自定義 parser 生成的 AST,根節點的 type 為 Program,根節點的 body 屬性存儲了 script 代碼的 ast,根節點上的 templateBody 為 template 部分的 ast。由于 ESlint 只會遍歷根節點以及 body 上的節點,因此如果我們想為 templateBody 注冊 visitor,那么可以通過 services 來實現。

    services

    san-eslint-parser 會在 services 屬性上定義三個方法,我們只關注其中一個,簡化后的代碼如下:

    let emitter = null; // 發布訂閱器 function defineTemplateBodyVisitor(templateBodyVisitor) {let scriptVisitor = {};if (!emitter) {emitter = new EventEmitter();scriptVisitor["Program"] = node => {traverseNodes(rootAST.templateBody)};}for (const selector of Object.keys(templateBodyVisitor)) {emitter.on(selector, templateBodyVisitor[selector]);}return scriptVisitor; }

    該方法主要完成了兩部分的工作:

  • 收集每條 rule 中針對 templateBody 的 visitor:該方法接收的參數為templateBodyVisitor,該對象存儲了所有針對 templateBody 的 visitor,這些 visitor會注冊到自定義 parser 內部的發布訂閱器上。
  • 返回一個針對 script 對應的 AST 的 visitor:該 visitor 的 type 為 Program,根據上文中ESlint 工作原理一節,我們可以知道,ESlint 在遍歷 AST 的過程中,當遇到類型為 Program 的根節點時,會執行該visitor,并且該 visitor 會調用自定義 parser 內部方法對 templateBody 存儲的 AST 進行遍歷。
  • 因此,我們可以利用上述方法在每條 rule 中編寫相關的 visitor 來處理 templateBody 中不同 type 類型語法節點,如下代碼所示:

    module.exports = {meta: {...},create(context) {return context.parserServices.defineTemplateBodyVisitor(context, {'VElement'(node) {// do something},'VText'(node) {// do something}});} };

    屬性規則

    在 template 模板中,我們需要檢測某個標簽上的事件,內聯樣式,必選屬性三種內容,為了避免重復代碼,希望通過配置的方式實現規則。首先定義內置組件的描述信息,舉例來說:

    {"name": "lottie-view", // 標簽名稱"events": [ // 支持的事件"click","touchstart","touchmove","touchend","touchcancel","layout","longclick","pressin","pressout","firstmeaningfulpaint","animationfinish","downloadfinish"],"attributes": { "required": [],"oneOf": [["src", "source"]], // 必須的可選屬性"content": {}},"style": {"required": [],"notsurpport": []},"nestedTag": [] // 允許的子標簽 }

    上面的描述信息中依次定義了標簽名稱,支持的事件,必選的屬性,不支持 / 必須的內聯樣式,以及允許的子標簽名稱。描述信息的另一個優勢是當組件庫更新或者添加組件的時候,只需要在組件中維護這樣的信息,則可以在不發布新版本 ESlint 插件的時候應用到組件新的規則。

    在上文中已經介紹了如何在規則中通過編寫相關的 visitor 來處理 templateBody 中不同 type 類型語法節點,因此我們只需要對節點的相關數據進行一些判斷,就可以實現代碼檢測。判斷的邏輯這里不再介紹,只貼上一個 VElement 中需要關注的節點屬性:

    上圖中是下面標簽對應的節點數據,為了獲取標簽的屬性,我們可以從 startTag.attributes 中獲取,可以看到其中屬性名稱為 style 的節點數據。

    <div style="background-color:#fff; flex:1">...</div>

    樣式規則

    對于樣式規則來說,我們需要同時檢測 tempate 上的內聯樣式,也需要檢測 style塊中的樣式代碼,簡化后的規則代碼如下:

    module.exports = {meta: {...},create(context) {return context.parserServices.defineTemplateBodyVisitor(context, {'VElement[name="template"]'(node) {const {ast: result, startLine, startOffset} = styleAstCache.getAst();if (result && result.root) {result.root.walkDecls(decl => {// do something});}},VAttribute(node) {const name = utils.getName(node);if (name == null) {return;}if (name === 'style' && node.value && node.value.value) {let styleValArr = inlineStyleParser(node.value.value);styleValArr.forEach(decl => {// do something});}}});} };

    可以看到我們對 template 對應的 AST 定義了兩個 visitor,第一個 visitor 用于獲取 VElement 并且節點名稱是 template 的語法節點,在該節點的 visitor 中,利用 postcss 提供的 API 遍歷 style對應的 ast。第二個 visitor 用于檢測 template 中每個標簽上 style 屬性中的內聯樣式。

    在 vscode 中的檢測效果

    在每一條規則中,當發現不符合規則的代碼時,我們可以通過 context.report 將對應的 ast 語法節點 / 位置信息以及錯誤信息提供給 ESlint

    // 提供節點 context.report({node: node,messageId: "..." }); // 提供位置信息:loc context.report({loc: node.loc,message: "..." });

    這樣 ESlint 能夠通過 vscode 中的插件對錯誤代碼進行高亮,從而實現在編譯前提示代碼中不支持的樣式,事件,嵌套規則等等。

    05stylelint-plugin-san-native

    到此,我們介紹了如何開發 ESlint 插件檢測.san/.js/.ts 文件中的 san 組件,下面介紹如何開發 StyleLint 插件來檢測.less/.sass/.scss/.styl 文件中的樣式代碼。StyleLint 提供了類似 ESlint 的配置方式,可以在配置文件.stylelintrc.* 中 extends 多個配置文件,對單個 rule 進行配置,支持通過編寫插件實現自定義的規則,支持使用 processor 在開始檢測之前對源碼字符串進行修改,并在結果輸出之前對檢測結果進行修改。

    StyleLint 工作原理

    下圖為 StyleLint 的工作流程圖,這里的 processor.code 相當于 ESlint 中的 preprocess,而 processor.result 相當于 ESlint 中的 postprocess。StyleLint 與 ESlint 的工作原理非常相似,從整體上來說,processor.code 與 processor.result 之間的過程與 ESLint 有區別,StyleLint 中會遍歷所有 rules,然后將 AST 根節點交給每個 rule 進行遍歷,而不像 ESlint 中需要自己遍歷 AST。

    StyleLint 規則

    從上文 StyleLint 的工作流程分析可以知道,StyleLint 的規則接收一個 AST 根節點以及配置數據,因此其規則示例代碼如下:

    module.exports = function rule(primary, secondary, context) {return (root, result) => {}; };

    其中,primary 以及 secondary 為 rule 配置的時候填寫的配置,舉例來說:

    "rules": {"block-no-empty": null, // primary 為 null"color-no-invalid-hex": true, // primary 為 true"comment-empty-line-before": [ "always", // primary 為 always{"ignore": ["stylelint-commands", "between-comments"]} // secondary] }

    StyleLint 插件

    只需要將 rule 利用 StyleLint 提供的方法處理后即可生成一個插件,并且需要提供 ruleName 以及 messages

    const stylelint = require("stylelint"); const ruleName = "plugin/xxx"; const messages = stylelint.utils.ruleMessages(ruleName, {expected: "Expected ..." }); module.exports = stylelint.createPlugin(ruleName,function (primary, secondary, context) {return function (root, result) {// ...stylelint.utils.report({/* .o. */});};} ); module.exports.ruleName = ruleName; module.exports.messages = messages;

    stylelint-plugin-san-native 實現

    構建的項目目錄結構如下:

    其中入口文件 index.js 的簡化代碼如下:

    module.exports = [stylelint.createPlugin(...),stylelint.createPlugin(...),// ... ]

    同時提供了兩份配置文件分別為:always.js 以及 temporary.js,下面為 always.js 的代碼:

    module.exports = {plugins: ['.'],rules: {'@baidu/stylelint-plugin-san-native/no-flex-basis': true,// ...} };

    在實際工程項目的.stylelintrc.js 中可以通過 extends 字段復用配置文件,比如:

    module.exports = {extends: ['@baidu/stylelint-plugin-san-native/always','@baidu/stylelint-plugin-san-native/temporary'],rules: {}

    由于篇幅有限,我們只對一個具體的規則實現進行介紹,比如在 san-native 中并不支持樣式屬性 justify-content 的值被設置為 baseline,因此我們需要對該屬性的值進行檢測以及報錯處理,規則的部分關鍵代碼如下:

    const {utils} = require('stylelint'); const getDeclarationValue = require('stylelint/lib/utils/getDeclarationValue'); const declarationValueIndex = require('stylelint/lib/utils/declarationValueIndex'); const valueParser = require('postcss-value-parser'); const meta = {styleName: 'justify-content',message: `Only some values of '${styleName}' are supported in sna-native`,surrpportValue: ['flex-start', 'flex-end', 'center', 'space-between', 'space-around'] }; const ruleName = `stylelint-plugin-san-native/valid-justify-content`; const messages = utils.ruleMessages(ruleName, {expected: () => meta.message }); module.exports = function rule(primary) {return (root, result) => {const validOptions = utils.validateOptions(result, ruleName, {primary});if (!validOptions || !primary) { return; }root.walkDecls(decl => {// 將declaration語法節點上屬性鍵值對解析成ASTconst parsed = valueParser(getDeclarationValue(decl));// 遍歷每個屬性值對應的節點parsed.walk(node => {if (meta.surrpportValue.indexOf(node.value) < 0) {utils.report({// 獲取declaration語法節點中屬性值部分在與declaration語法節點開始位置的偏移量index: declarationValueIndex(decl) + node.sourceIndex,message: messages.expected(),node: decl,ruleName,result});}});});}; }; module.exports.ruleName = ruleName; module.exports.messages = messages;

    在對代碼進行分析之前,我們需要了解 postcss 返回的 AST 的兩個關鍵點:

    1.屬性聲明與賦值會被解析成類型為 declaration 的語法節點,舉例如下:

    justify-content: baseline;

    2.可以通過 AST 上的 walkDecls 方法獲取 AST 樹中的每個類型為 declaration 的語法節點,該方法是由 postcss 提供,更多的方法可見 postcss 官方文檔

    上面代碼中 rule 函數利用 root.walkDecls 遍歷語法樹中的 declaration 語法節點,并且每個 declaration 語法節點會被傳入 root.walkDecls 接收的回調函數中,在該回調函數中如果發現屬性值在 san-native 中不支持,則需要通過 stylelint.utils.report 將錯誤信息,發生錯誤的節點,以及屬性值偏移量,當前規則名稱傳遞給 StyleLint,這樣 StyleLint 才能夠定位到不規范代碼的位置。同時借助編輯器插件將不符合代碼規范的代碼高亮出來,以 vscode 為例,進行如下的高亮提示:

    06 總結

    至此,我們介紹了如何實現 ESlint 以及 StyleLint 的插件來檢測 san-native 項目中不符合規定的代碼,并從底層原理的角度上介紹了插件里各個字段以及方法在檢測過程中的作用,希望能對大家有所幫助。

    文章看完,還不過癮?
    更多精彩內容歡迎關注百度開發者中心公眾號

    總結

    以上是生活随笔為你收集整理的百度工程师手把手教你实现代码规范检测工具的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。