UNPKG

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