好奇:eslint的工作原理

需要了解的概念

1. 抽象語法樹AST(Abstract Syntax Tree)

維基百科中的解釋:
源代碼語法結(jié)構(gòu)的一種抽象表示倒庵。它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu)灸蟆,樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)屉佳。

如webpack脚作、rollup钝尸、UglifyJS挎狸、Lint等很多的工具和庫的核心都是通過Abstract Syntax Tree 抽象語法樹這個(gè)概念來實(shí)現(xiàn)對(duì)代碼的檢查钾埂、分析等操作的河闰。
AST explorer這個(gè)網(wǎng)站可以在線生成AST科平。

image.png

2. JavaScript Parser

JavaScript Parser,把js源碼轉(zhuǎn)化為抽象語法樹的解析器姜性。

瀏覽器會(huì)把js源碼通過解析器轉(zhuǎn)為抽象語法樹瞪慧,再進(jìn)一步轉(zhuǎn)化為字節(jié)碼或直接生成機(jī)器碼。

一般來說每個(gè)js引擎都會(huì)有自己的抽象語法樹格式部念,Chrome的v8引擎弃酌,firefox的SpiderMonkey引擎等等,MDN提供了詳細(xì)SpiderMonkey AST format的詳細(xì)說明儡炼,算是業(yè)界的標(biāo)準(zhǔn)妓湘。

發(fā)展到現(xiàn)在可能不同的JavaScript Parser的AST格式會(huì)不同,或基于SpiderMonkey AST format乌询,或重新設(shè)計(jì)自己的AST format榜贴,或基于SpiderMonkey AST format優(yōu)化改進(jìn)。通過優(yōu)化抽象語法樹妹田,來使程序運(yùn)行的更快唬党,也是一種提高效率的方法。
常用的JavaScript Parser有:

  • Esprima
  • UglifyJS2
  • Traceur
  • Acorn
  • Shift

eslint用的是espree

抽象語法樹的用途

  • 代碼語法的檢查鬼佣、代碼風(fēng)格的檢查驶拱、代碼的格式化、代碼的高亮沮趣、代碼錯(cuò)誤提示屯烦、代碼自動(dòng)補(bǔ)全等等
    • IDE的錯(cuò)誤提示、格式化房铭、高亮驻龟、自動(dòng)補(bǔ)全等等
    • Eslint等代碼風(fēng)格工具
  • 代碼混淆壓縮
    • UglifyJS2等
  • 優(yōu)化變更代碼,改變代碼結(jié)構(gòu)使達(dá)到想要的結(jié)構(gòu)
    • 代碼打包工具webpack缸匪、rollup等等
    • CommonJS翁狐、AMD、CMD凌蔬、UMD等代碼規(guī)范之間的轉(zhuǎn)化
    • CoffeeScript露懒、TypeScript、JSX等轉(zhuǎn)化為原生Javascript

Eslint如何工作的

eslint產(chǎn)生的背景

C 語言誕生之初砂心,程序員編寫的代碼風(fēng)格各異懈词,在移植時(shí)會(huì)出現(xiàn)一些因?yàn)椴粐?yán)謹(jǐn)?shù)拇a段導(dǎo)致無法被編譯器執(zhí)行的問題。于是在 1979 年辩诞,一款叫 lint的程序被開發(fā)出來坎弯,能夠通過掃描源代碼檢測(cè)潛在的錯(cuò)誤。

最初javascript開發(fā)出來是在web中實(shí)現(xiàn)簡(jiǎn)單的交互(表單提交),后來隨著互聯(lián)網(wǎng)發(fā)展抠忘,需要展示更多的東西撩炊,業(yè)務(wù)日漸復(fù)雜化,前端項(xiàng)目越來越龐大崎脉。再加上 JavaScript 本身設(shè)計(jì)上存在許多缺陷拧咳,代碼不嚴(yán)謹(jǐn)也可能就會(huì)觸發(fā)神奇的錯(cuò)誤。

所以語法檢測(cè)工具就誕生了囚灼。在市場(chǎng)檢驗(yàn)下骆膝,eslint脫穎而出。參考資料eslint起源啦撮。

eslint配制

ESlint 被設(shè)計(jì)為完全可配置的谭网,這意味著你可以關(guān)閉每一個(gè)規(guī)則而只運(yùn)行基本語法驗(yàn)證,或混合和匹配 ESLint 默認(rèn)綁定的規(guī)則和你的自定義規(guī)則赃春,以讓 ESLint 更適合你的項(xiàng)目愉择。可以查看詳細(xì)eslint配置官方文檔织中。

