精华内容
下载资源
问答
  • 主要介绍了详解create-react-app 自定义 eslint 配置,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • eslint-import-resolver-babel-module, babel插件模块解析器的自定义eslint解析 eslint-import-resolver-babel-module 用于eslint-plugin-import的babel-plugin-module-resolver 解析程序。安装npm install -
  • 方案二 在package.json 中的 script 命令 添加环境变量EXTEND_ESLINT=treu 开启自定义eslint 我们可以打开react-script 源码看到 webpack.config.js 文件 当环境变量设定为true时候,react-script会认为我们...

    create-react-app项目自定义eslitn配置方式

    方案一 eject 项目webpack配置进行自定义

    这个方案比较low,不建议使用。这里不讲解了。

    方案二  在 package.json 中的 script 命令 添加环境变量 EXTEND_ESLINT=treu 开启自定义

    react-script4.x版本以下可用这个方案

    我们可以打开react-script 源码看到 webpack.config.js 文件

    当环境变量设定为true时候,react-script会认为我们自定义eslint配置,不启用 eslint-config-react-app 的配置。

    但是开启这个变量我们只能在package.json中的 eslintConfig 字段进行配置自定义,无法通过根目录 .eslintrc 配置,所以不建议使用。我们使用第三方案

    方案三 react-app-rewired 和 customize-cra

    react-app-rewired 和 customize-cra 是用于对react-scripts手脚架包装一次进行使用,可不对react eject 就可以对项目自定义webpack,仅限于react-scriptr4.x以下。

    1.先安装依赖 

    npm i react-app-rewired customize-cra -D

    2.在项目跟目录创建 config-overrides.js 内容如下

    const { override, addWebpackAlias, useEslintRc } = require('customize-cra')
    const path = require('path')
    
    module.exports = override(
      // 注意,一定要用 path.resolve 引入eslint的配置文件,否则不生效
      useEslintRc(path.resolve(__dirname, './.eslintrc.json')),
      // 配置路径别名
      addWebpackAlias({
        '@': path.resolve(__dirname, './src'),
        '_c': path.resolve(__dirname, './src/components')
      })
    )
    

    3.修改 package.json 的命令 

    "start": "cross-env REACT_APP_API=development PORT=3005 react-app-rewired start",

    4.然后在根目录创建 .eslintrc.json 进行自定义eslint配置即可。这里分享一下我们团队eslint配置,具体更多配置,大家可自己到eslint官网进行参考, 0 表示关闭,1 表示警告,2 表示严重错误

    {
        "env": {
            "node": true,
            "mocha": true,
            "jest": true,
            "es6": true,
            "browser": true
        },
        "extends": [
            "eslint:recommended",
            "plugin:react/recommended"
        ],
        "parser": "babel-eslint",
        "parserOptions": {
            "ecmaFeatures": {
                "jsx": true
            },
            "ecmaVersion": 6,
            "sourceType": "module"
        },
        "plugins": [
            "react",
            "jsx-a11y",
            "react-hooks"
        ],
        "settings": {
            "react": {
                "version": "detect"
            }
        },
        "globals": {
            "JSX": true,
            "React": true,
            "NodeJS": true,
            "Promise": true
        },
        "rules": {
            "no-console": [1, { "allow": ["error"] }],
            "consistent-return": 2,
            "curly": [2, "multi-or-nest"],
            "dot-location": 0,
            "eqeqeq": 2,
            "no-alert": 2,
            "no-eq-null": 2,
            "no-lone-blocks": 2,
            "no-return-await": 2,
            "no-unused-expressions": 2,
            "no-label-var": 1,
            "array-bracket-spacing": 2,
            "brace-style": 0,
            "comma-spacing": 1,
            "consistent-this": 1,
            "eol-last": 0,
            "multiline-ternary": [1, "always-multiline"],
            "new-cap": [2, { "capIsNew": false }],
            "no-trailing-spaces": 0,
            "semi": ["error", "never"],
            "space-before-blocks": 2,
            "space-in-parens": 2,
            "spaced-comment": 2,
            "switch-colon-spacing": ["error", { "after": true, "before": false }],
            "arrow-spacing": 2,
            "quotes": [0, "single"],
            "key-spacing": 2,
            "comma-dangle": ["error", "never"],
            "react-hooks/exhaustive-deps": 0,
            "no-empty-function": 1,
            "react-native/no-inline-styles": 0,
            "react/forbid-prop-types": 0,
            "react/prop-types": 0,
            "react/display-name": 0,
            "prefer-promise-reject-errors": 0,
            "react/no-array-index-key": 2,
            "react/no-unused-state": 2,
            "react/jsx-indent-props": 1,
            "react/jsx-no-comment-textnodes": 1,
            "react/jsx-no-duplicate-props": 2,
            "react/jsx-no-target-blank": [1, { "enforceDynamicLinks": "always" }],
            "react/jsx-no-undef": 2,
            "react/jsx-props-no-multi-spaces": 1,
            "react/jsx-tag-spacing": 1,
            "react/jsx-uses-vars": 2,
            "react/jsx-wrap-multilines": 2,
            "react-hooks/rules-of-hooks": 2
        }
    }
    

    方案4 react-script 4.x 方案

    react17 官方团队修改了脚手架允许直接在外部声明.eslintrc文件覆盖eslint配置。不需要对package和react-app-rewired 和 customize-cra就可用实现eslint 配置。

    在根目录创建文件 .eslintrc.json,配置的extends 字段需要改一下

    {
        "env": {
            "node": true,
            "mocha": true,
            "jest": true,
            "es6": true,
            "browser": true
        },
        "extends": [
            "eslint:recommended",
            "plugin:react/recommended",
            "plugin:react-hooks/recommended"
        ],
        "parser": "babel-eslint",
        "parserOptions": {
            "ecmaFeatures": {
                "jsx": true
            },
            "ecmaVersion": 6,
            "sourceType": "module"
        },
        "plugins": [
            "react",
            "jsx-a11y",
            "react-hooks"
        ],
        "settings": {
            "react": {
                "version": "detect"
            }
        },
        "globals": {
            "JSX": true,
            "React": true,
            "NodeJS": true,
            "Promise": true
        },
        "rules": {
            "no-console": [1, { "allow": ["error"] }],
            "consistent-return": 2,
            "curly": [2, "multi-or-nest"],
            "dot-location": 0,
            "eqeqeq": 2,
            "no-alert": 2,
            "no-eq-null": 2,
            "no-lone-blocks": 2,
            "no-return-await": 2,
            "no-unused-expressions": 2,
            "no-label-var": 1,
            "array-bracket-spacing": 2,
            "brace-style": 0,
            "comma-spacing": 1,
            "consistent-this": 1,
            "eol-last": 0,
            "multiline-ternary": [1, "always-multiline"],
            "new-cap": [2, { "capIsNew": false }],
            "no-trailing-spaces": 0,
            "semi": ["error", "never"],
            "space-before-blocks": 2,
            "space-in-parens": 2,
            "spaced-comment": 2,
            "switch-colon-spacing": ["error", { "after": true, "before": false }],
            "arrow-spacing": 2,
            "quotes": [0, "single"],
            "key-spacing": 2,
            "comma-dangle": ["error", "never"],
            "react-hooks/exhaustive-deps": 0,
            "no-empty-function": 1,
            "react-native/no-inline-styles": 0,
            "react/forbid-prop-types": 0,
            "react/prop-types": 0,
            "react/display-name": 0,
            "prefer-promise-reject-errors": 0,
            "react/no-array-index-key": 2,
            "react/no-unused-state": 2,
            "react/jsx-indent-props": 2,
            "react/jsx-no-comment-textnodes": 1,
            "react/jsx-no-duplicate-props": 2,
            "react/jsx-no-target-blank": [1, { "enforceDynamicLinks": "always" }],
            "react/jsx-no-undef": 2,
            "react/jsx-props-no-multi-spaces": 1,
            "react/jsx-tag-spacing": 1,
            "react/jsx-uses-vars": 2,
            "react/jsx-wrap-multilines": 2,
            "react-hooks/rules-of-hooks": 2
        }
    }
    

     

    展开全文
  • ????这是第60篇不掺水的原创,想要了解更多,请戳上方蓝色字体:政采云前端团队关注我们吧~本文首发于政采云前端团队博客:自定义 ESLint 规则,让代码持续美丽https:...

    ????  这是第 60 篇不掺水的原创,想要了解更多,请戳上方蓝色字体:政采云前端团队 关注我们吧~

    本文首发于政采云前端团队博客:自定义 ESLint 规则,让代码持续美丽

    https://www.zoo.team/article/eslint-rules

    背景

    一段真实的代码发展历史

    很久很久以前,有一个需求,然后产出了一段代码,代码优雅而简洁

    export const getConfig = (param1, param2) => {
      return ...
    };
    

    不久又来了个需求,加个参数扩展,so easy!

    export const getConfig = (param1, param2, param3) => {
      return ...
    };
    

    经过多次产品需求迭代后,现在的代码

    export const getConfig = (param1, param2, param3, param4, param5, param6, param7……) => {
      return ...
    };
    

    在产品迭代过程中,上面的 case 一个函数的参数从 2 个发展到了 7 个,优雅的代码逐渐变为不可维护。这是什么问题?这归咎于日益增长的需求,快速响应和代码质量之间的矛盾。

    那如何避免呢?

    • 制定代码规范

    • 靠开发同学的自我修养

    • 进行 Code Review

    • 工具提示

    • 发版控制,不允许发版

    制定代码规范肯定是需要的,那如何约束代码呢?规范文档宣讲,再凭借开发同学的自我修养?答案是:无法保证。

    Code Review ?但难免也有落网之鱼。发版控制?能有效解决但是开发体验不好。

    如果我们在开发者写代码的时候就及时给到提示和建议,那开发体验就很棒了,而 ESLint 的自定义规则就可以实现在开发过程中给开发同学友好的提示。

    ESLint 原理

    ESLint 是一个代码检查工具,通过静态的分析,寻找有问题的模式或者代码。默认使用 Espree (https://github.com/eslint/espree) 解析器将代码解析为 AST 抽象语法树,然后再对代码进行检查。

    看下最简单的一段代码使用 espree 解析器转换成的抽象语法树结构,此处可以使用 astexplorer (https://astexplorer.net/) 快速方便查看解析成 AST 的结构:

    代码片段:

    var a = 1;
    

    转换出的结果:

    {
      "type": "Program",
      "start": 0,
      "end": 10,
      "range": [
        0,
        10
      ],
      "body": [
        {
          "type": "VariableDeclaration",
          "start": 0,
          "end": 10,
          "range": [
            0,
            10
          ],
          "declarations": [
            {
              "type": "VariableDeclarator",
              "start": 4,
              "end": 9,
              "range": [
                4,
                9
              ],
              "id": {
                "type": "Identifier",
                "start": 4,
                "end": 5,
                "range": [
                  4,
                  5
                ],
                "name": "a"
              },
              "init": {
                "type": "Literal",
                "start": 8,
                "end": 9,
                "range": [
                  8,
                  9
                ],
                "value": 1,
                "raw": "1"
              }
            }
          ],
          "kind": "var"
        }
      ],
      "sourceType": "module"
    }
    

    代码转换为 AST 后,可以很方便的对代码的每个节点对代码进行检查。

    自定义 ESLint 规则开发

    怎么自定义

    语法树分析

    对目标代码进行语法树解析,可使用 astexplorer (https://astexplorer.net/)

    编写规则

    下面是一个规则简单的结构(官方 API 文档说明:https://eslint.org/docs/developer-guide/working-with-rules#rule-basics)

    module.exports = {
      meta: {
        docs: {
          description: "最多参数允许参数",
        },
      },
      create: function (context) {
        return {
          FunctionDeclaration: (node) => {
            if (node.params.length > 3) {
              context.report({
                node,
                message: "参数最多不能超过3个",
              });
            }
          },
        };
      },
    };
    
    • meta(对象)包含规则的元数据

    • create ( function ) 返回一个对象,其中包含了 ESLint 在遍历 JavaScript 代码的抽象语法树 AST ( ESTree 定义的 AST ) 时,用来访问节点的方法

    • context.report ( )  用来发布警告或错误,并能提供自动修复功能(取决于你所使用的配置)

    最简单的示例(只使用 node 和 message 参数):

    context.report({
        node,
        message: "参数最多不能超过3个",
    });
    

    使用上面的这个规则,结合编辑器就有了对整个 node 节点的提示,如果需要更精确的错误或警告提示,我们可以使用 loc 参数,API 文档说明 (https://eslint.org/docs/developer-guide/working-with-rules#context-report)。

    image

    如何使用自定义规则

    使用自定义的 ESLint 规则,你需要自定义一个 ESLint 的插件,然后将规则写到自定义的 ESLint 插件中,然后在业务代码中添加 ESLint 配置,引入 ESLint 插件。

    ESLint 插件

    创建

    创建一个 ESLint plugin,并创建 一个 ESLint rule

    基于 Yeoman generator (https://yeoman.io/authoring/) ,可以快速创建 ESLint plugin 项目。

    npm i -g yo
    npm i -g generator-eslint
    // 创建一个plugin
    yo eslint:plugin
    // 创建一个规则
    yo eslint:rule
    

    创建好的项目目录结构:

    • rules 文件夹存放的是各个规则文件

    • tests 文件夹存放单元测试文件

    • package.json 是你的 ESLint 插件 npm 包的说明文件,其中的 name 属性就是你的 ESLint  插件的名称,命名规则:带前缀 eslint-plugin-

    示例代码:

    lib/rules/max-params.js

    module.exports = {
      meta: {
        docs: {
          description: "最多参数",
        },
      },
      create: function (context) {
        /**
         * 获取函数的参数的开始、结束位置
         * @param {node} node AST Node 
         */
        function getFunctionParamsLoc(node) {
          const paramsLength = node.params.length;
          return {
            start: node.params[0].loc.start,
            end: node.params[paramsLength - 1].loc.end,
          };
        }
        return {
          FunctionDeclaration: (node) => {
            if (node.params.length > 3) {
              context.report({
                loc: getFunctionParamsLoc(node),
                node,
                message: "参数最多不能超过3个",
              });
            }
          },
        };
      },
    };
    

    补充测试用例

    /tests/lib/rules/max-params.js

    var ruleTester = new RuleTester();
    ruleTester.run("max-params", rule, {
      valid: ["function test(d, e, f) {}"],
      invalid: [
        {
            code: "function test(a, b, c, d) {}",
            errors: [{
                message: "参数最多不能超过3个",
            }]
        },
      ],
    });
    

    ESLint 插件安装

    在需要的业务代码中安装你的 ESLint 插件。(eslint-plugin-my-eslist-plugin 是你的 ESLint 插件 npm 包的包名)

    npm install eslint-plugin-my-eslist-plugin 
    

    如果你的 npm 包还未发布,需要进行本地调试:

    可使用 npm link 本地调试,npm link 的使用 (https://www.baidu.com/s?ie=UTF-8&wd=npm%20link)。

    配置

    添加你的 plugin 包名(eslint-plugin- 前缀可忽略) 到 .eslintrc 配置文件的 plugins 字段。

    .eslintrc 配置文件示例:

    {
        "plugins": [
            "zoo" // 你的 ESLint plugin 的名字
        ]
    }
    

    rules 中再将 plugin 中的规则导入。⚠️ ESlint更新后,需要重启 vsCode,才能生效。( vsCode  重启快捷方式:CTRL +SHITF + P,输入 Reload Window

    此处涉及 ESLint 的规则设置(参考说明:https://eslint.org/docs/user-guide/configuring#configuring-rules)

    {
        "rules": {
            "zoo/rule-name": 2
        }
    }
    

    效果

    image

    实际应用案例

    函数、方法的入参个数控制,其实已经在 ESLint 的规则中了。在业务场景中,我们需要对我们的业务规则编写自定义的 ESLint 规则。

    一个简单的业务场景:业务中通常会出现跳转到很多不同的业务域名的操作,不同的环境有不同的域名,我们需要从配置中取出域名使用,而不是采取硬编码域名的方案。

    由此我们产生出了一个规则:禁止硬编码业务域名。

    规则为:

    module.exports = {
      meta: {
        type: "suggestion",
        docs: {
          description: "不允许硬编码业务域名",
        },
        fixable: "code",
      },
    
      create: function (context) {
        const sourceCode = context.getSourceCode();
    
        function checkDomain(node) {
          // 匹配硬编码的业务域名的正则
          const Reg = /^(http:\/\/|https:\/\/|\/\/)(.*.){0,1}zcygov(.com|cn)(.*)/;
          const content =
            (node.type === "Literal" && node.value) ||
            (node.type === "TemplateLiteral" && node.quasis[0].value.cooked);
    
          const domainNode =
            (node.type === "Literal" && node) ||
            (node.type === "TemplateLiteral" && node.quasis[0]);
    
          if (Reg.test(content)) {
            context.report({
              node,
              // 错误/警告提示信息
              message: "不允许硬编码业务域名",
              // 修复
              fix(fixer) {
                
                const fixes = [];
                
                let domainKey = content.match(Reg)[2];
                domainKey = domainKey
                  ? domainKey.substr(0, domainKey.length - 1)
                  : "";
    
                if (node.type === "Literal") {
                  fixes.push(
                    fixer.replaceTextRange(
                      [domainNode.start + 1, domainNode.end - 1],
                      content.replace(Reg, `$4`)
                    )
                  );
                }
    
                if (node.type === "TemplateLiteral") {
                  fixes.push(
                    fixer.replaceTextRange(
                      [domainNode.start, domainNode.end],
                      content.replace(Reg, `$4`)
                    )
                  );
                }
                 
                if (
                  node.type === "Literal" &&
                  node.parent.type === "JSXAttribute"
                ) {
                  fixes.push(fixer.insertTextBefore(node, "{"));
                  fixes.push(fixer.insertTextAfter(node, "}"));
                }
    
                fixes.push(
                  fixer.insertTextBefore(
                    node,
                    `window.getDomain('${domainKey}') + `
                  )
                );
    
                return fixes;
              },
            });
          }
        }
        return {
          // 文本
          Literal: checkDomain,
          // 模板字符串
          TemplateLiteral: checkDomain,
        };
      },
    };
    

    补充测试用例

    /tests/lib/rules/no-zcy-domain.js

    var rule = require("../../../lib/rules/no-zcy-domain"),
        RuleTester = require("eslint").RuleTester;
    
    var ruleTester = new RuleTester();
    ruleTester.run("no-zcy-domain", rule, {
      valid: [
        "bar",
        "baz",
        `
      var s = {
        x: "zcygov"
      };
      `,
      ],
      invalid: [
        {
          code: `
                  var s = "//zcygov.cn"
                `,
          errors: [
            {
              message: "不允许硬编码业务域名",
            },
          ],
        },
        {
          code: `
                var s = {
                  x: "http://bidding.zcygov.cn"
                };
                `,
          errors: [
            {
              message: "不允许硬编码业务域名",
            },
          ],
        },
      ],
    });
    

    结合 vsCode 保存自动修复 ESLint 错误的功能,效果如下:

    更多的应用场景

    除了上面说的硬编码的场景,还可以将沉淀出的最佳实践和业务规范通过自定义 ESLint 的方式来提示开发者,这对于多人协助、代码维护、代码风格的一致性都会有很大的帮助。

    更多的应用场景有:

    • Input 必须要有 maxlength 属性,防止请求的后端接口数据库异常

    • 代码中不能出现加减乘除等计算,如果需要计算应该引入工具函数,来控制由于前端浮点数计算引起的 Bug

    • 规范限制,单位元的两边的括号要用英文括号,不能用中文括号,来达到交互展示统一的效果

    • 代码中不能使用 OSS 地址的静态资源路径,应该使用 CDN 地址的资源路径

    • ...

    参考文献

    • https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/SpiderMonkey/Parser_API

    • https://eslint.org/docs/developer-guide/working-with-rules

    推荐阅读

    1、你不知道的前端异常处理(万字长文,建议收藏)

    2、你不知道的 TypeScript 泛型(万字长文,建议收藏)

    3、你不知道的 Web Workers (万字长文,建议收藏)

    4、lucifer与它的《力扣加加》来啦

    5、或许是一本可以彻底改变你刷 LeetCode 效率的题解书

    6、想去力扣当前端,TypeScript 需要掌握到什么程度?

    7、如果张东升是个程序员

    关注加加,星标加加~

    如果觉得文章不错,帮忙点个在看呗

    展开全文
  • 自定义 eslint 规则校验代码业务逻辑 eslint 是 JavaScript 社区中主流的 lint 工具,提供的大量规则有效的保障了许多项目的代码质量。本文将介绍如何通过自定义 eslint 检查规则,校验项目中特有的一些业务逻辑,如...
        

    自定义 eslint 规则校验代码业务逻辑

    eslint 是 JavaScript 社区中主流的 lint 工具,提供的大量规则有效的保障了许多项目的代码质量。本文将介绍如何通过自定义 eslint 检查规则,校验项目中特有的一些业务逻辑,如 i18n、特殊作用域、特殊 API 使用规范性等。

    代码静态分析与 eslint

    代码静态分意指是不需要实际执行代码就能获取到程序中的部分信息并加以使用,lint 就是其中一种常见的实践,通常为检查代码中错误的写法或是不符合标准的代码风格。许多编程语言都自带 lint 工具,甚至直接将其植入到编译器中。

    但这一重要的功能对于 JavaScript 来说却是一大痛点,作为动态且弱类型的语言 JavaScript 没有编译阶段也就无从进行静态分析,这导致程序错误只能在运行时被发现,部分错误非常低级例如variable is undefined。而当程序变得更为复杂时,这类错误甚至难以在开发、测试阶段暴露,只会在用户实际使用的过程中遇到,造成严重的后果。

    为了弥补语言天生的弱点,社区开发出了一些 lint 工具,在所谓预编译阶段完成代码的静态分析检查,而 eslint 就是其中的佼佼者。现在社区已经普遍接受使用 eslint 作为代码规范工具,也延伸出了许多常用的规则与规则集。但实际上 eslint 拓展性极佳,我们还可以基于 eslint 提功的静态分析能力对代码进行业务逻辑的检查,本文将讲解一些笔者所在项目中的静态分析实践,以说明这一方案的适用场景和优缺点。

    eslint 基本原理

    首先快速说明 eslint 工作的基本流程,帮助理解它将给我们提供哪些方面的能力以及如何编写我们的自定义规则。

    配置规则与插件

    eslint 主要依靠配置决定执行哪些规则的校验,例如我们可以通过配置no-extra-semi决定是否需要写分号,这类规则中不包含具体的业务逻辑,而是对所有项目通用,因此会被集成在 eslint 的内置规则中。

    而还有一些规则也不包含业务逻辑,但只在部分项目场景中使用,如 React 相关的大量规则,那么显然不应该集成在内置规则中,但也应该自成一个集合。这种情况下 eslint 提供了另一种规则单位——插件,可以作为多个同类规则的集合被引入到配置中。

    如果我们准备自定义一些规则用于校验项目中的业务逻辑,那么也应该创建一套自用的插件,并将自用的规则都存放其中。推荐使用 eslint 的 yeoman generator 脚手架新建插件或规则,该脚手架能够生成插件项目的目录结构、规则文件、文档以及单元测试等模版,下文中我们将通过示例理解这些文件的的作用。

    JavaScript 解析

    如上文所说,要实现静态分析则需要自建一个预编译阶段对代码进行解析,eslint 也不例外。

    首先我们看看大部分编译器工作时的三个阶段:

    1. 解析,将未经处理的代码解析成更为抽象的表达式,通常为抽象语法树,即 AST。
    2. 转换,通过修改解析后的代码表达式,将其转换为符合预期的新格式。
    3. 代码生成,将转换后的表达式生成为新的目标代码。

    如果想快速的加深对编译器工作原理的理解,推荐阅读 the-super-tiny-compiler

    对于 eslint 而言,主要是将 JavaScript 代码解析为 AST 之后,再在遍历 AST 的过程中对代码进行各个规则的校验。因此 eslint 也有一个解析器用于将原始代码解析为特定的 AST,目前所使用的解析器是 eslint 基于 Acorn 开发的一个名为 Espree 的项目。而对于我们编写自定义规则来说更关心的是解析器生成的 AST 节点的结构,在阅读 eslint 文档之后会了解到包括 Espree 在内的许多编译器项目都需要一套 JavaScript 的 AST 规范,而为了保证规范的一致性以及实效性,社区共同维护了一套规范:estree

    在接下来讲解规则编写与执行的过程中,我们将直接引用 estree 的各种 AST 结构。

    规则的执行

    eslint 中一般一个规则存放在一个文件中,以 module 的形式导出并挂载,其结构如下:

    module.exports = {
      meta: {
        docs: {
          description: 'disallow unnecessary semicolons',
          category: 'Possible Errors',
          recommended: true,
          url: 'https://eslint.org/docs/rules/no-extra-semi',
        },
        fixable: 'code',
        schema: [], // no options
      },
      create: function(context) {
        return {
          // callback functions
        };
      },
    };

    其中meta部分主要包括规则的描述、类别、文档地址、修复方式以及配置下 schema 等信息,对于项目中自用的规则来说可以只填写基本的描述和类别,其余选项在有需要时再根据文档补充,并不会影响规则的检验逻辑。

    create则需要定义一个函数用于返回一个包含了遍历规则的对象,并且该函数会接收context对象作为参数,context对象中除了包含report等报告错误的方法之外,还提供了许多帮助方法,可以简化规则的编写。下文中我们会通过几个示例理解create函数的使用方式,但首先可以通过一段代码建立初步的印象:

    module.exports = {
      create: function(context) {
        // declare the state of the rule
        return {
          ReturnStatement: function(node) {},
          'FunctionExpression:exit': function(node) {},
          'ArrowFunctionExpression:exit': function(node) {},
        };
      },
    };

    在这段代码中我们可以看到create返回的所谓“包含了遍历规则的对象”的基本结构。对象的 value 均为一个接收当前 AST 节点的函数,而 key 则是 eslint 的节点 selector。selector 分为两部分,第一部分为必须声明的 AST 节点类型,如ReturnStatementFunctionExpression。第二部分则是可选的:exit标示,因为在遍历 AST 的过程中会以“从上至下”再“从下至上”的顺序经过节点两次,selector 默认会在下行的过程中执行对应的访问函数,如果需要再上行的过程中执行,则需要添加:exit

    那么 eslint 解析出的 AST 有哪些节点类型,每种节点的数据结构又是什么,则需要通过查看上文提到的 estree 定义文档进行了解。

    适用场景与示例

    接下来我们会看到 eslint 自定义规则校验的一些具体示例,但首先我们先要明确它的适用场景以及与一些常见代码 QA 手段的异同。

    适用场景

    我们可以通过以下方法判断一个工具的质量:

    工具质量 = 工具节省的时间 / 开发工具消耗的时间

    对于静态分析来说,要想提高“工具节省的时间”,应该要让检查的规则尽量覆盖全局性的且经常发生的问题,如使用最为广泛的检查:是否使用了未定义的变量。同时还需要考虑当问题发生后 debug 所消耗的时间,例如有的项目有 i18n 需求,而在代码的个别地方又直接使用了中文的字符串,虽然问题很小,但是人工测试覆盖却很麻烦,如果能够通过工具进行覆盖,那么原来用于 debug 的时间也应该归入“工具节省的时间”当中。

    另一方面则是对比“开发工具消耗的时间”,首先要强调通过静态分析去对逻辑进行判断,不论是学习成本还是实际编写成本都较高,如果一类问题可以通过编写简单的单元测试进行覆盖,那么应该优先考虑使用单元测试。但有的时候代码逻辑对外部依赖较多,单元测试的开销很大,例如我们有一段 e2e 测试的代码,需要在目标浏览器环境中执行一段代码,但是常规的 eslint 并不能判断某个函数中的代码实际执行在另一个作用域下,部分检查就会失效,例如浏览器运行时引用的变量实际定义在本地运行时中,eslint 无法察觉。而如果通过单元测试覆盖,则需要实际运行对应的 e2e 代码,或者 mock 其执行环境的各种依赖,都是非常重的工作,取舍之下通过静态分析覆盖会事半功倍。

    最后还需要考虑到使用体验,许多编辑器都有 eslint 的集成插件,可以在编程的过程中实时检测各个规则,在实时性方面远强于单元测试等 QA 手段的使用体验。

    示例 1:i18n

    许多项目都有国际化的需求,因此项目中的文案需要避免直接使用中文,常见的方案包括用变量代替字符串或者使用全局的翻译函数处理字符串,例如:

    // 错误:直接只用中文字符串
    console.log('中文');
    // 使用变量
    const currentLocale = 'cn';
    const T = {
      str_1: {
        cn: '中文',
      },
    };
    console.log(T.str_1[currentLocale]);
    // 使用翻译函数处理
    console.log(t('中文'));

    如果出现了直接使用中文字符串的错误,其实在代码运行过程中也不会有任何错误提示,只能靠 code review 和人工观察测试来发现。我们尝试自定义一条 eslint 规则解决它,此处假设项目中使用的是将所有中文内容存放在一个变量中,其余地方直接引用变量的方法。

    const SYMBOL_REGEX = /[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]/;
    const WORD_REGEX = /[\u3400-\u9FBF]/;
    
    function hasChinese(value) {
      return WORD_REGEX.test(value) || SYMBOL_REGEX.test(value);
    }
    
    module.exports = {
      create: function(context) {
        return {
          Literal: function(node) {
            const { value } = node;
            if (hasChinese(value)) {
              context.report({
                node,
                message: '{{ str }} contains Chinese, move it to T constant.',
                data: {
                  str: node.value,
                },
              });
            }
          },
        };
      },
    };

    在这段代码中,我们在create里遍历所有Literal类型节点,因为我们需要检查的对象是所有字符串。根据 estree 的定义,我们会知道Literal类型阶段结构如下:

    interface Literal <: Expression {
        type: "Literal";
        value: string | boolean | null | number | RegExp;
    }

    那么需要做的就是判断该节点的 value 是否包含中文,在这里我们用的是正则表达式进行判断,当含有中文字符或标点时,就调用context.report方法报告一个错误。在应用这条规则之后,全局所有直接使用中文字符串的代码都会报错,只需要对统一存放中文的变量T所在的代码部分禁用这条规则,就可以避免误判。

    在笔者所在项目中我们使用的是“通过翻译函数处理”的方式,所以规则会更为复杂一些,需要判断当前字符串的父节点是否为我们的翻译函数,Espree 会在每个节点上都记录对应的父节点信息,因此我们可以通过类似node.parent.callee.name === 't'这样的方式进行判断。不过实际情况中还需要做更安全、全面的判断,例如正确识别这样的使用方式t('你好' + '世界'),后一个字符串的父节点是加法运算符。

    在这个示例中我们主要理解了遍历函数的工作方式以及如何使用合理的节点类型实现需求,因此不再过度展开实际场景中的细节实现。不过相信读者已经可以感受到写一条自定义规则需要非常全面的考虑代码中的各类场景,这也是为什么 eslint 要求自定义规则要遵循 TDD 的开发方式,用足够多的单元测试保证规则使用时符合预期,在最后我们会介绍 eslint 提供的单测框架。

    示例 2:特殊作用域

    首先构建一个场景用于展示这类规则:

    不论是以及非常成熟的 Node.JS + selenium 体系还是较新的 headless chrome 生态,这类端到端工具一般都会提供在目标浏览器上执行一段 JavaScript 的能力,例如这样:

    client.execute(
      function(foo, bar) {
        document.title = foo + bar;
      },
      ['foo', 'bar']
    );

    client.execute方法接收两个参数,第一个为在浏览器端执行的函数,第二个则是从当前代码传递给执行函数的参数,而浏览器端也只能使用传递的参数而不能直接使用当前代码中的变量。在这种场景下,很容易出现类似这样的问题:

    const foo = 'foo';
    const bar = 'bar';
    client.execute(function() {
      document.title = foo + bar;
    });

    对于 eslint 来说并不知道document.title = foo + bar;将在浏览器端的作用域中执行,而又发现有同名变量foobar被定义在当前代码中,则不会认为这段代码有错误,这种情况下我们就可以尝试自定义规则来对这个特殊场景做检查:

    module.exports = {
      create: function(context) {
        return {
          'Program:exit': function() {
            const globalScope = context.getScope();
            const stack = globalScope.childScopes.slice();
    
            while (stack.length) {
              const scope = stack.pop();
              stack.push.apply(stack, scope.childScopes);
    
              if (scope.block.parent.callee.property.name === 'execute') {
                const undefs = scope.through.forEach((ref) =>
                  context.report({
                    node: ref.identifier,
                    message: "'{{name}}' is not defined.",
                    data: ref.identifier,
                  })
                );
              }
            }
          },
        };
      },
    };

    以上代码中继续省略一些过于细节的实现,例如判断子作用域是否为client.execute的第一个参数以及将浏览器中的全局变量加入未定义变量的白名单等等,重点关注 eslint 为我们提供的一些帮助方法。

    这次我们的节点选择器为Program:exit,也就是下行完毕、开始上行完整的 AST 时执行我们的自定义检查,Program类型的节点对应的是完整的源码树,在 eslint 中即是当前文件。

    在检查时,首先我们使用context.getScope获取了当前正在遍历的作用域,又由于我们处在Program节点中,这个作用域即为这个代码文件中的最高作用域。之后我们构建一个栈,通过不断地把 childScopes 压入栈中在读取出来的方式,实现递归的访问到所有的子作用域。

    之后在处理每个子作用域时,都做了一个简单的判断(同样是简化过后的版本),来确定该作用域是否为我们需要独立判断的client.execute方法中第一个函数内的作用域。

    当找到该函数内的作用域之后,我们就可以使用scope对象上的各种方法进行判断了。事实上作用域是静态分析中较为复杂的部分,如果完全独立的去判断作用域中的引用等问题相对复杂,好在 eslint 对外暴露了 scope manager interface,让我们可以最大程度的复用封装好的各类作用域接口。

    在 scope manager interface 中可以看到scope.through方法的描述:

    The array of references which could not be resolved in this scope.

    正是我们需要的!所以最后只需要简单的遍历scope.through返回的未定义引用数组,就可以找到该作用域下所有的未定义变量。

    通过这个示例,可以看出 eslint 本身已经对许多常用需求做了高阶的封装,直接复用可以大大缩减“开发工具消耗的时间”。

    示例 3:保证 API 使用规范

    继续构建一个场景:假如我们在业务中我们有一个内部 API "Checker",用于校验某些操作(action)是否可执行,而校验的方式是判断 action 对应的规则(rule)是否全部通过,代码如下:

    const checker = new Checker({
      rules: {
        ruleA(value) {},
        ruleB(value) {},
      },
      actions: {
        action1: ['ruleA', 'ruleB'],
        action2: ['ruleB'],
      },
    });

    在 Checker 这个 API 使用的过程中,我们需要:

    1. 所有 action 依赖的 rule 都在rules属性中被定义。
    2. 所有定义的 rule 都被 action 使用。

    由于 action 和 rule 的关联性只靠 action value 数组中的字符串名称与 rule key 值保持一致来维护,所以第一条要求如果出了问题只能在运行时发现错误,而第二条要求甚至不会造成任何错误,但在长期的迭代下可能会遗留大量无用代码。

    当然这个场景我们很容易通过单元测试进行覆盖,但如果 Checker 是一个在项目各种都会分散使用的 API,那么单元测试即使有一个通用的用例,也需要开发者手动导出 checker 再引入到测试代码中去,这本身就存在一定遗漏的风险。

    从开发体验出发,我们也尝试用 eslint 的自定义规则完成这个需求,实现一个实时的 Checker API 使用方式校验。

    首先我们需要在静态分析阶段分辨代码中的一个 Class 是否为 Checker Class,从而进一步做校验,单纯从变量名称判断过于粗暴,容易发生误判;而从 Class 来源分析很可能出现跨文件引用的情况,又过于复杂。所以我们借鉴一些编程语言中处理类似场景的做法,在需要编译器特殊处理的地方加一些特殊的标记帮助编译器定位,例如这样:

    // [action-checker]
    const checker = new Checker({});

    在构造 checker 实例的前一行写一个注释// [action-checker],表明下一行开始的代码是使用了 Checker API,在这基础上,我们就可以开始编写 eslint 规则:

    const COMMENT_MARKER = '[action-checker]';
    
    function getStartLine(node) {
      return node.loc.start.line;
    }
    
    module.exports = {
      create: function(context) {
        const sourceCode = context.getSourceCode();
        const markerLines = {};
    
        return {
          Program: function() {
            const comments = sourceCode.getAllComments();
            comments.forEach((comment) => {
              if (comment.value.trim() === COMMENT_MARKER) {
                markerLines[getStartLine(comment)] = comment;
              }
            });
          },
          ObjectExpression: function(expressionNode) {
            const startLine = getStartLine(expressionNode);
            if (markLines[startLine - 1]) {
              // check actions and rules
            }
          },
        };
      },
    };

    在这个示例中,我们使用了context.getSourceCode获取 sourceCode 对象,和上个例子中的 scope 类似,也是 eslint 封装过后的接口,例如可以继续通过sourceCode.getAllComments获取代码中的所有注释。

    为了实现通过注释定位 checker 实例的目的,我们在markLines对象中存储了带有特殊标记的注释的行数,获取行数的方式则是node.loc.start.line。这里的loc也是 eslint 给各个 AST 节点增加的一个重要属性,包含了节点对应代码在源代码中的坐标信息。

    之后遍历所有ObjectExpression类型节点,通过markLines中存储的位置信息,确定某个ObjectExpression节点是否为我们需要校验的 checker 对象,再根据 estree 中定义的ObjectExpression结构,找到我们需要的 actions values 和 rules keys 进行比较,此处不对细节处理做进一步展开。

    这个示例说明注释作为静态分析中非常重要的元素有很好的利用价值,许多项目也提供从一定格式(例如 JSDoc)的注释中直接生成文档的功能,也是代码静态分析常见的应用,除了示例中用到的sourceCode.getAllComments可以获取所有注释,还提供sourceCode.getJSDocComment这样只获取 JSDoc 类型注释的方法。

    总而言之,基于 eslint 提供的强大框架,我们可以拓展出很多极大提高开发体验和代码质量的用法。

    杂项

    借鉴社区

    eslint 本身提供的功能很强但也很多,光从文档中不一定能找到最适用的方法,而 eslint 本身已经有大量的 通用规则,很多时候直接从相近的规则中学习会更加有效。例如示例 2 中对作用域的判断就是从社区的通用规则no-undef中借鉴了很多大部分思路。

    TDD

    上文提到,静态分析需要非常全面的考虑编译器会遇到的各类代码,但如果每次编写规则都需要在一个很大的 code base 中进行测试效率也很低。因此 eslint 提倡用测试驱动开发的方式,先写出对规则的预期结果,再实现规则。

    如果通过上文提到的 eslint yeoman 脚手架新建一个规则模版,会自动生成一个对应的测试文件。以示例 1 为例,内容如下:

    const rule = require('../../../lib/rules/use-t-function');
    const RuleTester = require('eslint').RuleTester;
    
    const parserOptions = {
      ecmaVersion: 8,
      sourceType: 'module',
      ecmaFeatures: {
        experimentalObjectRestSpread: true,
        jsx: true,
      },
    };
    
    const ruleTester = new RuleTester({ parserOptions });
    ruleTester.run('use-t-function', rule, {
      valid: [
        { code: 'fn()' },
        { code: '"This is not a chinese string."' },
        { code: "t('名称:')" },
        { code: "t('一' + '二' + '三')" },
      ],
    
      invalid: [
        {
          code: '<Col xs={6}>名称:</Col>',
          errors: [
            {
              message: '名称: contains Chinese, use t function to wrap it.',
              type: 'Literal',
            },
          ],
        },
      ],
    });

    核心的部分是require('eslint').RuleTester提供的单测框架 Class,传入一些参数例如解析器配置之后就可以实例化一个 ruleTester。实际执行时需要提供足够的 valid 和 invalid 代码场景,并且对 invalid 类型代码报告的错误信息做断言,当所有测试用例通过后,就可以认为规则的编写符合预期了。

    完整示例代码

    自定义 eslint 规则在我们的实际项目中已经有所应用,示例中的实际完整规则代码都存放在公网 Github 仓库中,如果对文中跳过的细节实现感兴趣可以自行翻看。

    展开全文
  • 看我如何玩转自定义ESLint规则

    千次阅读 2018-02-26 17:47:00
    来了淘宝之后各种忙,最近正好在弄自定义ESLint相关的东西,写篇文章mark下。最近开通了自己的微信公共号“王和阳的航海日志”,上面记录着自己的学习、思考、实践和成长的过程,欢迎关注,欢迎交流。 基本思路...

    前言

    来了淘宝之后各种忙,最近正好在弄自定义ESLint相关的东西,写篇文章mark下。最近开通了自己的微信公共号“王和阳的航海日志”,上面记录着自己的学习、思考、实践和成长的过程,欢迎关注,欢迎交流。

    公共号二维码

    基本思路

    关于如何使用ESLint来自定义规则,具体的开发流程详见ESLint官网。这里就不赘述,接下来我重点讲讲基本思路。

    首先需要了解的是,在分析代码前,ESlint会通过Estree(ESLint所支持的一种Parser,不同的Parser有不同的适用场景)对代码进行词法分析和语法分析,并将代码解析一棵抽象语法树AST(Abstract Syntax Tree),将不同类型的代码语句分成不同类型的节点,则一份代码文件便形成了一个树状的结构,之后ESlint会依次遍历语法树上的节点。

    为了便于对AST有一个直观的了解,可以在astexplorer上直接输入代码后查看对应的结构图,这里要注意是在页面的tab栏选择对应的Parser,不同的Parser解析出来的结果会有差异。我选择的Parser是babel-eslint,并输入代码

    import View from "rax-view";
    import Text from "rax-text";
    class Mod extends Component {
      render() {
        return <View><Text>111</Text></View>;
      }
    }

    解析后得到的语法树如下:
    实例AST

    JavaScript的AST抽象语法树中具体有哪些节点,可以详见这里,需要注意的是,以上的节点信息中不包括jsx代码,具体的jsx特有的节点的详细信息可以在这里找到。

    牛刀小试

    在了解了这些后,我们可以由此做一些自定义规则,创建自定义规则的步骤如下
    1. 找出合法的代码并输入到astexplorer.net中,查看对应的语法树
    2. 找出非法的代码并输入astexplorer.net中,查看对应的语法树
    3. 对比两者的不同,并在关键节点上进行检查

    这里我们以在rax里禁止输入dangerouslySetInnerHTML为例讲讲应该如何自定义规则。
    这里的合法代码与上一段中的代码相同,对应的语法树如下:
    合法代码的AST

    非法代码如下:

    <View dangerouslySetInnerHTML={{__html: 'First &middot; Second'}}></View>

    对应的语法树如下:
    非法代码的AST
    观察后可以发现dangerouslySetInnerHTML在语法树中其实就是一个JSXIdentifier下的一个key为name的值,那么我们只需要把这个特殊值给检测出来即可。具体代码如下:

    const jsxUtils = require('jsx-ast-utils');
    
    const {hasProp} = jsxUtils;
    
    module.exports = {
    
      meta: {
        docs: {
          description: 'Disallow use dangerouslySetInnerHTML',
          category: 'Best Practices',
          recommended: false
        },
        fixable: null,
        schema: []
      },
    
    // context中有很多实用的方法,比如获取注释、获取纯文本源码
      create(context) {
        return {
          JSXOpeningElement: node => {
            const dangerouslySetInnerHTML = hasProp(node.attributes, 'dangerouslySetInnerHTML');
    
            if (dangerouslySetInnerHTML) {
              context.report({
                node,
                message: `disallow use dangerouslySetInnerHTML!`
              });
            }
          }
        };
      }
    };

    Code Path

    下面结合ESLint所提供的功能,继续讲讲一些好玩的功能。ESLint中能够对代码路径进行分析,而代码路径是程序的执行路线。

    一段程序由几段代码路径构成,而代码路径又由CodePathCodePathSegment这两个对象来表示。
    - CodePath表示的是整个代码的路径,这个对象不仅在全局中存在,且存在于每一个函数中。
    - CodePathSegment是代码路径的一部分,一段代码路径由多个CodePathSegment对象组成。
    让我们以最简单的一段代码为例来分析下整个过程是如何进行的

    while (a) {
        a = foo();
    }
    bar();

    对应的Code Path如下:
    1. 一开始,代码分析直接run到循环的末尾
    步骤1
    2. 随后进入循环的路径中
    步骤2
    3. 到达末尾
    步骤3

    整个过程其实就和我们人在阅读代码的时候是类似的,只是在这个分析过程中,每一步都是以一个节点作为跳转,且有清晰的逻辑。

    在了解了上述这些信息后,我们可以做一些简单的代码检查,例如检查是否一段代码是可以被访问的

    var last = require("lodash").last;
    
    function isReachable(segment) {
        return segment.reachable;
    }
    
    module.exports = function(context) {
        var codePathStack = [];
    
        return {
            // 存储CodePath对象
            "onCodePathStart": function(codePath) {
                codePathStack.push(codePath);
            },
            "onCodePathEnd": function(codePath) {
                codePathStack.pop();
            },
    
            // 检查是否可执行
            "ExpressionStatement": function(node) {
                var codePath = last(codePathStack);
    
                // 检查当前代码片段
                if (!codePath.currentSegments.some(isReachable)) {
                    //若代码不能被访问,则报错
                    context.report({message: "Unreachable!", node: node});
                }
            }
        };
    };

    总结

    ESLint功能强大,结合代码路径、各种加强版的parser能做到很多神奇的检查规则和奇技淫巧黑魔法,但真正重要的,是结合各个业务的特点进行定制,在团队开发项目时,能又快又好地完成项目,同时让代码具有可读性和可维护性,才是每一个用ESLint的人心中所应该保持的初心。

    引用资料

    展开全文
  • 本文首发于政采云前端团队博客:自定义 ESLint 规则,让代码持续美丽https://www.zoo.team/article/eslint-rules背景“一段真实的代码发展历史很久很久以前,有一个需求,然后产出了一段代码,代码优雅而简洁...
  • 本文首发于政采云前端团队博客:自定义 ESLint 规则,让代码持续美丽https://www.zoo.team/article/eslint-rules背景“一段真实的代码发展历史很久很久以前,有一个需求,然后产出了一段代码,代码优雅而简洁...
  • 这时候如果我们想要在项目中配置自定义eslint,可以参照以下步骤进行。 一、首先检查是否安装了 eslint和babel-eslint 查看package.json文件,一般npm run eject之后都会看到已经安装了。 如果尚未安装,则需要安装...
  • eslint-config-lfe 特性 规则基于airbnb(es6) 支持react,react-a11y,react-hook 支持vue 支持打字稿 规则 已覆盖的规范请查看 使用 安装 npm install eslint eslint - babel eslint - config - lfe 在项目根目录...
  • ESLint 是什么? ESLint 是一个可组装的javascript和jax代码检测工具。 官网 ESLint 怎么使用? 1、 在项目根目录创建一个.eslintrc.js文件 2、对Eslint代码检测进行配置 module.exports = { // ...
  • 介绍ESLint 是用来保持代码规范的Prettier 是十分受...具体步骤如下:安装依赖npm install --save-dev babel-eslint eslint eslint-config-prettier eslint-loader eslint-plugin-vue eslint-plugin-prettier pre...
  • eslint-plugin-eggache 鸡蛋RTFM问题的自定义伴游规则 用法 npm i eslint-plugin-eggache --save 将eggache添加到.eslintrc配置文件的plugins部分。 // ${app_root}/.eslintrc { "extends" : [ "plugin:eggache...
  • eslint插件额外规则 附加的附加规则 安装 npm install --save-dev eslint-plugin-extra-rules 配置示例 添加到您的.eslintrc : { "plugins" : [ "extra-rules" ] , "rules" : { "extra-rules/no-commented-out-...
  • eslint配置方式有两种: 注释配置:使用js注释来直接嵌入ESLint配置信息到一个文件里 配置文件:使用一个js,JSON或者YAML文件来给整个目录和它的子目录指定配置信息。这些配置可以写在一个文件名为.eslintrc.*的...
  • webstorm 自定义eslint格式化代码

    千次阅读 2018-09-13 14:55:20
    vscode上配置eslint格式化代码使用的Prettier 插件,一键格式化。 webstorm上的配置流程如下:https://blog.csdn.net/qq_29329037/article/details/80100450
  • Create-react-app 配置自定义eslint

    千次阅读 2019-12-20 13:22:07
    但是在实际项目中我们往往需要自定义一些配置,但又希望保留这种跟着包升级配置的优势。 eslint是项目中非常常用的工具,它的配置也是因人而异,因项目团队而异。而create-react-app默认是有一...
  • ESLint于2013年6月份推出,至今4个年头,最新版本v4.8.0。它是目前主流的用于Javascript和JSX代码规范检查的利器,很多大公司比如Airbnb和Google均有一套自己的Javascript编码规范,而规范的实施背后离不开ESLint的...
  • npm run eject =>...2、webpack.config.js 文件中eslint配置处开启useEslintrc: true (注意开启关闭规则时设置cache: true 的值为false否则缓存可能为影响校验展示结果) { test: /\.(js|mjs|jsx|t...
  • eslint导入解析器-babel模块 用于解析器 安装 npm install --save-dev eslint-plugin-import eslint-import-resolver-babel-module 用法 在您的.eslintrc文件中,将此解析器传递给eslint-plugin-import : ...
  • eslint-config-divisio ESLint可共享配置的文档 共享配置是一种创建编码样式的模式的好方法 :smiling_face_with_sunglasses: 安装 最好的选择是使用所有必要的对等依赖项来安装此软件包,因此您可以使用: npx ...
  • 使用eslint和editorconfig规范代码为什么要用这些:代码规范有利于团队协作纯手工规范耗时耗力而且不能保证准确性能配合编辑器自动提醒错误,提高开发效率eslint随着ECMAScript版本一直更新的Js lint工具,插件丰富...
  • image 如何使用自定义规则 使用自定义ESLint 规则,你需要自定义一个 ESLint 的插件,然后将规则写到自定义ESLint 插件中,然后在业务代码中添加 ESLint 配置,引入 ESLint 插件。 ESLint 插件 创建 创建一个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 590
精华内容 236
关键字:

自定义eslint