Permalink
Cannot retrieve contributors at this time
178 lines (154 sloc)
5.84 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/eslint/lib/rules/prefer-named-capture-group.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @fileoverview Rule to enforce requiring named capture groups in regular expression. | |
* @author Pig Fang <https://github.com/g-plane> | |
*/ | |
"use strict"; | |
//------------------------------------------------------------------------------ | |
// Requirements | |
//------------------------------------------------------------------------------ | |
const { | |
CALL, | |
CONSTRUCT, | |
ReferenceTracker, | |
getStringIfConstant | |
} = require("@eslint-community/eslint-utils"); | |
const regexpp = require("@eslint-community/regexpp"); | |
//------------------------------------------------------------------------------ | |
// Helpers | |
//------------------------------------------------------------------------------ | |
const parser = new regexpp.RegExpParser(); | |
/** | |
* Creates fixer suggestions for the regex, if statically determinable. | |
* @param {number} groupStart Starting index of the regex group. | |
* @param {string} pattern The regular expression pattern to be checked. | |
* @param {string} rawText Source text of the regexNode. | |
* @param {ASTNode} regexNode AST node which contains the regular expression. | |
* @returns {Array<SuggestionResult>} Fixer suggestions for the regex, if statically determinable. | |
*/ | |
function suggestIfPossible(groupStart, pattern, rawText, regexNode) { | |
switch (regexNode.type) { | |
case "Literal": | |
if (typeof regexNode.value === "string" && rawText.includes("\\")) { | |
return null; | |
} | |
break; | |
case "TemplateLiteral": | |
if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) { | |
return null; | |
} | |
break; | |
default: | |
return null; | |
} | |
const start = regexNode.range[0] + groupStart + 2; | |
return [ | |
{ | |
fix(fixer) { | |
const existingTemps = pattern.match(/temp\d+/gu) || []; | |
const highestTempCount = existingTemps.reduce( | |
(previous, next) => | |
Math.max(previous, Number(next.slice("temp".length))), | |
0 | |
); | |
return fixer.insertTextBeforeRange( | |
[start, start], | |
`?<temp${highestTempCount + 1}>` | |
); | |
}, | |
messageId: "addGroupName" | |
}, | |
{ | |
fix(fixer) { | |
return fixer.insertTextBeforeRange( | |
[start, start], | |
"?:" | |
); | |
}, | |
messageId: "addNonCapture" | |
} | |
]; | |
} | |
//------------------------------------------------------------------------------ | |
// Rule Definition | |
//------------------------------------------------------------------------------ | |
/** @type {import('../shared/types').Rule} */ | |
module.exports = { | |
meta: { | |
type: "suggestion", | |
docs: { | |
description: "Enforce using named capture group in regular expression", | |
recommended: false, | |
url: "https://eslint.org/docs/latest/rules/prefer-named-capture-group" | |
}, | |
hasSuggestions: true, | |
schema: [], | |
messages: { | |
addGroupName: "Add name to capture group.", | |
addNonCapture: "Convert group to non-capturing.", | |
required: "Capture group '{{group}}' should be converted to a named or non-capturing group." | |
} | |
}, | |
create(context) { | |
const sourceCode = context.sourceCode; | |
/** | |
* Function to check regular expression. | |
* @param {string} pattern The regular expression pattern to be checked. | |
* @param {ASTNode} node AST node which contains the regular expression or a call/new expression. | |
* @param {ASTNode} regexNode AST node which contains the regular expression. | |
* @param {string|null} flags The regular expression flags to be checked. | |
* @returns {void} | |
*/ | |
function checkRegex(pattern, node, regexNode, flags) { | |
let ast; | |
try { | |
ast = parser.parsePattern(pattern, 0, pattern.length, { | |
unicode: Boolean(flags && flags.includes("u")), | |
unicodeSets: Boolean(flags && flags.includes("v")) | |
}); | |
} catch { | |
// ignore regex syntax errors | |
return; | |
} | |
regexpp.visitRegExpAST(ast, { | |
onCapturingGroupEnter(group) { | |
if (!group.name) { | |
const rawText = sourceCode.getText(regexNode); | |
const suggest = suggestIfPossible(group.start, pattern, rawText, regexNode); | |
context.report({ | |
node, | |
messageId: "required", | |
data: { | |
group: group.raw | |
}, | |
suggest | |
}); | |
} | |
} | |
}); | |
} | |
return { | |
Literal(node) { | |
if (node.regex) { | |
checkRegex(node.regex.pattern, node, node, node.regex.flags); | |
} | |
}, | |
Program(node) { | |
const scope = sourceCode.getScope(node); | |
const tracker = new ReferenceTracker(scope); | |
const traceMap = { | |
RegExp: { | |
[CALL]: true, | |
[CONSTRUCT]: true | |
} | |
}; | |
for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { | |
const regex = getStringIfConstant(refNode.arguments[0]); | |
const flags = getStringIfConstant(refNode.arguments[1]); | |
if (regex) { | |
checkRegex(regex, refNode, refNode.arguments[0], flags); | |
} | |
} | |
} | |
}; | |
} | |
}; |