Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

了解ESLint各个parser之间的关系 #96

Open
willson-wang opened this issue Jun 27, 2021 · 0 comments
Open

了解ESLint各个parser之间的关系 #96

willson-wang opened this issue Jun 27, 2021 · 0 comments

Comments

@willson-wang
Copy link
Owner

我们在项目中使用了处在提案1阶段的语法时,比如

export foo from 'foo' 
export foo, { bar } from "foo";

该语法还处于1阶段,具体查看proposal-export-default-from stage-1-proposals

当我们使用eslint检查的时候会报如下错误

Parsing error: Unexpected token foo eslint
export foo from './foo'

然后网上去找资料,安装@babel/eslint-parser包可以通过这个校验

yarn add @babel/core @babel/eslint-parser -D

{
  parser: "@babel/eslint-parser",
  parserOptions: { 
    sourceType: "module",
    allowImportExportEverywhere: false,
  },
  "plugins": ["@babel"]
};

安装完成之后再次执行eslint检查,可能会提示下面的错误

Parsing error: No Babel config file detected for /Users/wangks/Documents/f/practice/src/stage1/foo.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files

新建一个空的.babelrc文件

在重新eslint .

这时候可能提示如下错误

Parsing error: This experimental syntax requires enabling the parser plugin: 'exportDefaultFrom' (1:7)

然后我们安装对应的babel插件

yarn add @babel/plugin-proposal-export-default-from -D

在.babelrc内添加plugins

{
    "plugins": [
        "@babel/plugin-proposal-export-default-from"
    ]
}

在重新eslint .

校验通过了

虽然通过了验证,但是这里有会有一些疑问?

  • eslint的parser可以指定不同的解析器,解析器的作用是什么?
  • eslint为什么对于阶段1的语法解析不出来?
  • @babel/eslint-parser的作用是什么?

为什么parser可以指定不同的解析器,解析器的作用是什么?

首先我们看下eslint检查代码的原理:

  • ESLint uses Espree for JavaScript parsing.
  • ESLint uses an AST to evaluate patterns in code.

从eslint的lint原理来看,需要将代码转换成ast,而这个将code转换成ast,则需要一个解析器,而eslint选择的解析器则是Espree,而Espree最开始是从Esprima中拉出来的一个分支

eslint为什么对于阶段1的语法解析不出来?

原因:ESLint's parser only officially supports the latest final ECMAScript standard

Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the TC39 process), we will accept issues and pull requests related to the new feature, subject to our contributing guidelines.

也就是说eslint只支持已发布了的ECMAScript标准语法及进入到stage 4阶段的实验性语法

@babel/eslint-parser的作用是什么?

我们知道新语法从提案到发布是需要经历比较长的一段时间,而有些新语法是会带来不错的开发体验,所以我们会选择在项目中使用这些新语法,而我们一般在项目中都会借助babel来将我们的代码从es6+转化成es5的代码,及选择babel插件来支持实验性的语法;

从eslint知道,对于JS的实验性(例如新功能)和非标准(例如 Flow 或 TypeScript 类型)语法是不支持检测的,那么我们在实际项目开发的时候想要对这些法语进行支持,则通过@babel/eslint-parser解析器即可,@babel/eslint-parser 是一个解析器,它允许 ESLint 在 Babel 转换的源代码上运行

@babel/eslint-parser将代码生成ast的时候,会被转换成 ESLint 可以理解的 ESTree兼容结构;这样我们就可以在标准的语法上,针对实验性或者非标准的语法做lint检测

@babel/eslint-parser的使用

yarn add eslint @babel/core @babel/eslint-parser @babel/eslint-plugin -D

// 需要注意当使用@babel/eslint-parser时,parserOptions部分属性可能不生效,因为parserOptions的属性是针对默认的parser设置的
module.exports = {
  parser: "@babel/eslint-parser",
  parserOptions: { 
    sourceType: "module",
    allowImportExportEverywhere: false,
    ecmaFeatures: {
      globalReturn: false,
    },
    babelOptions: {
      configFile: "path/to/config.js",
    },
  },
  "plugins": ["@babel"]
};

也可以只针对部分js文件使用@babel/eslint-parser解析

module.exports = {
  rules: {
    indent: "error",
  },
  overrides: [
    {
      files: ["files/transformed/by/babel/*.js"],
      parser: "@babel/eslint-parser",
      "plugins": ["@babel"]
    },
  ],
};

@babel/eslint-parser 将code转换成ESLint可以理解的ESTree兼容结构,但它无法更改内置规则以支持实验性语法。
@babel/eslint-plugin 从新定义实验性语法的lint规则

到这里我们已经知道,针对js的lint,如果我们在项目开发的时候只选择标准语法,不使用实验性的语法或者非标准性的语法,我们使用默认的eslint解析器即可,反之则选择babel/eslint-parser

我们现在已经知道js的lint该怎么选了,那么ts的lint要怎么做呢?

首先最开始我们的ts项目都是通过tslint来做ts代码的lint检测,eslint是做不到ts代码的代码检测的

eslint为什么做不到ts代码的检测呢?

