自己动手写符合自己业务需求的eslint规则

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

image.png

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

使用eslint和stylelint之类的工具扫描前端代码现在已经基本成为前端同学的标配。但是,业务这么复杂,指望eslint等提供的工具完全解决业务中遇到的代码问题还是不太现实的。我们一线业务同学也要有自己的写规则的能力。

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

image.png

另外,在进入细节之前,请思考一下: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-consolevalid✓ 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 failing1) no-consolevalidconsole.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+0at 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是比较简洁的:

image.png

代码信息类主要我们使用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的对应关系如下图:

image.png

我们前面的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"});}}};}

三 代码路径分析

前面我们讨论的基本都是一个代码片段,现在我们把代码逻辑串起来,形成一条代码路径。

代码路径就不止只有顺序结构,还有分支和循环。

image.png

除了采用上面的事件处理方法之外,我们还可以针对CodePath事件进行处理:

image.png

事件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:

image.png

五 高级话题

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以及语言细节有比较深的了解。预祝大家通过写出适合自己业务的检查器,写出更健壮的代码。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/511863.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

双11特刊|一站式在线数据管理平台DMS技术再升级,高效护航双11

简介&#xff1a; 10万企业共同选择的数据库服务平台 阿里云数据库已连续多年稳定支撑天猫双11&#xff0c;历经极端流量场景淬炼。除了保障稳定顺滑的基本盘&#xff0c;今年大促期间数据库通过全面云原生化&#xff0c;大幅提升用户体验&#xff0c;让技术帮助业务产生更有价…

账户配置阻止使用计算机.怎样开机,开机自启动设置怎么操作 开机自启动设置如何禁止【图文介绍】...

我们都知道&#xff0c;如今的电脑被我们广泛地运用着&#xff0c; 因为它具有很多的功能&#xff0c;比如我们可以通过电脑实现我们平时工作的需要&#xff0c;还可以不出 门 便能够网上购物&#xff0c;非常方便快捷。电脑一般来说都是由硬件系统和软件系统组成的。有时候可能…

Gartner:2021年全球半导体收入增长26%

供稿 | Gartner 出品 | CSDN 云计算 根据Gartner公司的最终统计结果&#xff0c;2021年全球半导体收入同比增长26.3%&#xff0c;总计5950亿美元。 Gartner研究副总裁Andrew Norwood表示&#xff1a;“引起当前芯片短缺的各种事件继续影响全球原设备制造商&#xff08;OEM&…

pandas 判断是否等于nan_Python之pandas笔记

