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