Eslint statically checks javascript code for logic errors, as well as formatting errors. The idea is to parse the code into an AST and then check for problems based on the AST.
Tslint statically checks typescript code for logical errors, formatting errors. The principle is also based on AST.
Since they are all AST based and do similar things, why not merge them together?
Later, it did merge, and tsLint was merged into ESLint, marking tsLint deprecated.
But they are different asts, and tsLint has some type checking logic that ESLint does not support. So how do they merge?
In this article we will explore.
Different AST
Eslint has its own Parser for EsPree and a corresponding AST.
Typescript also has its own Parser and corresponding AST.
Babel also has its own Parser and corresponding AST.
What is the relationship between these AST’s?
The original Parser was Esprima, which referenced the AST standard of Mozilla’s SpiderMonkey engine and then expanded on it. The ESTREE standard was later formed.
Many subsequent parsers are implementations and extensions of this ESTree standard. Esprima, Espree, Babel Parser (Babylon), Acorn, etc.
There are, of course, non-standard parser implementations of typescript, Terser, etc.
Their relationship is shown below:
Esprima and Acorn are both implementations of the ESTree standard, and Acorn supports plug-in mechanisms to extend the syntax, so Espree and Babel Parser are directly based on Acorn.
Terser, typescript, and so on are another.
Therefore, for JS AST, we can simply divide it into two categories: ESTree series and non-ESTree series.
You can use astExplorer.net to visually view the AST generated by different Parsers.
Espree is esLint’s own parser, but it is mainly used for static checking of code logic and format, and is not as advanced as Babel Parser in implementing new syntax. So ESLint supports switching from parser to parser, meaning you can configure different parsers to parse code.
The configuration file can be configured with different parser options and parserOptions.
Here’s how esLint, typescript, Babel, Vue, etc. Parser is used in ESLint:
- The default parser is espree. Parse produces estree family AST and implements a series of rules based on these AST.
{
"parserOptions": {
"ecmaVersion": 6."sourceType": "module"."ecmaFeatures": {
"jsx": true}}}Copy the code
- It is possible to switch to the AST of Babel via @babel/ eslint-Parser. It is also the Estree family, but supports more syntax. After Babel7, it supports typescript, JSX, flow, and other syntax parsing.
{
parser: "@babel/eslint-parser".parserOptions: {
sourceType: "module".plugins: []}}Copy the code
- You can switch to typescript’s Parser via @typescript-esLint /parser, which parses type information.
{
"parser": "@typescript-eslint/parser"."parserOptions": {
"project": "./tsconfig.json"}}Copy the code
- Vue single-file components can be parsed using VUe-esLint-Parser, since vue component code also checks for specification and logic errors through ESLint, so the corresponding Parser is implemented.
{
"parser": "vue-eslint-parser"."parserOptions": {
"sourceType": "module"."ecmaVersion": 2018."ecmaFeatures": {
"globalReturn": false."impliedStrict": false."jsx": false}}}Copy the code
Also, the JS part of a single file component can specify different parser classes.
{
"parser": "vue-eslint-parser"."parserOptions": {
"parser": {
// Specify the default JS parser
"js": "espree".// Specify '
"ts": "@typescript-eslint/parser".// Specify parser for some scripts in the template
"<template>": "espree",}}}Copy the code
If it’s a bit confusing, typescript, Babel, Vue, and other Parser versions have versions for ESLint. It’s not unusual to think about it because Lint is AST based and if it can’t be parsed then lint needs to be extended to support switching.
But the AST after parser may be different, so will the implementation of Lint’s rule. In order to reuse rule, it is better for everyone to rely on the ESTREE standard.
The convergence of TSLint and ESLint follows the same idea, and let’s look at it in detail.
Tslint merges into ESLint
Tslint is a standalone tool that parses code based on typescript’s Parser and implements a set of rules based on that AST.
If you want to merge into ESLint, how?
The main concern is how the AST merges, because rule is based on the AST.
Const a = 1; This code right here,
Estree series AST looks like this:
Typescript AST looks like this:
Assts are different, so as-based rules must also have different implementations.
How do you merge them?
Transformation! Just convert one AST into another.
Yes, @typescript-esLint/Parser does exactly that, converting the AST of TS to the AST of ESTree (of course, for the parts of the type that estree doesn’t have, it keeps the AST but prefixes it with TS). This way, esLint’s rules can be used to check for problems in typescript code.
Let’s take a quick look at @typescript-esLint /parser:
I’m simplifying it. It looks like this:
function parseAndGenerateServices(code) {
Parse with ts parser
let {ast, program} = createIsolatedProgram(code);
// Convert to estree AST
ast = convertAst(ast);
return {
ast,
services: {
program,
esTreeNodeToTSNodeMap,
tsNodeToESTreeNodeMap
}
}
}
Copy the code
First, the source code was parsed into AST by TS Parser, and then converted into ESTree. The mapping between ESTree node and TS node was recorded and saved by two maps.
The transformation process is actually to traverse the AST of TS and create a new AST of ESTree.
For AST types that do not exist in estree, copy them directly and add TS before the AST name.
In this way, the AST produced by TS Parser is converted to estree.
Now that the AST is unified, ESLint’s rules can be used for Lint TS code.
But for some types of parts, we still need to use the TS API to check the AST.
Remember we saved two maps? Map from estree node to TS node and vice versa. So, when we need to use the AST of TS, we can map back to it:
Eslint’s custom Parser returns services as well as an AST, which is used to put other information, such as the map used here, There is also a TS program API (such as Program.getTypechecker). The ESTree AST can then be mapped back to the TS AST when needed.
The purpose of reusing ESLint rules is achieved by mapping the TS AST to the Estree AST and storing node mappings and apis for manipulating THE TS AST, which can be used to do separate TS related Lint. It came together perfectly.
This integration can be summed up as “seeking common ground while reserving differences” :
- Convergence: Convert AST to estREE family to reuse a set of rules for estree AST.
- Differences: Mappings are retained during the conversion process, along with some apis that can be mapped back if you need to do separate checks on ts types, etc.
conclusion
Js has different parser types, including estree series and non-ESTree series:
-
Estree series include Esprima, Acorn, espree, Babel Parser, etc.
-
Non-estree types include typescript, Terser, etc.
Parser switching is supported in ESLint. You can switch between Babel Parser, Vue Template Parser, typescript, and Espree, as well as extend other parsers.
Tslint is a standalone tool for doing parse with typescript. It and ESLint were both ast-based tools for checking for logic and formatting errors in code, and were later merged.
To reuse estree-based rules, @typescript-esLint/Parser converts TS nodes into Estree nodes, but retains mapping and some TS AST apis.
In this way, estree AST-based rules can run normally, and TS Ast-based rules can be mapped back to the original TS node and run.
In this way, ESLint and TSLint are perfectly fused together. It’s kind of neat.