一、创建1.创建 Series (一维&#xff0c;带标签的数组)t pd.Series(np.arange(10), index list(string.ascii_uppercase[:10]))2.创建 DataFrame (二维&#xff0c;Series的容器 )t pd.DataFrame(np.arange(6).reshape((2,3)), indexlist(ab),columnslist(csr) )3.DataFrame…

解密 Dubbo 三大中心的部署架构

简介&#xff1a;Dubbo作为一个微服务框架&#xff0c;Dubbo SDK与应用服务绑定在同一个进程内&#xff0c;它跟随着应用服务被部署在分布式集群各个位置&#xff0c;为了在分布式环境下实现各个应用服务间的协作&#xff0c; Dubbo 定义了一些中心化组件。 作者 | 华钟明 01…

历经7年双11实战,阿里巴巴是如何定义云原生混部调度优先级及服务质量的?

简介&#xff1a; 本文将聚焦在 K8s 层的容器优先级和服务质量模型上&#xff0c;希望给业界提供一些可借鉴的思路。 作者&#xff1a;南异 引言 阿里巴巴在离线混部技术从 2014 年开始&#xff0c;经历了七年的双十一检验&#xff0c;内部已经大规模落地推广&#xff0c;每…

python 判断是否有余数_判断多个坐标是否在同一条直线上|Python练习系列[13]

练习内容:判断多个坐标是否在同一条直线上判断多个坐标是否在同一条直线上|Python练习系列[13]_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.comprint(请输入几个点的横纵坐标,程序将会返回这几个点是否在同一条直线上) def coor_nums():#获得每个值的横纵坐标int_list…

阿里云 FaaS 架构设计

简介&#xff1a;本篇内容将从 2 个部分为读者介绍关于阿里云 FaaS 架构设计和神龙高密部署的 FaaS&#xff0c;希望可以让大家对阿里云 FaaS 有更深入的了解&#xff0c;并可以将 FaaS 应用到项目中&#xff0c;达到降本提效的目的。 一、基于 ECS 的 FaaS 在阿里云传统架构…

MLPerf纪录技术分享:优化卷积合并算法提升Resnet50推理性能

作者 | 王申领 供稿 | 浪潮 MLPerf是一套衡量机器学习系统性能的权威标准&#xff0c;将在标准目标下训练或推理机器学习模型的时间&#xff0c;作为一套系统性能的测量标准。MLPerf推理任务包括图像识别&#xff08;ResNet50&#xff09;、医学影像分割&#xff08;3D-UNet&a…

nas存储如何做远程服务器数据备份_备份数据?7 个理由告诉你为什么要用 NAS,而不用移动硬盘...

您有遇过这样的情况吗&#xff1f;用 U 盘或移动硬盘备份文件&#xff0c;但在重要时刻却找不到 U 盘&#xff0c;甚至遇到移动硬盘毁损的状况。这个时候是不是忽然间好恨自己&#xff0c;拿什么拯救你——我亲爱的数据。转而使用 NAS 的原因。让我们来看看地球上最安全的存储是…

用手机写代码:基于 Serverless 的在线编程能力探索

简介&#xff1a;Serverless 架构的按量付费模式&#xff0c;可以在保证在线编程功能性能的前提下&#xff0c;进一步降低成本。本文将会以阿里云函数计算为例&#xff0c;通过 Serverless 架构实现一个 Python 语言的在线编程功能&#xff0c;并对该功能进一步的优化&#xff…

如何形成统一设计风格-实践篇

简介&#xff1a;在上一篇《业务团队如何统一架构设计风格&#xff1f;》中&#xff0c;探讨了一种业务架构的设计规范&#xff0c;以期达到这些目标&#xff1a;用标准约束技术细节&#xff1b;用技术工具而非文档推行标准&#xff1b;持续重构而非造新轮子&#xff1b;重视业…

计算机教师资格考试试题,全国教师资格考试信息技术练习题(二)

中公教师通过对全国教师资格考试考情的分析&#xff0c;总结出全国教师资格考试《信息技术学科知识与能力》算法与程序设计部分的知识点&#xff0c;并提供了该模块的相关考试试题&#xff0c;希望能帮助考生抓住考点、有针对性地复习。一、算法与程序设计模块考点分析通过对全…

A/B测试白皮书:领先企业营收增长是落后者5倍

Forrester调查显示&#xff1a;企业使用A/B测试的ROI达126% 4月26日&#xff0c;《火山引擎A/B测试总体经济影响白皮书》正式发布。这份白皮书由市场研究公司Forrester调研撰写&#xff0c;揭示了A/B测试对于企业营收增长、运营成本、生产力优化等方面的重要影响。基于对多家企…

limit mongodb 聚合_MongoDB 统计 group 操作用不了,试试 mapReduce 吧

问题回顾今天&#xff0c;同事小张 Q 我&#xff0c; 说自己辛苦花了一天的时间&#xff0c;基于 mongodb 数据库开发的待办统计功能一直报错&#xff01;于是笔者花了近半小时了解小张的开发需求以及代码实现方式&#xff0c;大致明白问题出在对待办 collection 做统计时&…

基于 EMR OLAP 的开源实时数仓解决方案之 ClickHouse 事务实现

简介&#xff1a;阿里云 EMR OLAP 与 Flink 团队深度合作&#xff0c;支持了 Flink 到 ClickHouse 的 Exactly-Once写入来保证整个实时数仓数据的准确性。本文介绍了基于 EMR OLAP 的开源实时数仓解决方案。 作者简介&#xff1a;阿里云 EMR-OLAP 团队&#xff1b;主要负责开源…

【ClickHouse 技术系列】- 在 ClickHouse 中处理实时更新

简介&#xff1a;本文翻译自 Altinity 针对 ClickHouse 的系列技术文章。面向联机分析处理&#xff08;OLAP&#xff09;的开源分析引擎 ClickHouse&#xff0c;因其优良的查询性能&#xff0c;PB级的数据规模&#xff0c;简单的架构&#xff0c;被国内外公司广泛采用。本系列技…

从“数字化出海”到“出海数字化”,亚马逊云科技如何助力出海业务数字化转型

国内市场快速发展之外&#xff0c;全球也是广阔的市场。 据中国贸促会《中国企业对外投资现状及意向调查报告&#xff08;2021年版&#xff09;》显示&#xff0c;我国对外直接投资流量和存量稳居全球前三。在开拓海外市场的成绩里&#xff0c;2021全球《财富》世界500强榜单里…

amos调节变量怎么画_插画师该怎么收费?两个方法一看就懂。

任何自由插画师都逃不过要给客户报价这么一个令人头痛的环节&#xff0c;包括医学插画师。甲方往往希望看到一个菜单一样的价格表&#xff0c;把一切类型的插画安排的明明白白。而这样简单粗暴的算法&#xff0c;作为乙方又何尝不想要呢&#xff01;纵观插画圈&#xff0c;萌新…

技术实践第二期|Flutter异常捕获

简介&#xff1a;应用性能稳定是良好用户体验中非常关键的一环&#xff0c;为了更好保障应用性能稳定&#xff0c;异常捕获在保证线上产品稳定中扮演着至关重要的角色。我们团队在推出了U-APM移动应用性能监控的产品后&#xff0c;帮助开发者定位并解决掉很多线上的疑难杂症。随…