简介:eslint是构建在AST Parser根底上的规定扫描器,缺省状况下应用espree作为AST解析器。rules写好对于AST事件的回调,linter解决源代码之后会依据相应的事件来回调rules中的处理函数。另外,在进入细节之前,请思考一下:eslint的边界在哪里?哪些性能是通过eslint写规定能够做到的,哪些是用eslint无奈做到的?

作者 | 旭伦
起源 | 阿里技术公众号

应用eslint和stylelint之类的工具扫描前端代码当初曾经根本成为前端同学的标配。然而,业务这么简单,指望eslint等提供的工具齐全解决业务中遇到的代码问题还是不太事实的。咱们一线业务同学也要有本人的写规定的能力。

eslint是构建在AST Parser根底上的规定扫描器,缺省状况下应用espree作为AST解析器。rules写好对于AST事件的回调,linter解决源代码之后会依据相应的事件来回调rules中的处理函数。

另外,在进入细节之前,请思考一下:eslint的边界在哪里?哪些性能是通过eslint写规定能够做到的,哪些是用eslint无奈做到的?

一 先学会如何写规定测试

兵马未动,测试后行。规定写进去,如何用理论代码进行测试呢?

所幸非常简单,间接写个json串把代码写进来就好了。

咱们来看个no-console的例子,就是不容许代码中呈现console.*语句的规定。

首先把规定和测试运行对象ruleTester引进来:

//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const rule = require("../../../lib/rules/no-console"),    { RuleTester } = require("../../../lib/rule-tester");//------------------------------------------------------------------------------// Tests//------------------------------------------------------------------------------const ruleTester = new RuleTester();

而后咱们就间接调用ruleTester的run函数就好了。无效的样例放在valid上面,有效的样例放在invalid上面,是不是很简略。

咱们先看下无效的:

