UNPKG

3.82 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to count multiple spaces in regular expressions
3 * @author Matt DuVall <http://www.mattduvall.com/>
4 */
5
6"use strict";
7
8const astUtils = require("./utils/ast-utils");
9
10//------------------------------------------------------------------------------
11// Rule Definition
12//------------------------------------------------------------------------------
13
14module.exports = {
15 meta: {
16 type: "suggestion",
17
18 docs: {
19 description: "disallow multiple spaces in regular expressions",
20 category: "Possible Errors",
21 recommended: true,
22 url: "https://eslint.org/docs/rules/no-regex-spaces"
23 },
24
25 schema: [],
26 fixable: "code"
27 },
28
29 create(context) {
30 const sourceCode = context.getSourceCode();
31
32 /**
33 * Validate regular expressions
34 * @param {ASTNode} node node to validate
35 * @param {string} value regular expression to validate
36 * @param {number} valueStart The start location of the regex/string literal. It will always be the case that
37 * `sourceCode.getText().slice(valueStart, valueStart + value.length) === value`
38 * @returns {void}
39 * @private
40 */
41 function checkRegex(node, value, valueStart) {
42 const multipleSpacesRegex = /( {2,})( [+*{?]|[^+*{?]|$)/u,
43 regexResults = multipleSpacesRegex.exec(value);
44
45 if (regexResults !== null) {
46 const count = regexResults[1].length;
47
48 context.report({
49 node,
50 message: "Spaces are hard to count. Use {{{count}}}.",
51 data: { count },
52 fix(fixer) {
53 return fixer.replaceTextRange(
54 [valueStart + regexResults.index, valueStart + regexResults.index + count],
55 ` {${count}}`
56 );
57 }
58 });
59
60 /*
61 * TODO: (platinumazure) Fix message to use rule message
62 * substitution when api.report is fixed in lib/eslint.js.
63 */
64 }
65 }
66
67 /**
68 * Validate regular expression literals
69 * @param {ASTNode} node node to validate
70 * @returns {void}
71 * @private
72 */
73 function checkLiteral(node) {
74 const token = sourceCode.getFirstToken(node),
75 nodeType = token.type,
76 nodeValue = token.value;
77
78 if (nodeType === "RegularExpression") {
79 checkRegex(node, nodeValue, token.range[0]);
80 }
81 }
82
83 /**
84 * Check if node is a string
85 * @param {ASTNode} node node to evaluate
86 * @returns {boolean} True if its a string
87 * @private
88 */
89 function isString(node) {
90 return node && node.type === "Literal" && typeof node.value === "string";
91 }
92
93 /**
94 * Validate strings passed to the RegExp constructor
95 * @param {ASTNode} node node to validate
96 * @returns {void}
97 * @private
98 */
99 function checkFunction(node) {
100 const scope = context.getScope();
101 const regExpVar = astUtils.getVariableByName(scope, "RegExp");
102 const shadowed = regExpVar && regExpVar.defs.length > 0;
103
104 if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0]) && !shadowed) {
105 checkRegex(node, node.arguments[0].value, node.arguments[0].range[0] + 1);
106 }
107 }
108
109 return {
110 Literal: checkLiteral,
111 CallExpression: checkFunction,
112 NewExpression: checkFunction
113 };
114
115 }
116};