]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 | ||
12 | const { | |
13 | CALL, | |
14 | CONSTRUCT, | |
15 | ReferenceTracker, | |
16 | getStringIfConstant | |
17 | } = require("eslint-utils"); | |
18 | const regexpp = require("regexpp"); | |
19 | ||
20 | //------------------------------------------------------------------------------ | |
21 | // Helpers | |
22 | //------------------------------------------------------------------------------ | |
23 | ||
24 | const parser = new regexpp.RegExpParser(); | |
25 | ||
26 | //------------------------------------------------------------------------------ | |
27 | // Rule Definition | |
28 | //------------------------------------------------------------------------------ | |
29 | ||
30 | module.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); | |
d3726936 | 62 | } catch { |
eb39fafa DC |
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 | }; |