UNPKG

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