UNPKG

6.79 kBJavaScriptView Raw
1/**
2 * @fileoverview Disallow trailing spaces at the end of lines.
3 * @author Nodeca Team <https://github.com/nodeca>
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const astUtils = require("./utils/ast-utils");
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17module.exports = {
18 meta: {
19 type: "layout",
20
21 docs: {
22 description: "disallow trailing whitespace at the end of lines",
23 category: "Stylistic Issues",
24 recommended: false,
25 url: "https://eslint.org/docs/rules/no-trailing-spaces"
26 },
27
28 fixable: "whitespace",
29
30 schema: [
31 {
32 type: "object",
33 properties: {
34 skipBlankLines: {
35 type: "boolean",
36 default: false
37 },
38 ignoreComments: {
39 type: "boolean",
40 default: false
41 }
42 },
43 additionalProperties: false
44 }
45 ],
46
47 messages: {
48 trailingSpace: "Trailing spaces not allowed."
49 }
50 },
51
52 create(context) {
53 const sourceCode = context.getSourceCode();
54
55 const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]",
56 SKIP_BLANK = `^${BLANK_CLASS}*$`,
57 NONBLANK = `${BLANK_CLASS}+$`;
58
59 const options = context.options[0] || {},
60 skipBlankLines = options.skipBlankLines || false,
61 ignoreComments = options.ignoreComments || false;
62
63 /**
64 * Report the error message
65 * @param {ASTNode} node node to report
66 * @param {int[]} location range information
67 * @param {int[]} fixRange Range based on the whole program
68 * @returns {void}
69 */
70 function report(node, location, fixRange) {
71
72 /*
73 * Passing node is a bit dirty, because message data will contain big
74 * text in `source`. But... who cares :) ?
75 * One more kludge will not make worse the bloody wizardry of this
76 * plugin.
77 */
78 context.report({
79 node,
80 loc: location,
81 messageId: "trailingSpace",
82 fix(fixer) {
83 return fixer.removeRange(fixRange);
84 }
85 });
86 }
87
88 /**
89 * Given a list of comment nodes, return the line numbers for those comments.
90 * @param {Array} comments An array of comment nodes.
91 * @returns {number[]} An array of line numbers containing comments.
92 */
93 function getCommentLineNumbers(comments) {
94 const lines = new Set();
95
96 comments.forEach(comment => {
97 const endLine = comment.type === "Block"
98 ? comment.loc.end.line - 1
99 : comment.loc.end.line;
100
101 for (let i = comment.loc.start.line; i <= endLine; i++) {
102 lines.add(i);
103 }
104 });
105
106 return lines;
107 }
108
109 //--------------------------------------------------------------------------
110 // Public
111 //--------------------------------------------------------------------------
112
113 return {
114
115 Program: function checkTrailingSpaces(node) {
116
117 /*
118 * Let's hack. Since Espree does not return whitespace nodes,
119 * fetch the source code and do matching via regexps.
120 */
121
122 const re = new RegExp(NONBLANK, "u"),
123 skipMatch = new RegExp(SKIP_BLANK, "u"),
124 lines = sourceCode.lines,
125 linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher()),
126 comments = sourceCode.getAllComments(),
127 commentLineNumbers = getCommentLineNumbers(comments);
128
129 let totalLength = 0,
130 fixRange = [];
131
132 for (let i = 0, ii = lines.length; i < ii; i++) {
133 const lineNumber = i + 1;
134
135 /*
136 * Always add linebreak length to line length to accommodate for line break (\n or \r\n)
137 * Because during the fix time they also reserve one spot in the array.
138 * Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF)
139 */
140 const linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1;
141 const lineLength = lines[i].length + linebreakLength;
142
143 const matches = re.exec(lines[i]);
144
145 if (matches) {
146 const location = {
147 start: {
148 line: lineNumber,
149 column: matches.index
150 },
151 end: {
152 line: lineNumber,
153 column: lineLength - linebreakLength
154 }
155 };
156
157 const rangeStart = totalLength + location.start.column;
158 const rangeEnd = totalLength + location.end.column;
159 const containingNode = sourceCode.getNodeByRangeIndex(rangeStart);
160
161 if (containingNode && containingNode.type === "TemplateElement" &&
162 rangeStart > containingNode.parent.range[0] &&
163 rangeEnd < containingNode.parent.range[1]) {
164 totalLength += lineLength;
165 continue;
166 }
167
168 /*
169 * If the line has only whitespace, and skipBlankLines
170 * is true, don't report it
171 */
172 if (skipBlankLines && skipMatch.test(lines[i])) {
173 totalLength += lineLength;
174 continue;
175 }
176
177 fixRange = [rangeStart, rangeEnd];
178
179 if (!ignoreComments || !commentLineNumbers.has(lineNumber)) {
180 report(node, location, fixRange);
181 }
182 }
183
184 totalLength += lineLength;
185 }
186 }
187
188 };
189 }
190};