ruleTester.run("no-console", rule, {    valid: [        "Console.info(foo)",        // single array item        { code: "console.info(foo)", options: [{ allow: ["info"] }] },        { code: "console.warn(foo)", options: [{ allow: ["warn"] }] },        { code: "console.error(foo)", options: [{ allow: ["error"] }] },        { code: "console.log(foo)", options: [{ allow: ["log"] }] },        // multiple array items        { code: "console.info(foo)", options: [{ allow: ["warn", "info"] }] },        { code: "console.warn(foo)", options: [{ allow: ["error", "warn"] }] },        { code: "console.error(foo)", options: [{ allow: ["log", "error"] }] },        { code: "console.log(foo)", options: [{ allow: ["info", "log", "warn"] }] },        // https://github.com/eslint/eslint/issues/7010        "var console = require('myconsole'); console.log(foo)"    ],

能通过的状况比拟容易,咱们就间接给代码和选项就好。

而后是有效的:

    invalid: [        // no options        { code: "console.log(foo)", errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.error(foo)", errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.info(foo)", errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.warn(foo)", errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        //  one option        { code: "console.log(foo)", options: [{ allow: ["error"] }], errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.error(foo)", options: [{ allow: ["warn"] }], errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.info(foo)", options: [{ allow: ["log"] }], errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.warn(foo)", options: [{ allow: ["error"] }], errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        // multiple options        { code: "console.log(foo)", options: [{ allow: ["warn", "info"] }], errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.error(foo)", options: [{ allow: ["warn", "info", "log"] }], errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.info(foo)", options: [{ allow: ["warn", "error", "log"] }], errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        { code: "console.warn(foo)", options: [{ allow: ["info", "log"] }], errors: [{ messageId: "unexpected", type: "MemberExpression" }] },        // In case that implicit global variable of 'console' exists        { code: "console.log(foo)", env: { node: true }, errors: [{ messageId: "unexpected", type: "MemberExpression" }] }    ]});

有效的要判断下出错信息是不是合乎预期。

咱们应用mocha运行下下面的测试脚本:

./node_modules/.bin/mocha tests/lib/rules/no-console.js

运行后果如下:

no-console   valid     ✓ Console.info(foo)     ✓ console.info(foo)     ✓ console.warn(foo)     ✓ console.error(foo)     ✓ console.log(foo)     ✓ console.info(foo)     ✓ console.warn(foo)     ✓ console.error(foo)     ✓ console.log(foo)     ✓ var console = require('myconsole'); console.log(foo)   invalid     ✓ console.log(foo)     ✓ console.error(foo)     ✓ console.info(foo)     ✓ console.warn(foo)     ✓ console.log(foo)     ✓ console.error(foo)     ✓ console.info(foo)     ✓ console.warn(foo)     ✓ console.log(foo)     ✓ console.error(foo)     ✓ console.info(foo)     ✓ console.warn(foo)     ✓ console.log(foo) 23 passing (83ms)

如果在valid外面放一个不能通过的,则会报错,比方咱们加一个:

ruleTester.run("no-console", rule, {    valid: [        "Console.info(foo)",        // single array item        { code: "console.log('Hello,World')", options: [] },

就会报上面的错:

  1 failing  1) no-console       valid         console.log('Hello,World'):      AssertionError [ERR_ASSERTION]: Should have no errors but had 1: [  {    ruleId: 'no-console',    severity: 1,    message: 'Unexpected console statement.',    line: 1,    column: 1,    nodeType: 'MemberExpression',    messageId: 'unexpected',    endLine: 1,    endColumn: 12  }]      + expected - actual      -1      +0            at testValidTemplate (lib/rule-tester/rule-tester.js:697:20)      at Context.< anonymous> (lib/rule-tester/rule-tester.js:972:29)      at processImmediate (node:internal/timers:464:21)

阐明咱们刚加的console是会报一个messageId为unexpected,而nodeType为MemberExpression的谬误。

咱们应将其放入到invalid外面:

invalid: [    // no options    { code: "console.log('Hello,World')", errors: [{ messageId: "unexpected", type: "MemberExpression" }] },

再运行,就能够胜利了:

  invalid     ✓ console.log('Hello,World')

二 规定入门

会跑测试之后,咱们就能够写本人的规定啦。

咱们先看下规定的模板,其实次要要提供meta对象和create办法:

module.exports = {    meta: {        type: "规定类型,如suggestion",        docs: {            description: "规定形容",            category: "规定分类:如Possible Errors",            recommended: true,            url: "阐明规定的文档地址,如https://eslint.org/docs/rules/no-extra-semi"        },        fixable: "是否能够修复,如code",        schema: [] // 选项    },    create: function(context) {        return {            // 事件回调        };    }};

总体来说,一个eslint规定所能做的事件,就是写事件回调函数,在回调函数中应用context中获取的AST等信息进行剖析。

context提供的API是比拟简洁的:

代码信息类次要咱们应用getScope获取作用域的信息,getAncestors获取上一级AST节点,getDeclaredVariables获取变量表。最初的绝招是间接获取源代码getSourceCode本人剖析去。

markVariableAsUsed用于跨文件剖析,用于剖析变量的应用状况。

report函数用于输入剖析后果,比方报错信息、批改倡议和主动修复的代码等。

这么说太形象了,咱们来看例子。

还以no-console为例,咱们先看meta局部,这部分不波及逻辑代码,都是一些配置:

meta: {      type: "suggestion",      docs: {          description: "disallow the use of `console`",          recommended: false,          url: "https://eslint.org/docs/rules/no-console"      },      schema: [          {              type: "object",              properties: {                  allow: {                      type: "array",                      items: {                          type: "string"                      },                      minItems: 1,                      uniqueItems: true                  }              },              additionalProperties: false          }      ],      messages: {          unexpected: "Unexpected console statement."      }  },

咱们再看no-console的回调函数,只解决一处Program:exit, 这是程序退出的事件:

        return {            "Program:exit"() {                const scope = context.getScope();                const consoleVar = astUtils.getVariableByName(scope, "console");                const shadowed = consoleVar && consoleVar.defs.length > 0;                /*                 * 'scope.through' includes all references to undefined                 * variables. If the variable 'console' is not defined, it uses                 * 'scope.through'.                 */                const references = consoleVar                    ? consoleVar.references                    : scope.through.filter(isConsole);                if (!shadowed) {                    references                        .filter(isMemberAccessExceptAllowed)                        .forEach(report);                }            }        };

1 获取作用域和AST信息

咱们首先通过context.getScope()获取作用域信息。作用域与AST的对应关系如下图:

咱们后面的console语句的例子,首先拿到的都是全局作用域,举例如下:

< ref *1> GlobalScope {  type: 'global',  set: Map(38) {    'Array' => Variable {      name: 'Array',      identifiers: [],      references: [],      defs: [],      tainted: false,      stack: true,      scope: [Circular *1],      eslintImplicitGlobalSetting: 'readonly',      eslintExplicitGlobal: false,      eslintExplicitGlobalComments: undefined,      writeable: false    },    'Boolean' => Variable {      name: 'Boolean',      identifiers: [],      references: [],      defs: [],      tainted: false,      stack: true,      scope: [Circular *1],      eslintImplicitGlobalSetting: 'readonly',      eslintExplicitGlobal: false,      eslintExplicitGlobalComments: undefined,      writeable: false    },    'constructor' => Variable {      name: 'constructor',      identifiers: [],      references: [],      defs: [],      tainted: false,      stack: true,      scope: [Circular *1],      eslintImplicitGlobalSetting: 'readonly',      eslintExplicitGlobal: false,      eslintExplicitGlobalComments: undefined,      writeable: false    },...

具体看一下38个全局变量,温习下Javascript根底吧:

  set: Map(38) {     'Array' => [Variable],     'Boolean' => [Variable],     'constructor' => [Variable],     'Date' => [Variable],     'decodeURI' => [Variable],     'decodeURIComponent' => [Variable],     'encodeURI' => [Variable],     'encodeURIComponent' => [Variable],     'Error' => [Variable],     'escape' => [Variable],     'eval' => [Variable],     'EvalError' => [Variable],     'Function' => [Variable],     'hasOwnProperty' => [Variable],     'Infinity' => [Variable],     'isFinite' => [Variable],     'isNaN' => [Variable],     'isPrototypeOf' => [Variable],     'JSON' => [Variable],     'Math' => [Variable],     'NaN' => [Variable],     'Number' => [Variable],     'Object' => [Variable],     'parseFloat' => [Variable],     'parseInt' => [Variable],     'propertyIsEnumerable' => [Variable],     'RangeError' => [Variable],     'ReferenceError' => [Variable],     'RegExp' => [Variable],     'String' => [Variable],     'SyntaxError' => [Variable],     'toLocaleString' => [Variable],     'toString' => [Variable],     'TypeError' => [Variable],     'undefined' => [Variable],     'unescape' => [Variable],     'URIError' => [Variable],     'valueOf' => [Variable]   },

咱们看到,所有的变量,都以一个名为set的Map中,这样咱们就能够以遍历获取所有的变量。

针对no-console的规定,咱们次要是要查找是否有叫console的变量名。于是能够这么写:

getVariableByName(initScope, name) {      let scope = initScope;      while (scope) {          const variable = scope.set.get(name);          if (variable) {              return variable;          }          scope = scope.upper;      }      return null;  },

咱们能够在方才列出的38个变量中发现,console是并没有定义的变量,所以

const consoleVar = astUtils.getVariableByName(scope, "console");

的后果是null.

于是咱们要去查找未定义的变量,这部分是在scope.through中,果然找到了name是console的节点:

[  Reference {    identifier: Node {      type: 'Identifier',      loc: [SourceLocation],      range: [Array],      name: 'console',      parent: [Node]    },    from: < ref *2> GlobalScope {      type: 'global',      set: [Map],      taints: Map(0) {},      dynamic: true,      block: [Node],      through: [Circular *1],      variables: [Array],      references: [Array],      variableScope: [Circular *2],      functionExpressionScope: false,      directCallToEvalScope: false,      thisFound: false,      __left: null,      upper: null,      isStrict: false,      childScopes: [],      __declaredVariables: [WeakMap],      implicit: [Object]    },    tainted: false,    resolved: null,    flag: 1,    __maybeImplicitGlobal: undefined  }]

这样咱们就能够写个查看reference的名字是不是console的函数就好:

  function isConsole(reference) {      const id = reference.identifier;      return id && id.name === "console";  }

而后用这个函数去filter scope.though中的所有未定义的变量:

scope.through.filter(isConsole);

最初一步是输入报告,针对过滤出的reference进行报告:

    references        .filter(isMemberAccessExceptAllowed)        .forEach(report);

报告问题应用context的report函数:

    function report(reference) {        const node = reference.identifier.parent;        context.report({            node,            loc: node.loc,            messageId: "unexpected"        });    }

产生问题的代码行数能够从node中获取到。

2 解决特定类型的语句

no-console从规定书写上并不是最容易的,咱们以其为例次要是这类问题最多。上面咱们触类旁通,看看针对其它不应该呈现的语句该如何解决。

其中最简略的就是针对一类语句通通报错,比方no-continue规定,就是遇到ContinueStatement就报错:

module.exports = {    meta: {        type: "suggestion",        docs: {            description: "disallow `continue` statements",            recommended: false,            url: "https://eslint.org/docs/rules/no-continue"        },        schema: [],        messages: {            unexpected: "Unexpected use of continue statement."        }    },    create(context) {        return {            ContinueStatement(node) {                context.report({ node, messageId: "unexpected" });            }        };    }};

不容许应用debugger的no-debugger规定:

 create(context) {     return {         DebuggerStatement(node) {             context.report({                 node,                 messageId: "unexpected"             });         }     }; }

不许应用with语句:

  create(context) {       return {           WithStatement(node) {               context.report({ node, messageId: "unexpectedWith" });           }       };   }

在case语句中不许定义变量、函数和类:

 create(context) {     function isLexicalDeclaration(node) {         switch (node.type) {             case "FunctionDeclaration":             case "ClassDeclaration":                 return true;             case "VariableDeclaration":                 return node.kind !== "var";             default:                 return false;         }     }     return {         SwitchCase(node) {             for (let i = 0; i < node.consequent.length; i++) {                 const statement = node.consequent[i];                 if (isLexicalDeclaration(statement)) {                     context.report({                         node: statement,                         messageId: "unexpected"                     });                 }             }         }     }; }

多个类型语句能够共用一个处理函数。

比方不许应用构造方法生成数组:

function check(node) {        if (            node.arguments.length !== 1 &&            node.callee.type === "Identifier" &&            node.callee.name === "Array"        ) {            context.report({ node, messageId: "preferLiteral" });        }    }    return {        CallExpression: check,        NewExpression: check    };

不许给类定义赋值:

 create(context) {     function checkVariable(variable) {         astUtils.getModifyingReferences(variable.references).forEach(reference => {             context.report({ node: reference.identifier, messageId: "class", data: { name: reference.identifier.name } });         });     }     function checkForClass(node) {         context.getDeclaredVariables(node).forEach(checkVariable);     }     return {         ClassDeclaration: checkForClass,         ClassExpression: checkForClass     }; }

函数的参数不容许重名:

create(context) {    function isParameter(def) {        return def.type === "Parameter";    }    function checkParams(node) {        const variables = context.getDeclaredVariables(node);        for (let i = 0; i < variables.length; ++i) {            const variable = variables[i];            const defs = variable.defs.filter(isParameter);            if (defs.length >= 2) {                context.report({                    node,                    messageId: "unexpected",                    data: { name: variable.name }                });            }        }    }    return {        FunctionDeclaration: checkParams,        FunctionExpression: checkParams    };}

如果事件太多的话,能够写成一个数组,这被称为选择器数组:

const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];...                        [loopSelector](node) {                if (currentCodePath.currentSegments.some(segment => segment.reachable)) {                    loopsToReport.add(node);                }            },

除了间接解决语句类型,还能够针对类型加上一些额定的判断。

比方不容许应用delete运算符:

create(context) {      return {          UnaryExpression(node) {              if (node.operator === "delete" && node.argument.type === "Identifier") {                  context.report({ node, messageId: "unexpected" });              }          }      };  }

不准应用"=="和"!="运算符:

  create(context) {       return {           BinaryExpression(node) {               const badOperator = node.operator === "==" || node.operator === "!=";               if (node.right.type === "Literal" && node.right.raw === "null" && badOperator ||                       node.left.type === "Literal" && node.left.raw === "null" && badOperator) {                   context.report({ node, messageId: "unexpected" });               }           }       };   }

不许和-0进行比拟:

create(context) {      function isNegZero(node) {          return node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && node.argument.value === 0;      }      const OPERATORS_TO_CHECK = new Set([">", ">=", "<", "<=", "==", "===", "!=", "!=="]);      return {          BinaryExpression(node) {              if (OPERATORS_TO_CHECK.has(node.operator)) {                  if (isNegZero(node.left) || isNegZero(node.right)) {                      context.report({                          node,                          messageId: "unexpected",                          data: { operator: node.operator }                      });                  }              }          }      };  }

不准给常量赋值:

create(context) {      function checkVariable(variable) {          astUtils.getModifyingReferences(variable.references).forEach(reference => {              context.report({ node: reference.identifier, messageId: "const", data: { name: reference.identifier.name } });          });      }      return {          VariableDeclaration(node) {              if (node.kind === "const") {                  context.getDeclaredVariables(node).forEach(checkVariable);              }          }      };  }

3 :exit - 语句完结事件

除了语句事件之外,eslint还提供了:exit事件。

比方下面的例子咱们应用了VariableDeclaration语句事件,咱们上面看看如何应用VariableDeclaration完结时调用的VariableDeclaration:exit事件。

咱们看一个不容许应用var定义变量的例子:

        return {            "VariableDeclaration:exit"(node) {                if (node.kind === "var") {                    report(node);                }            }        };

如果感觉进入和退出不好辨别的话,咱们来看一个不容许在非函数的块中应用var来定义变量的例子:

            BlockStatement: enterScope,            "BlockStatement:exit": exitScope,            ForStatement: enterScope,            "ForStatement:exit": exitScope,            ForInStatement: enterScope,            "ForInStatement:exit": exitScope,            ForOfStatement: enterScope,            "ForOfStatement:exit": exitScope,            SwitchStatement: enterScope,            "SwitchStatement:exit": exitScope,            CatchClause: enterScope,            "CatchClause:exit": exitScope,            StaticBlock: enterScope,            "StaticBlock:exit": exitScope,

这些逻辑的作用是,进入语句块的时候调用enterScope,退出语句块的时候调用exitScope:

        function enterScope(node) {            stack.push(node.range);        }        function exitScope() {            stack.pop();        }

4 间接应用文字信息 - Literal

比方不容许应用"-.7"这样省略了0的浮点数。此时应用Literal来解决纯文字信息。

  create(context) {       const sourceCode = context.getSourceCode();       return {           Literal(node) {               if (typeof node.value === "number") {                   if (node.raw.startsWith(".")) {                       context.report({                           node,                           messageId: "leading",                           fix(fixer) {                               const tokenBefore = sourceCode.getTokenBefore(node);                               const needsSpaceBefore = tokenBefore &&                                   tokenBefore.range[1] === node.range[0] &&                                   !astUtils.canTokensBeAdjacent(tokenBefore, `0${node.raw}`);                               return fixer.insertTextBefore(node, needsSpaceBefore ? " 0" : "0");                           }                       });                   }                   if (node.raw.indexOf(".") === node.raw.length - 1) {                       context.report({                           node,                           messageId: "trailing",                           fix: fixer => fixer.insertTextAfter(node, "0")                       });                   }               }           }       };   }

不准应用八进制数字:

    create(context) {        return {            Literal(node) {                if (typeof node.value === "number" && /^0[0-9]/u.test(node.raw)) {                    context.report({                        node,                        messageId: "noOcatal"                    });                }            }        };    }

三 代码路径分析

后面咱们探讨的根本都是一个代码片段,当初咱们把代码逻辑串起来,造成一条代码门路。

代码门路就不止只有程序构造,还有分支和循环。

除了采纳下面的事件处理办法之外,咱们还能够针对CodePath事件进行解决:

事件onCodePathStart和onCodePathEnd用于整个门路的剖析,而onCodePathSegmentStart, onCodePathSegmentEnd是CodePath中的一个片段,onCodePathSegmentLoop是循环片段。

咱们来看一个循环的例子:

 create(context) {     const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [],         loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes),         loopSelector = loopTypesToCheck.join(","),         loopsByTargetSegments = new Map(),         loopsToReport = new Set();     let currentCodePath = null;     return {         onCodePathStart(codePath) {             currentCodePath = codePath;         },         onCodePathEnd() {             currentCodePath = currentCodePath.upper;         },         [loopSelector](node) {             if (currentCodePath.currentSegments.some(segment => segment.reachable)) {                 loopsToReport.add(node);             }         },         onCodePathSegmentStart(segment, node) {             if (isLoopingTarget(node)) {                 const loop = node.parent;                 loopsByTargetSegments.set(segment, loop);             }         },         onCodePathSegmentLoop(_, toSegment, node) {             const loop = loopsByTargetSegments.get(toSegment);             if (node === loop || node.type === "ContinueStatement") {                 loopsToReport.delete(loop);             }         },         "Program:exit"() {             loopsToReport.forEach(                 node => context.report({ node, messageId: "invalid" })             );         }     }; }

四 提供问题主动修复的代码

最初,咱们讲讲如何给问题给供主动修复代码。

咱们之前报告问题都是应用context.report函数,主动修复代码也是通过这个接口返回给调用者。

咱们以将"=="和"!="替换成"==="和"!=="为例。

这个fix没有多少技术含量哈,就是给原来发现问题的运算符多加一个"=":

report(node, ${node.operator}=);

最终实现时是调用了fixer的replaceText函数:

                fix(fixer) {                    if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {                        return fixer.replaceText(operatorToken, expectedOperator);                    }                    return null;                }

残缺的report代码如下:

        function report(node, expectedOperator) {            const operatorToken = sourceCode.getFirstTokenBetween(                node.left,                node.right,                token => token.value === node.operator            );            context.report({                node,                loc: operatorToken.loc,                messageId: "unexpected",                data: { expectedOperator, actualOperator: node.operator },                fix(fixer) {                    if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {                        return fixer.replaceText(operatorToken, expectedOperator);                    }                    return null;                }            });        }

Fixer反对4个增加API,2个删除API,2个替换类的API:

五 高级话题

1 React JSX的反对

Facebook给咱们封装好了框架,写起来也是蛮眼生的。刚好之前没有举markVariableAsUsed的例子,正好一起看了:

module.exports = {  meta: {    docs: {      description: 'Prevent React to be marked as unused',      category: 'Best Practices',      recommended: true,      url: docsUrl('jsx-uses-react'),    },    schema: [],  },  create(context) {    const pragma = pragmaUtil.getFromContext(context);    const fragment = pragmaUtil.getFragmentFromContext(context);    function handleOpeningElement() {      context.markVariableAsUsed(pragma);    }    return {      JSXOpeningElement: handleOpeningElement,      JSXOpeningFragment: handleOpeningElement,      JSXFragment() {        context.markVariableAsUsed(fragment);      },    };  },};

JSX的非凡之处是减少了JSXOpenElement, JSXClosingElement, JSXOpenFragment, JSXClosingFragment等解决JSX的事件。

2 TypeScript的反对

随着tslint合并到eslint中,TypeScript的lint性能由typescript-eslint承载。

因为estree只反对javascript,typescript-eslint提供兼容estree格局的parser.

既然是ts的lint,天然是领有了ts的反对,领有了新的工具办法,其根本架构仍是和eslint统一的:

import * as ts from 'typescript';import * as util from '../util';export default util.createRule({  name: 'no-for-in-array',  meta: {    docs: {      description: 'Disallow iterating over an array with a for-in loop',      recommended: 'error',      requiresTypeChecking: true,    },    messages: {      forInViolation:        'For-in loops over arrays are forbidden. Use for-of or array.forEach instead.',    },    schema: [],    type: 'problem',  },  defaultOptions: [],  create(context) {    return {      ForInStatement(node): void {        const parserServices = util.getParserServices(context);        const checker = parserServices.program.getTypeChecker();        const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);        const type = util.getConstrainedTypeAtLocation(          checker,          originalNode.expression,        );        if (          util.isTypeArrayTypeOrUnionOfArrayTypes(type, checker) ||          (type.flags & ts.TypeFlags.StringLike) !== 0        ) {          context.report({            node,            messageId: 'forInViolation',          });        }      },    };  },});

3 更换ESLint的AST解析器

ESLint反对应用第三方AST解析器,刚好Babel也反对ESLint,于是咱们就能够用@babel/eslint-parser来替换espree. 装好插件之后,批改.eslintrc.js即可:

module.exports = {  parser: "@babel/eslint-parser",};

Babel自带反对TypeScript。

六 StyleLint

说完了Eslint,咱们再花一小点篇幅看下StyleLint。

StyleLint与Eslint的架构思维一脉相承,都是对于AST的事件剖析进行解决的工具。

只不过css应用不同的AST Parser,比方Post CSS API, postcss-value-parser, postcss-selector-parser等。

咱们来看个例子体感一下:

const rule = (primary) => {    return (root, result) => {        const validOptions = validateOptions(result, ruleName, { actual: primary });        if (!validOptions) {            return;        }        root.walkDecls((decl) => {            const parsedValue = valueParser(getDeclarationValue(decl));            parsedValue.walk((node) => {                if (isIgnoredFunction(node)) return false;                if (!isHexColor(node)) return;                report({                    message: messages.rejected(node.value),                    node: decl,                    index: declarationValueIndex(decl) + node.sourceIndex,                    result,                    ruleName,                });            });        });    };};

也是相熟的report函数回报,也能够反对autofix的生成。

七 小结

以上,咱们根本将eslint规定写法的大抵框架梳理分明了。当然,理论写规刚的过程中还须要对于AST以及语言细节有比拟深的理解。预祝大家通过写出适宜本人业务的查看器,写出更强壮的代码。

原文链接
本文为阿里云原创内容,未经容许不得转载。