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