UNPKG

5.01 kBJavaScriptView Raw
1/**
2 * @fileoverview Disallow use of multiple spaces.
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13 meta: {
14 docs: {
15 description: "disallow multiple spaces",
16 category: "Best Practices",
17 recommended: false
18 },
19
20 fixable: "whitespace",
21
22 schema: [
23 {
24 type: "object",
25 properties: {
26 exceptions: {
27 type: "object",
28 patternProperties: {
29 "^([A-Z][a-z]*)+$": {
30 type: "boolean"
31 }
32 },
33 additionalProperties: false
34 }
35 },
36 additionalProperties: false
37 }
38 ]
39 },
40
41 create(context) {
42
43 // the index of the last comment that was checked
44 const exceptions = { Property: true },
45 options = context.options[0];
46 let hasExceptions = true,
47 lastCommentIndex = 0;
48
49 if (options && options.exceptions) {
50 Object.keys(options.exceptions).forEach(function(key) {
51 if (options.exceptions[key]) {
52 exceptions[key] = true;
53 } else {
54 delete exceptions[key];
55 }
56 });
57 hasExceptions = Object.keys(exceptions).length > 0;
58 }
59
60 /**
61 * Determines if a given source index is in a comment or not by checking
62 * the index against the comment range. Since the check goes straight
63 * through the file, once an index is passed a certain comment, we can
64 * go to the next comment to check that.
65 * @param {int} index The source index to check.
66 * @param {ASTNode[]} comments An array of comment nodes.
67 * @returns {boolean} True if the index is within a comment, false if not.
68 * @private
69 */
70 function isIndexInComment(index, comments) {
71 while (lastCommentIndex < comments.length) {
72 const comment = comments[lastCommentIndex];
73
74 if (comment.range[0] <= index && index < comment.range[1]) {
75 return true;
76 } else if (index > comment.range[1]) {
77 lastCommentIndex++;
78 } else {
79 break;
80 }
81 }
82
83 return false;
84 }
85
86 //--------------------------------------------------------------------------
87 // Public
88 //--------------------------------------------------------------------------
89
90 return {
91 Program() {
92
93 const sourceCode = context.getSourceCode(),
94 source = sourceCode.getText(),
95 allComments = sourceCode.getAllComments(),
96 pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g; // note: repeating space
97 let parent;
98
99
100 /**
101 * Creates a fix function that removes the multiple spaces between the two tokens
102 * @param {RuleFixer} leftToken left token
103 * @param {RuleFixer} rightToken right token
104 * @returns {Function} fix function
105 * @private
106 */
107 function createFix(leftToken, rightToken) {
108 return function(fixer) {
109 return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
110 };
111 }
112
113 while (pattern.test(source)) {
114
115 // do not flag anything inside of comments
116 if (!isIndexInComment(pattern.lastIndex, allComments)) {
117
118 const token = sourceCode.getTokenByRangeStart(pattern.lastIndex);
119
120 if (token) {
121 const previousToken = sourceCode.getTokenBefore(token);
122
123 if (hasExceptions) {
124 parent = sourceCode.getNodeByRangeIndex(pattern.lastIndex - 1);
125 }
126
127 if (!parent || !exceptions[parent.type]) {
128 context.report({
129 node: token,
130 loc: token.loc.start,
131 message: "Multiple spaces found before '{{value}}'.",
132 data: { value: token.value },
133 fix: createFix(previousToken, token)
134 });
135 }
136 }
137
138 }
139 }
140 }
141 };
142
143 }
144};