我们先看下TypeScript是什么,TypeScript是javascript的超集,在javascript基础上加了静态类型定义与分析;而eslint是没有去支持这些功能的

tslint又是什么呢?

tslint是一个专门为基于上述 TypeScript AST 格式而编写的 linter。

专门的tslint有优点也有缺点;

  • 优点:是不需要任何工具来协调AST格式之间的差异,
  • 缺点:是该工具因此无法重用之前在JavaScript生态系统中围绕linting所做的任何工作,它必须重新实现所有内容从头开始。从规则到自动修复功能等等。

最后经过tslint的核心开发者2019年决定放弃tslint,转而支持 typescript-eslint;具体文章可参考TSLint in 2019;后续TypeScript 团队自己也宣布了将 TypeScript 代码库从 TSLint 迁移到 typescript-eslint 的计划Migrate the repo to ESLint

而typescript-eslint又是什么呢?

ESLint 和 TypeScript 都依赖于将源代码转换为AST的数据格式来完成它们的工作。实际情况是ESLint和TypeScript彼此使用不同的AST;

typescript-eslint 的存在就是让我们可以一起使用 ESLint和TypeScript,而无需担心任何可能的实现细节差异。

typescript-eslint的使用方式

yarn add -D eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  plugins: [
    '@typescript-eslint',
  ],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
  ],
};

@typescript-eslint/parser 将typescript转换成eslint能过理解的estree

到这里我们知道了针对ts文件的lint检测需要通过@typescript-eslint/parser来做?那么问题来了?

1、@typescript-eslint/parser支持实验性的语法或者非标准性的语法吗?

要看@typescript-eslint/parser是否支持实验性的语法或者非标准性的语法,我们需要先看下typescript是否支持?

没有在typescript的文档内看到有对实验性的语法或者非标准性的语法的说明,只在相关issues内看到有说到?

We have no plans to implement any proposals unless they hit stage 3 and we have high confidence in them.
TypeScript Roadmap: January - June 2019
Nightly-only experimental features
Suggestion: Experimental Support for ECMAScript Stage 1/2 Proposals

所以typescript是基本不支持实验性的语法或者非标准性的语法

那么@typescript-eslint/parser也就不可能去支持实验性的语法或者非标准性的语法

2、@babel/eslint-parser与@typescript-eslint/parser之间有什么异同?

相同点:都是用于eslint代码检测的一种解析器

不同点:一个用于支持实验性的js语法,一个支持ts语法

概括起来就是@babel/eslint-parser支持TypeScript本身不支持的额外语法,但是不支持ts的类型检查,而typescript-eslint支持ts的类型检查

最终由于它们是由不同底层工具驱动的独立项目,因此目前两个项目的维护者,不打算将它们结合起来。

具体原因参考What about Babel and @babel/eslint-parser

总结

  1. eslint常用的解析器有:babel/eslint-parser、@typescript-eslint/parser、espree、esprima
  2. 对js项目,需要支持实验性语法的lint检测,可以使用@babel/eslint-parser,反之则使用默认解析器
  3. 对于ts项目,使用@typescript-eslint/parser,不推荐使用实验性阶段的语法,除非是typescript本身支持的实验性语法

推荐配置

js项目不支持实验性语法配置

yarn add eslint eslint-config-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node -D
{
  parserOptions: { 
    "sourceType": "module",
    "ecmaVersion": 2021,
  },
  "extends": [
    "standard"  
  ],
};

js项目支持实验性语法配置

yarn add eslint @babel/core @babel/eslint-parser -D

{
  parser: "@babel/eslint-parser",
  parserOptions: { 
    sourceType: "module",
    allowImportExportEverywhere: false,
  },
  "plugins": ["@babel"]
};

ts项目配置

yarn add -D eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  plugins: [
    '@typescript-eslint',
  ],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
  ],
};

补充eslint中extends、plugins、rules之间的区别

先看rules,rules的具体作用,就是实际的eslint检查规则,一条规则对应一种语法检查

module.exports = {
  rules: {
    'quotes': 'single',
    'space-before-blocks': 'always',
  }
};

当我们把项目内的eslint规则都定义好,最后如果我们想要在其它项目内仍然复用当前的eslint配置,最直接的方法,当然是拷贝一份过去,但是这样太麻烦了,也不利于后面的规则变更,所以有了extends的用武之地

extends的作用就是封装一份常用的eslint配置并当成npm包来使用,需要用到的项目安装对应的npm包,并在extends内引入即可

module.exports = {
  "extends": [
    "standard"  
  ],
  rules: {
    'quotes': 'single',
    'space-before-blocks': 'always',
  }
};

允许 extends 多个模块,如果规则冲突,位置靠后的包将覆盖前面的。rules 中的规则相同,并且优先级恒定高于 extends

最后我们知道一条规则对应一种检查,那么eslint不可能提供所有的规则来覆盖我们的语法,这时候eslint提供了plugin,允许自定义plugin定义语法检查规则

module.exports = {
  "extends": [
    "standard"  
  ],
  "plugins": [
    "import",
    "node",
    "promise"
  ],
  rules: {
    'quotes': 'single',
    'space-before-blocks': 'always',
  }
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant