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