An error occurred while loading the file. Please try again.
no-regex-spaces.js 5.93 KiB
/**
 * @fileoverview Rule to count multiple spaces in regular expressions
 * @author Matt DuVall <http://www.mattduvall.com/>
 */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
const regexpp = require("regexpp");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const regExpParser = new regexpp.RegExpParser();
const DOUBLE_SPACE = / {2}/u;
/**
 * Check if node is a string
 * @param {ASTNode} node node to evaluate
 * @returns {boolean} True if its a string
 * @private
function isString(node) {
    return node && node.type === "Literal" && typeof node.value === "string";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
    meta: {
        type: "suggestion",
        docs: {
            description: "disallow multiple spaces in regular expressions",
            category: "Possible Errors",
            recommended: true,
            url: "https://eslint.org/docs/rules/no-regex-spaces"
        schema: [],
        fixable: "code",
        messages: {
            multipleSpaces: "Spaces are hard to count. Use {{{length}}}."
    create(context) {
        /**
         * Validate regular expression
         * @param {ASTNode} nodeToReport Node to report.
         * @param {string} pattern Regular expression pattern to validate.
         * @param {string} rawPattern Raw representation of the pattern in the source code.
         * @param {number} rawPatternStartRange Start range of the pattern in the source code.
         * @param {string} flags Regular expression flags.
         * @returns {void}
         * @private
        function checkRegex(nodeToReport, pattern, rawPattern, rawPatternStartRange, flags) {
            // Skip if there are no consecutive spaces in the source code, to avoid reporting e.g., RegExp(' \ ').
            if (!DOUBLE_SPACE.test(rawPattern)) {
return; } const characterClassNodes = []; let regExpAST; try { regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); } catch { // Ignore regular expressions with syntax errors return; } regexpp.visitRegExpAST(regExpAST, { onCharacterClassEnter(ccNode) { characterClassNodes.push(ccNode); } }); const spacesPattern = /( {2,})(?: [+*{?]|[^+*{?]|$)/gu; let match; while ((match = spacesPattern.exec(pattern))) { const { 1: { length }, index } = match; // Report only consecutive spaces that are not in character classes. if ( characterClassNodes.every(({ start, end }) => index < start || end <= index) ) { context.report({ node: nodeToReport, messageId: "multipleSpaces", data: { length }, fix(fixer) { if (pattern !== rawPattern) { return null; } return fixer.replaceTextRange( [rawPatternStartRange + index, rawPatternStartRange + index + length], ` {${length}}` ); } }); // Report only the first occurrence of consecutive spaces return; } } } /** * Validate regular expression literals * @param {ASTNode} node node to validate * @returns {void} * @private */ function checkLiteral(node) { if (node.regex) { const pattern = node.regex.pattern; const rawPattern = node.raw.slice(1, node.raw.lastIndexOf("/")); const rawPatternStartRange = node.range[0] + 1; const flags = node.regex.flags; checkRegex( node, pattern, rawPattern, rawPatternStartRange, flags
); } } /** * Validate strings passed to the RegExp constructor * @param {ASTNode} node node to validate * @returns {void} * @private */ function checkFunction(node) { const scope = context.getScope(); const regExpVar = astUtils.getVariableByName(scope, "RegExp"); const shadowed = regExpVar && regExpVar.defs.length > 0; const patternNode = node.arguments[0]; const flagsNode = node.arguments[1]; if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) { const pattern = patternNode.value; const rawPattern = patternNode.raw.slice(1, -1); const rawPatternStartRange = patternNode.range[0] + 1; const flags = isString(flagsNode) ? flagsNode.value : ""; checkRegex( node, pattern, rawPattern, rawPatternStartRange, flags ); } } return { Literal: checkLiteral, CallExpression: checkFunction, NewExpression: checkFunction }; } };