UNPKG

9.31 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to disalow whitespace that is not a tab or space, whitespace inside strings and comments are allowed
3 * @author Jonathan Kingston
4 * @author Christophe Porteneuve
5 */
6
7"use strict";
8
9//------------------------------------------------------------------------------
10// Constants
11//------------------------------------------------------------------------------
12
13const ALL_IRREGULARS = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/;
14const IRREGULAR_WHITESPACE = /[\f\v\u0085\u00A0\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg;
15const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mg;
16const LINE_BREAK = /\r\n|\r|\n|\u2028|\u2029/g;
17
18//------------------------------------------------------------------------------
19// Rule Definition
20//------------------------------------------------------------------------------
21
22module.exports = {
23 meta: {
24 docs: {
25 description: "disallow irregular whitespace outside of strings and comments",
26 category: "Possible Errors",
27 recommended: true
28 },
29
30 schema: [
31 {
32 type: "object",
33 properties: {
34 skipComments: {
35 type: "boolean"
36 },
37 skipStrings: {
38 type: "boolean"
39 },
40 skipTemplates: {
41 type: "boolean"
42 },
43 skipRegExps: {
44 type: "boolean"
45 }
46 },
47 additionalProperties: false
48 }
49 ]
50 },
51
52 create(context) {
53
54 // Module store of errors that we have found
55 let errors = [];
56
57 // Comment nodes. We accumulate these as we go, so we can be sure to trigger them after the whole `Program` entity is parsed, even for top-of-file comments.
58 const commentNodes = [];
59
60 // Lookup the `skipComments` option, which defaults to `false`.
61 const options = context.options[0] || {};
62 const skipComments = !!options.skipComments;
63 const skipStrings = options.skipStrings !== false;
64 const skipRegExps = !!options.skipRegExps;
65 const skipTemplates = !!options.skipTemplates;
66
67 const sourceCode = context.getSourceCode();
68
69 /**
70 * Removes errors that occur inside a string node
71 * @param {ASTNode} node to check for matching errors.
72 * @returns {void}
73 * @private
74 */
75 function removeWhitespaceError(node) {
76 const locStart = node.loc.start;
77 const locEnd = node.loc.end;
78
79 errors = errors.filter(function(error) {
80 const errorLoc = error[1];
81
82 if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
83 if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) {
84 return false;
85 }
86 }
87 return true;
88 });
89 }
90
91 /**
92 * Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
93 * @param {ASTNode} node to check for matching errors.
94 * @returns {void}
95 * @private
96 */
97 function removeInvalidNodeErrorsInIdentifierOrLiteral(node) {
98 const shouldCheckStrings = skipStrings && (typeof node.value === "string");
99 const shouldCheckRegExps = skipRegExps && (node.value instanceof RegExp);
100
101 if (shouldCheckStrings || shouldCheckRegExps) {
102
103 // If we have irregular characters remove them from the errors list
104 if (ALL_IRREGULARS.test(node.raw)) {
105 removeWhitespaceError(node);
106 }
107 }
108 }
109
110 /**
111 * Checks template string literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
112 * @param {ASTNode} node to check for matching errors.
113 * @returns {void}
114 * @private
115 */
116 function removeInvalidNodeErrorsInTemplateLiteral(node) {
117 if (typeof node.value.raw === "string") {
118 if (ALL_IRREGULARS.test(node.value.raw)) {
119 removeWhitespaceError(node);
120 }
121 }
122 }
123
124 /**
125 * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
126 * @param {ASTNode} node to check for matching errors.
127 * @returns {void}
128 * @private
129 */
130 function removeInvalidNodeErrorsInComment(node) {
131 if (ALL_IRREGULARS.test(node.value)) {
132 removeWhitespaceError(node);
133 }
134 }
135
136 /**
137 * Checks the program source for irregular whitespace
138 * @param {ASTNode} node The program node
139 * @returns {void}
140 * @private
141 */
142 function checkForIrregularWhitespace(node) {
143 const sourceLines = sourceCode.lines;
144
145 sourceLines.forEach(function(sourceLine, lineIndex) {
146 const lineNumber = lineIndex + 1;
147 let match;
148
149 while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
150 const location = {
151 line: lineNumber,
152 column: match.index
153 };
154
155 errors.push([node, location, "Irregular whitespace not allowed."]);
156 }
157 });
158 }
159
160 /**
161 * Checks the program source for irregular line terminators
162 * @param {ASTNode} node The program node
163 * @returns {void}
164 * @private
165 */
166 function checkForIrregularLineTerminators(node) {
167 const source = sourceCode.getText(),
168 sourceLines = sourceCode.lines,
169 linebreaks = source.match(LINE_BREAK);
170 let lastLineIndex = -1,
171 match;
172
173 while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
174 const lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
175 const location = {
176 line: lineIndex + 1,
177 column: sourceLines[lineIndex].length
178 };
179
180 errors.push([node, location, "Irregular whitespace not allowed."]);
181 lastLineIndex = lineIndex;
182 }
183 }
184
185 /**
186 * Stores a comment node (`LineComment` or `BlockComment`) for later stripping of errors within; a necessary deferring of processing to deal with top-of-file comments.
187 * @param {ASTNode} node The comment node
188 * @returns {void}
189 * @private
190 */
191 function rememberCommentNode(node) {
192 commentNodes.push(node);
193 }
194
195 /**
196 * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`.
197 * @returns {void}
198 * @private
199 */
200 function noop() {}
201
202 const nodes = {};
203
204 if (ALL_IRREGULARS.test(sourceCode.getText())) {
205 nodes.Program = function(node) {
206
207 /*
208 * As we can easily fire warnings for all white space issues with
209 * all the source its simpler to fire them here.
210 * This means we can check all the application code without having
211 * to worry about issues caused in the parser tokens.
212 * When writing this code also evaluating per node was missing out
213 * connecting tokens in some cases.
214 * We can later filter the errors when they are found to be not an
215 * issue in nodes we don't care about.
216 */
217
218 checkForIrregularWhitespace(node);
219 checkForIrregularLineTerminators(node);
220 };
221
222 nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral;
223 nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral;
224 nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop;
225 nodes.LineComment = skipComments ? rememberCommentNode : noop;
226 nodes.BlockComment = skipComments ? rememberCommentNode : noop;
227 nodes["Program:exit"] = function() {
228
229 if (skipComments) {
230
231 // First strip errors occurring in comment nodes. We have to do this post-`Program` to deal with top-of-file comments.
232 commentNodes.forEach(removeInvalidNodeErrorsInComment);
233 }
234
235 // If we have any errors remaining report on them
236 errors.forEach(function(error) {
237 context.report.apply(context, error);
238 });
239 };
240 } else {
241 nodes.Program = noop;
242 }
243
244 return nodes;
245 }
246};