Eslint 使用Espree 进行Javascript解析 Eslint 使用AST来评估代码中的模式 Eslint 每一条规则都是一个插件
一些解析器:
- esprima
- acorn
- @babel/parse (babylon) 基于acorn
- espree 最初从Esprima中fork出来,现在基于acorn
首先, 初始化工程,安装相应的包
pnpm add esprima estraverse escodegen
写一个小例子, 复习下之前编译原理文章里提到的ast相关知识
代码转换
需求:修改函数名称
const esprima = require("esprima");
const estraverse = require("estraverse");
const escodegen = require("escodegen");
let code = `function a(){}`;
// 将代码转成ast语法树
const ast = esprima.parseScript(code);
estraverse.traverse(ast, {
enter(node) {
console.log("enter", node.type);
if (node.type === "FunctionDeclaration") {
node.id.name = "ast";
}
},
leave(node) {
console.log("leave", node.type);
},
});
const output = escodegen.generate(ast);
console.log(output); // function ast() {}
Babel插件
需求:转换箭头函数
pnpm add @babel/core // babel的编译器,核心api都在里面,实现了插件功能
pnpm add @babel/types // 用于ast的lodash式工具库,验证以及转换AST节点的方法
pnpm add babel-plugin-transform-es2015-arrow-functions // 转换箭头函数
const babel = require("@babel/core");
const types = require("@babel/types");
const transformFunction = require("babel-plugin-transform-es2015-arrow-functions");
const code = `const sum = () => a+b`;
const result = babel.transform(code, {
plugins: [transformFunction],
});
console.log(result.code);
/**
const sum = function () {
return a + b;
};
*/
接下来,手写babel插件, transformFunction
const babel = require("@babel/core");
const types = require("@babel/types");
// const transformFunction = require("babel-plugin-transform-es2015-arrow-functions");
const transformFunction = {
visitor: {
ArrowFunctionExpression(path) {
let { node } = path;
node.type = "FunctionExpression";
hoistFunctionEvn(path);
let body = node.body;
if (!types.isBlockStatement(body)) {
node.body = types.blockStatement([types.returnStatement(body)]);
}
},
},
};
function getThisPath(path) {
let arr = [];
path.traverse({
ThisExpression(path) {
arr.push(path);
},
});
return arr;
}
function hoistFunctionEvn(path) {
// 查找父级作用域
const thisEnv = path.findParent(
(parent) =>
(parent.isFunction && !parent.isArrowFunctionExpression()) ||
parent.isProgram()
);
const bindingThis = "_this";
const thisPaths = getThisPath(path);
// 修改当前路径的this,改为_this
thisPaths.forEach((path) => {
// this -> _this
path.replaceWith(types.identifier(bindingThis));
});
thisEnv.scope.push({
id: types.identifier(bindingThis),
init: types.ThisExpression(),
});
}
const code = `const sum = () => a+b`;
const result = babel.transform(code, {
plugins: [transformFunction],
});
console.log(result.code);
编写自定义eslint插件
需求: 实现no-var, 面向api编程
sudo npm install yo generator-eslint -g
yo eslint:plugin
yo eslint:rule
/**
* @fileoverview kk
* @author my
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: "problem", // `problem`, `suggestion`, or `layout`
docs: {
description: "不能有var关键字",
recommended: false,
url: null, // URL to the documentation page for this rule
},
fixable: "code", // Or `code` or `whitespace`
schema: [], // Add a schema if the rule has options
messages: {
unexpected: "不能使用 {{ type }}",
}, // Add messageId and message
},
create(context) {
// variables should be defined here
const sourceCode = context.sourceCode;
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
// any helper functions should go here or else delete this section
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
// visitor functions for different types of nodes
VariableDeclaration(node) {
if (node.kind === "var") {
context.report({
node,
data: { type: "var" },
messageId: "unexpected",
fix(fixer) {
const varToken = sourceCode.getFirstToken(node, {
filter: (t) => t.value === "var",
});
return fixer.replaceText(varToken, "let");
},
});
}
},
};
},
};