執(zhí)行下面命令可以創(chuàng)建一個(gè)配置

eslint --init
/** .eslintrc.js */
module.exports = {
    "extends": "eslint:recommended",
    "rules": {
        // enable additional rules
        "indent": ["error", 4],
        "linebreak-style": ["error", "unix"],
        "quotes": ["error", "double"],
        "semi": ["error", "always"],

        // override default options for rules from base configurations
        "comma-dangle": ["error", "always"],
        "no-cond-assign": ["error", "always"],

        // disable rules from base configurations
        "no-console": "off",
    }
}
  • extends 屬性值可以是:

    • 在配置中指定的一個(gè)字符串
    • 字符串?dāng)?shù)組:每個(gè)配置繼承它前面的配置

ESLint 遞歸地進(jìn)行擴(kuò)展配置锥涕,所以一個(gè)基礎(chǔ)的配置也可以有一個(gè) extends 屬性。

  • rules 屬性可以做下面的任何事情以擴(kuò)展(或覆蓋)規(guī)則:

    • 啟用額外的規(guī)則
    • 改變繼承的規(guī)則級(jí)別而不改變它的選項(xiàng):
      • 基礎(chǔ)配置:"eqeqeq": ["error", "allow-null"]
      • 派生的配置:"eqeqeq": "warn"
      • 最后生成的配置:"eqeqeq": ["warn", "allow-null"]
    • 覆蓋基礎(chǔ)配置中的規(guī)則的選項(xiàng)
      • 基礎(chǔ)配置:"quotes": ["error", "single", "avoid-escape"]
      • 派生的配置:"quotes": ["error", "single"]
      • 最后生成的配置:"quotes": ["error", "single"]

最出名的兩個(gè)風(fēng)格:airbnb狭吼、standard

rule是怎么工作的层坠?

先了解一下rule的結(jié)構(gòu),官方文檔rule刁笙。

eslint推薦規(guī)則rule列表里我們選一個(gè)常見而且簡(jiǎn)單的規(guī)則來學(xué)習(xí):no-const-assign 禁止修改 const 聲明的變量破花。在eslint源碼庫中找到對(duì)應(yīng)的這條規(guī)則的源碼

const astUtils = require("../util/ast-utils");

module.exports = {
   meta: {
       type: "problem",

       docs: {
           description: "disallow reassigning `const` variables",
           category: "ECMAScript 6",
           recommended: true,
           url: "https://eslint.org/docs/rules/no-const-assign"
       },

       schema: [],

       messages: {
           const: "'{{name}}' is constant."
       }
   },

   create(context) {

       /**
        * Finds and reports references that are non initializer and writable.
        * @param {Variable} variable - A variable to check.
        * @returns {void}
        */
       function checkVariable(variable) {
           astUtils.getModifyingReferences(variable.references).forEach(reference => {
               context.report({ node: reference.identifier, messageId: "const", data: { name: reference.identifier.name } });
           });
       }

       return {
           VariableDeclaration(node) {
               if (node.kind === "const") {
                   context.getDeclaredVariables(node).forEach(checkVariable);
               }
           }
       };

   }
}

每條rule是一個(gè)暴露的nodejs模塊疲吸,模塊包含兩個(gè)屬性:meta座每、create

  • meta:包含規(guī)則的元數(shù)據(jù)摘悴。類別峭梳,文檔,可接收的參數(shù)的 schema蹂喻,messages等
  • create:create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree (AST as defined by ESTree) of JavaScript code:
    • if a key is a node type or a selector, ESLint calls that visitor function while going down the tree
    • if a key is a node type or a selector plus :exit, ESLint calls that visitor function while going up the tree
    • if a key is an event name, ESLint calls that handler function for code path analysis

我的理解就是create返回的這個(gè)對(duì)象包含三種不同類型的函數(shù)

  • AST中選擇器名字命名的函數(shù),例如VariableDeclaration
  • AST中選擇器名字+:exit的函數(shù)葱椭, 列入FunctionExpression:exit
  • code path 抽象的 5 個(gè)事件onCodePathStartonCodePathEnd口四、onCodePathSegmentStart孵运、onCodePathSegmentEndonCodePathSegmentLoop

