简介: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以及语言细节有比拟深的理解。预祝大家通过写出适宜本人业务的查看器,写出更强壮的代码。
原文链接
本文为阿里云原创内容,未经容许不得转载。