UNPKG

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