當(dāng)觸發(fā)這個(gè)函數(shù)發(fā)現(xiàn)不符合規(guī)則的時(shí)候會(huì)調(diào)用context.report()拋出問題蔓彩。
比如掐松,當(dāng)我們代碼中出現(xiàn)修改const聲明的變量phone的時(shí)候踱侣,就會(huì)拋出phone is constant.的錯(cuò)誤

舉個(gè)有完整的create栗子:array-callback-return 的rule:

function checkLastSegment (node) {
    // report problem for function if last code path segment is reachable
}

module.exports = {
    meta: { ... },
    create: function(context) {
        // declare the state of the rule
        return {
            ReturnStatement: function(node) {
                // at a ReturnStatement node while going down
            },
            // at a function expression node while going up:
            "FunctionExpression:exit": checkLastSegment,
            "ArrowFunctionExpression:exit": checkLastSegment,
            onCodePathStart: function (codePath, node) {
                // at the start of analyzing a code path
            },
            onCodePathEnd: function(codePath, node) {
                // at the end of analyzing a code path
            }
        };
    }
};

code-path-analysis

code-path就是程序的執(zhí)行路徑大磺。我們的代碼會(huì)被解析成一些code path,單個(gè)code path又是多個(gè)CodePathSegment組成。

看一下官方文檔中的例子探膊,這段if代碼段:

if (a && b) {
    foo();
}
bar();
image.png

ESLint 將 code path 抽象為 5 個(gè)事件杠愧,在rule里可以給這些事件綁定函數(shù)。

那eslint是如何觸發(fā)rule里的函數(shù)的呢逞壁?

查看官方文檔

eslint 對(duì)象的主要方法是 verify()流济,接收兩個(gè)參數(shù):要驗(yàn)證的源碼文本和一個(gè)配置對(duì)象(通過準(zhǔn)備好的配置文件加命令行操作會(huì)生成配置)。該方法首先使用 espree(或配置的解析器) 解析獲取的文本腌闯,檢索 AST绳瘟。AST 用來產(chǎn)生行/列和范圍的位置,對(duì)報(bào)告問題的位置和檢索與 AST 節(jié)點(diǎn)有關(guān)的源文本很有幫助姿骏。

一旦AST是可用的糖声,estraverse 被用來從上到下遍歷 AST。在每個(gè)節(jié)點(diǎn)分瘦,eslint對(duì)象觸發(fā)與該節(jié)點(diǎn)類型同名的一個(gè)事件(即 “Identifier”蘸泻,”WithStatement” 等)。在回退到子樹上時(shí)嘲玫,一個(gè)帶有 AST 類型名稱和 “:exit” 后綴的事件被觸發(fā)悦施,比如 “Identifier:exit” - 這允許規(guī)則在正向和逆向遍歷開始起作用。每個(gè)事件在恰當(dāng)?shù)?AST 節(jié)點(diǎn)可用時(shí)觸發(fā)去团。

現(xiàn)在我們已經(jīng)深入的了解rule了抡诞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市土陪,隨后出現(xiàn)的幾起案子昼汗,更是在濱河造成了極大的恐慌,老刑警劉巖旺坠,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乔遮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡取刃,警方通過查閱死者的電腦和手機(jī)蹋肮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來璧疗,“玉大人坯辩,你說我怎么就攤上這事”老溃” “怎么了漆魔?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我改抡,道長(zhǎng)矢炼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任阿纤,我火速辦了婚禮句灌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘欠拾。我一直安慰自己胰锌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布藐窄。 她就那樣靜靜地躺著资昧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荆忍。 梳的紋絲不亂的頭發(fā)上格带,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音东揣,去河邊找鬼践惑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嘶卧,可吹牛的內(nèi)容都是我干的尔觉。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼芥吟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼侦铜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钟鸵,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤钉稍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后棺耍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贡未,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蒙袍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俊卤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡害幅,死狀恐怖消恍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情以现,我是刑警寧澤狠怨,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布约啊,位于F島的核電站,受9級(jí)特大地震影響佣赖,放射性物質(zhì)發(fā)生泄漏恰矩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一茵汰、第九天 我趴在偏房一處隱蔽的房頂上張望枢里。 院中可真熱鬧,春花似錦蹂午、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巷疼,卻和暖如春晚胡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嚼沿。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工估盘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骡尽。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓遣妥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親攀细。 傳聞我的和親對(duì)象是個(gè)殘疾皇子箫踩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容