UNPKG

3.42 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to enforce requiring named capture groups in regular expression.
3 * @author Pig Fang <https://github.com/g-plane>
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const {
13 CALL,
14 CONSTRUCT,
15 ReferenceTracker,
16 getStringIfConstant
17} = require("eslint-utils");
18const regexpp = require("regexpp");
19
20//------------------------------------------------------------------------------
21// Helpers
22//------------------------------------------------------------------------------
23
24const parser = new regexpp.RegExpParser();
25
26//------------------------------------------------------------------------------
27// Rule Definition
28//------------------------------------------------------------------------------
29
30module.exports = {
31 meta: {
32 type: "suggestion",
33
34 docs: {
35 description: "enforce using named capture group in regular expression",
36 category: "Best Practices",
37 recommended: false,
38 url: "https://eslint.org/docs/rules/prefer-named-capture-group"
39 },
40
41 schema: [],
42
43 messages: {
44 required: "Capture group '{{group}}' should be converted to a named or non-capturing group."
45 }
46 },
47
48 create(context) {
49
50 /**
51 * Function to check regular expression.
52 * @param {string} pattern The regular expression pattern to be check.
53 * @param {ASTNode} node AST node which contains regular expression.
54 * @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not.
55 * @returns {void}
56 */
57 function checkRegex(pattern, node, uFlag) {
58 let ast;
59
60 try {
61 ast = parser.parsePattern(pattern, 0, pattern.length, uFlag);
62 } catch {
63
64 // ignore regex syntax errors
65 return;
66 }
67
68 regexpp.visitRegExpAST(ast, {
69 onCapturingGroupEnter(group) {
70 if (!group.name) {
71 context.report({
72 node,
73 messageId: "required",
74 data: {
75 group: group.raw
76 }
77 });
78 }
79 }
80 });
81 }
82
83 return {
84 Literal(node) {
85 if (node.regex) {
86 checkRegex(node.regex.pattern, node, node.regex.flags.includes("u"));
87 }
88 },
89 Program() {
90 const scope = context.getScope();
91 const tracker = new ReferenceTracker(scope);
92 const traceMap = {
93 RegExp: {
94 [CALL]: true,
95 [CONSTRUCT]: true
96 }
97 };
98
99 for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
100 const regex = getStringIfConstant(node.arguments[0]);
101 const flags = getStringIfConstant(node.arguments[1]);
102
103 if (regex) {
104 checkRegex(regex, node, flags && flags.includes("u"));
105 }
106 }
107 }
108 };
109 }
110};