需要了解的概念
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科平。
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è)事件
onCodePathStart
、onCodePathEnd
口四、onCodePathSegmentStart
孵运、onCodePathSegmentEnd
、onCodePathSegmentLoop
當(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的官方文檔粪小。
code-path就是程序的執(zhí)行路徑大磺。我們的代碼會(huì)被解析成一些code path,單個(gè)code path又是多個(gè)CodePathSegment
組成。
看一下官方文檔中的例子探膊,這段if代碼段:
if (a && b) {
foo();
}
bar();
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了抡诞。