UNPKG

eslint-plugin-regexp

Version:

ESLint plugin for finding RegExp mistakes and RegExp style guide violations.

121 lines (120 loc) 5.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../utils"); const ast_utils_1 = require("../utils/ast-utils"); const type_tracker_1 = require("../utils/type-tracker"); const eslint_utils_1 = require("@eslint-community/eslint-utils"); exports.default = (0, utils_1.createRule)("prefer-regexp-test", { meta: { docs: { description: "enforce that `RegExp#test` is used instead of `String#match` and `RegExp#exec`", category: "Best Practices", recommended: false, }, fixable: "code", schema: [], messages: { disallow: "Use the `RegExp#test()` method instead of `{{target}}`, if you need a boolean.", }, type: "suggestion", }, create(context) { const sourceCode = context.sourceCode; const typeTracer = (0, type_tracker_1.createTypeTracker)(context); return { CallExpression(node) { if (!(0, ast_utils_1.isKnownMethodCall)(node, { match: 1, exec: 1 })) { return; } if (!isUseBoolean(node)) { return; } if (node.callee.property.name === "match") { if (!typeTracer.isString(node.callee.object)) { return; } const arg = node.arguments[0]; const evaluated = (0, ast_utils_1.getStaticValue)(context, arg); let argIsRegExp = true; if (evaluated && evaluated.value instanceof RegExp) { if (evaluated.value.flags.includes("g")) { return; } } else if (!typeTracer.isRegExp(arg)) { argIsRegExp = false; } const memberExpr = node.callee; context.report({ node, messageId: "disallow", data: { target: "String#match" }, fix(fixer) { if (!argIsRegExp) { return null; } if (node.arguments.length !== 1 || (0, eslint_utils_1.hasSideEffect)(memberExpr, sourceCode) || (0, eslint_utils_1.hasSideEffect)(node.arguments[0], sourceCode)) { return null; } const openParen = sourceCode.getTokenAfter(node.callee, eslint_utils_1.isOpeningParenToken); const closeParen = sourceCode.getLastToken(node); const stringRange = memberExpr.object.range; const regexpRange = [ openParen.range[1], closeParen.range[0], ]; const stringText = sourceCode.text.slice(...stringRange); const regexpText = sourceCode.text.slice(...regexpRange); return [ fixer.replaceTextRange(stringRange, regexpText), fixer.replaceText(memberExpr.property, "test"), fixer.replaceTextRange(regexpRange, stringText), ]; }, }); } if (node.callee.property.name === "exec") { if (!typeTracer.isRegExp(node.callee.object)) { return; } const execNode = node.callee.property; context.report({ node: execNode, messageId: "disallow", data: { target: "RegExp#exec" }, fix: (fixer) => fixer.replaceText(execNode, "test"), }); } }, }; }, }); function isUseBoolean(node) { const parent = (0, ast_utils_1.getParent)(node); if (!parent) { return false; } if (parent.type === "UnaryExpression") { return parent.operator === "!"; } if (parent.type === "CallExpression") { return (parent.callee.type === "Identifier" && parent.callee.name === "Boolean" && parent.arguments[0] === node); } if (parent.type === "IfStatement" || parent.type === "ConditionalExpression" || parent.type === "WhileStatement" || parent.type === "DoWhileStatement" || parent.type === "ForStatement") { return parent.test === node; } if (parent.type === "LogicalExpression") { if (parent.operator === "&&" || parent.operator === "||") { return isUseBoolean(parent); } } return false; }