UNPKG

6.86 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const utils_1 = require("@typescript-eslint/utils");
23const tsutils = __importStar(require("tsutils"));
24const util_1 = require("../util");
25var ArgumentType;
26(function (ArgumentType) {
27 ArgumentType[ArgumentType["Other"] = 0] = "Other";
28 ArgumentType[ArgumentType["String"] = 1] = "String";
29 ArgumentType[ArgumentType["RegExp"] = 2] = "RegExp";
30 ArgumentType[ArgumentType["Both"] = 3] = "Both";
31})(ArgumentType || (ArgumentType = {}));
32exports.default = (0, util_1.createRule)({
33 name: 'prefer-regexp-exec',
34 defaultOptions: [],
35 meta: {
36 type: 'suggestion',
37 fixable: 'code',
38 docs: {
39 description: 'Enforce that `RegExp#exec` is used instead of `String#match` if no global flag is provided',
40 recommended: false,
41 requiresTypeChecking: true,
42 },
43 messages: {
44 regExpExecOverStringMatch: 'Use the `RegExp#exec()` method instead.',
45 },
46 schema: [],
47 },
48 create(context) {
49 const globalScope = context.getScope();
50 const parserServices = (0, util_1.getParserServices)(context);
51 const typeChecker = parserServices.program.getTypeChecker();
52 const sourceCode = context.getSourceCode();
53 /**
54 * Check if a given node type is a string.
55 * @param node The node type to check.
56 */
57 function isStringType(type) {
58 return (0, util_1.getTypeName)(typeChecker, type) === 'string';
59 }
60 /**
61 * Check if a given node type is a RegExp.
62 * @param node The node type to check.
63 */
64 function isRegExpType(type) {
65 return (0, util_1.getTypeName)(typeChecker, type) === 'RegExp';
66 }
67 function collectArgumentTypes(types) {
68 let result = ArgumentType.Other;
69 for (const type of types) {
70 if (isRegExpType(type)) {
71 result |= ArgumentType.RegExp;
72 }
73 else if (isStringType(type)) {
74 result |= ArgumentType.String;
75 }
76 }
77 return result;
78 }
79 function isLikelyToContainGlobalFlag(node) {
80 if (node.type === utils_1.AST_NODE_TYPES.CallExpression ||
81 node.type === utils_1.AST_NODE_TYPES.NewExpression) {
82 const [, flags] = node.arguments;
83 return (flags &&
84 flags.type === utils_1.AST_NODE_TYPES.Literal &&
85 typeof flags.value === 'string' &&
86 flags.value.includes('g'));
87 }
88 return node.type === utils_1.AST_NODE_TYPES.Identifier;
89 }
90 return {
91 "CallExpression[arguments.length=1] > MemberExpression.callee[property.name='match'][computed=false]"(memberNode) {
92 const objectNode = memberNode.object;
93 const callNode = memberNode.parent;
94 const [argumentNode] = callNode.arguments;
95 const argumentValue = (0, util_1.getStaticValue)(argumentNode, globalScope);
96 if (!isStringType(typeChecker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(objectNode)))) {
97 return;
98 }
99 // Don't report regular expressions with global flag.
100 if ((!argumentValue && isLikelyToContainGlobalFlag(argumentNode)) ||
101 (argumentValue &&
102 argumentValue.value instanceof RegExp &&
103 argumentValue.value.flags.includes('g'))) {
104 return;
105 }
106 if (argumentNode.type === utils_1.AST_NODE_TYPES.Literal &&
107 typeof argumentNode.value == 'string') {
108 const regExp = RegExp(argumentNode.value);
109 return context.report({
110 node: memberNode.property,
111 messageId: 'regExpExecOverStringMatch',
112 fix: (0, util_1.getWrappingFixer)({
113 sourceCode,
114 node: callNode,
115 innerNode: [objectNode],
116 wrap: objectCode => `${regExp.toString()}.exec(${objectCode})`,
117 }),
118 });
119 }
120 const argumentType = typeChecker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(argumentNode));
121 const argumentTypes = collectArgumentTypes(tsutils.unionTypeParts(argumentType));
122 switch (argumentTypes) {
123 case ArgumentType.RegExp:
124 return context.report({
125 node: memberNode.property,
126 messageId: 'regExpExecOverStringMatch',
127 fix: (0, util_1.getWrappingFixer)({
128 sourceCode,
129 node: callNode,
130 innerNode: [objectNode, argumentNode],
131 wrap: (objectCode, argumentCode) => `${argumentCode}.exec(${objectCode})`,
132 }),
133 });
134 case ArgumentType.String:
135 return context.report({
136 node: memberNode.property,
137 messageId: 'regExpExecOverStringMatch',
138 fix: (0, util_1.getWrappingFixer)({
139 sourceCode,
140 node: callNode,
141 innerNode: [objectNode, argumentNode],
142 wrap: (objectCode, argumentCode) => `RegExp(${argumentCode}).exec(${objectCode})`,
143 }),
144 });
145 }
146 },
147 };
148 },
149});
150//# sourceMappingURL=prefer-regexp-exec.js.map
\No newline at end of file