//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/no-console"), { RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------// Tests//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
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" }] } ]});
./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)
ruleTester.run("no-console", rule, { valid: [ "Console.info(foo)",
// single array item { code: "console.log('Hello,World')", options: [] },
1 failing
no-console valid :
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)
invalid: [
// no options { code: "console.log('Hello,World')", errors: [{ messageId: "unexpected", type: "MemberExpression" }] },
invalid ✓ console.log('Hello,World')
二 规则入门
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 { // 事件回调 }; }};
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." } },
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> GlobalScope { type: 'global', set: Map(38) { => Variable { name: 'Array', identifiers: [], references: [], defs: [], tainted: false, stack: true, scope: [Circular *1], eslintImplicitGlobalSetting: 'readonly', eslintExplicitGlobal: false, eslintExplicitGlobalComments: undefined, writeable: false }, => Variable { name: 'Boolean', identifiers: [], references: [], defs: [], tainted: false, stack: true, scope: [Circular *1], eslintImplicitGlobalSetting: 'readonly', eslintExplicitGlobal: false, eslintExplicitGlobalComments: undefined, writeable: false }, => Variable { name: 'constructor', identifiers: [], references: [], defs: [], tainted: false, stack: true, scope: [Circular *1], eslintImplicitGlobalSetting: 'readonly', eslintExplicitGlobal: false, eslintExplicitGlobalComments: undefined, writeable: false },...
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] },
getVariableByName(initScope, name) { let scope = initScope;
while (scope) { const variable = scope.set.get(name);
if (variable) { return variable; }
scope = scope.upper; }
return null; },
const consoleVar = astUtils.getVariableByName(scope, "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 }]
function isConsole(reference) { const id = reference.identifier;
return id && id.name === "console"; }
scope.through.filter(isConsole);
references.filter(isMemberAccessExceptAllowed).forEach(report);
function report(reference) { const node = reference.identifier.parent;
context.report({ node, loc: node.loc, messageId: "unexpected" }); }
= { 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." } },
{
return { { node, messageId: "unexpected" }); } };
}};
create(context) {
return { DebuggerStatement(node) { context.report({ node, messageId: "unexpected" }); } };
}
create(context) {
return { WithStatement(node) { context.report({ node, messageId: "unexpectedWith" }); } };
}
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); } },
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" }); } } };
}
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); } } }; }
return { "VariableDeclaration:exit"(node) { if (node.kind === "var") { report(node); } } };
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,
function enterScope(node) { stack.push(node.range); }
function exitScope() { stack.pop(); }
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" }); } } }; }
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" }) ); } }; }
report(node, `${node.operator}=`);
fix(fixer) { if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) { return fixer.replaceText(operatorToken, expectedOperator); } return null; }
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:
= { meta: { docs: { description: 'Prevent React to be marked as unused', category: 'Best Practices', recommended: true, url: docsUrl('jsx-uses-react'), }, schema: [], },
{ const pragma = pragmaUtil.getFromContext(context); const fragment = pragmaUtil.getFragmentFromContext(context);
function handleOpeningElement() { context.markVariableAsUsed(pragma); }
return { JSXOpeningElement: handleOpeningElement, JSXOpeningFragment: handleOpeningElement, { context.markVariableAsUsed(fragment); }, }; },};
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', }); } }, }; },});
module.exports = { parser: "@babel/eslint-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, }); }); }); };};