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}; 111