隱藏在 Eslint 的Fix 功能中的可以用來面試的算法題
我們知道 eslint 支持 fix,當(dāng)添加了 --fix 參數(shù)部分 rule 可以自動修復(fù)問題。
有沒有想過,這種功能是怎么實現(xiàn)的?babel 也能轉(zhuǎn)換代碼,它和 eslint 生成代碼的原理一樣么?
babel
babel 分為 parse、transform、generate 3 步。

在 transform 階段轉(zhuǎn)換完 AST 代碼之后,在 generate 階段會遞歸打印 AST 成目標(biāo)代碼。
generate 的原理就是遞歸根據(jù)每個 AST 的信息拼接字符串:
所以我們在插件里面改動了 AST,最終的代碼也會改。
eslint
eslint 的 rule 可以對 AST 進(jìn)行檢查,然后通過 context.report 報錯,還可以指定如何修復(fù):
自定義 rule 格式如下:
- module.exports = {
- meta: {
- fixable: true
- },
- create(context) {
- return {
- // 指定 AST 的類型
- ObjectExpression(node) {
- // 一系列檢查
- context.report({
- node,
- message: 'xxx 有錯誤',
- loc: node.loc,
- *fix(fixer) {
- yield fixer.replaceTextRange([rangeStart,rangeEnd], '替換的文本');
- }
- });
- }
- };
- }
- };
其中 fix 選項就是用于問題自動修復(fù)的,通過 fixer 的 api。
fixer 有這些 api 可用:
- insertTextAfter(nodeOrToken, text);
- insertTextAfterRange(range, text);
- insertTextBefore(nodeOrToken, text);
- insertTextBeforeRange(range, text);
- remove(nodeOrToken);
- removeRange(range);
- replaceText(nodeOrToken, text);
- replaceTextRange(range, text);
特別容易記,就是增、刪、改 3類,增分為在前面插入和在后面插入,每一類都支持基于 token 來修改 text 或者基于 range(下標(biāo)范圍)。
AST 中每個節(jié)點都保留了 range 的信息,也就是在源代碼的下標(biāo)是從哪到哪,這樣就可以根據(jù) range 來修改代碼,或者根據(jù) AST 查到 range 再去修改代碼。
那知道了對什么 range 做什么操作之后,是怎么自動修改代碼的呢?
下面是 eslint 中 fix 代碼的源碼:
- // 源碼
- const originalText = sourceCode.text;
- // 第一個 range 的開始
- const start = fixes[0].range[0];
- // 最后一個 range 的結(jié)束
- const end = fixes[fixes.length - 1].range[1];
- // 替換的文本
- let text = "";
- let lastPos = Number.MIN_SAFE_INTEGER;
- for (const fix of fixes) {
- if (fix.range[0] >= 0) {
- // 截取 range 的左邊的字符串,從當(dāng)前 range 和 上一個 range 的右邊位置取大的
- text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]);
- }
- // 拼接上修復(fù)的文本
- text += fix.text;
- // range 右邊的位置
- lastPos = fix.range[1];
- }
- // 用拼接的字符串替換 range 內(nèi)的字符串
- text += originalText.slice(Math.max(0, start, lastPos), end);
其中比較有意思的一個點是當(dāng)兩端 range 有交集的時候:
每一個 fix 都是對一個線段(range)內(nèi)文本的修復(fù),當(dāng)有交集的時候怎么處理,這其實可以作為一個算法題來考核候選人了。
從左到右應(yīng)用 fix,然后記錄當(dāng)前的 rangeRight,應(yīng)用下一段的時候就取 rangeLeft 和上一個 rangeRight 的最大值作為 rangeLeft。
把這個問題抽象出來之后還是一個比較有意思的算法題,我覺得用來面試比較不錯,而且有真實的應(yīng)用場景。
聊回正題,fix 功能的實現(xiàn)就是對每段 range 修改的文本進(jìn)行拼接,然后替換源碼字符串就可以了。
總結(jié)
babel 和 eslint 都可以修改代碼,babel 是操作了 AST,打印代碼的時候就會生成不同的代碼,而 eslint 則是一部分 rule 支持自動 fix,當(dāng)開啟了 --fix 的時候就會自動修復(fù)。
babel 生成代碼的原理是遞歸打印 AST,拼接字符串,所以改了 AST,生成的代碼就改了。
eslint 修復(fù)代碼的邏輯是對某段 range 的文本做替換,之后拼接,這個與 AST 無關(guān),所以 eslint 的 fix 功能是可選的。
比較有意思的是 eslint 的多個 rule 返回的對多段range 的修改如何應(yīng)用到對代碼修改上,當(dāng)有交集的時候怎么辦,我覺得這個問題可以作為算法題來考查面試者了。