UNPKG

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