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