no-trailing-spaces.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("../ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Rule Definition
  12. //------------------------------------------------------------------------------
  13. module.exports = {
  14. meta: {
  15. docs: {
  16. description: "disallow trailing whitespace at the end of lines",
  17. category: "Stylistic Issues",
  18. recommended: false,
  19. url: "https://eslint.org/docs/rules/no-trailing-spaces"
  20. },
  21. fixable: "whitespace",
  22. schema: [
  23. {
  24. type: "object",
  25. properties: {
  26. skipBlankLines: {
  27. type: "boolean"
  28. },
  29. ignoreComments: {
  30. type: "boolean"
  31. }
  32. },
  33. additionalProperties: false
  34. }
  35. ]
  36. },
  37. create(context) {
  38. const sourceCode = context.getSourceCode();
  39. const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]",
  40. SKIP_BLANK = `^${BLANK_CLASS}*$`,
  41. NONBLANK = `${BLANK_CLASS}+$`;
  42. const options = context.options[0] || {},
  43. skipBlankLines = options.skipBlankLines || false,
  44. ignoreComments = typeof options.ignoreComments === "boolean" && options.ignoreComments;
  45. /**
  46. * Report the error message
  47. * @param {ASTNode} node node to report
  48. * @param {int[]} location range information
  49. * @param {int[]} fixRange Range based on the whole program
  50. * @returns {void}
  51. */
  52. function report(node, location, fixRange) {
  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. * Given a list of comment nodes, return the line numbers for those comments.
  70. * @param {Array} comments An array of comment nodes.
  71. * @returns {number[]} An array of line numbers containing comments.
  72. */
  73. function getCommentLineNumbers(comments) {
  74. const lines = new Set();
  75. comments.forEach(comment => {
  76. for (let i = comment.loc.start.line; i <= comment.loc.end.line; i++) {
  77. lines.add(i);
  78. }
  79. });
  80. return lines;
  81. }
  82. //--------------------------------------------------------------------------
  83. // Public
  84. //--------------------------------------------------------------------------
  85. return {
  86. Program: function checkTrailingSpaces(node) {
  87. /*
  88. * Let's hack. Since Espree does not return whitespace nodes,
  89. * fetch the source code and do matching via regexps.
  90. */
  91. const re = new RegExp(NONBLANK),
  92. skipMatch = new RegExp(SKIP_BLANK),
  93. lines = sourceCode.lines,
  94. linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher()),
  95. comments = sourceCode.getAllComments(),
  96. commentLineNumbers = getCommentLineNumbers(comments);
  97. let totalLength = 0,
  98. fixRange = [];
  99. for (let i = 0, ii = lines.length; i < ii; i++) {
  100. const matches = re.exec(lines[i]);
  101. /*
  102. * Always add linebreak length to line length to accommodate for line break (\n or \r\n)
  103. * Because during the fix time they also reserve one spot in the array.
  104. * Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF)
  105. */
  106. const linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1;
  107. const lineLength = lines[i].length + linebreakLength;
  108. if (matches) {
  109. const location = {
  110. line: i + 1,
  111. column: matches.index
  112. };
  113. const rangeStart = totalLength + location.column;
  114. const rangeEnd = totalLength + lineLength - linebreakLength;
  115. const containingNode = sourceCode.getNodeByRangeIndex(rangeStart);
  116. if (containingNode && containingNode.type === "TemplateElement" &&
  117. rangeStart > containingNode.parent.range[0] &&
  118. rangeEnd < containingNode.parent.range[1]) {
  119. totalLength += lineLength;
  120. continue;
  121. }
  122. /*
  123. * If the line has only whitespace, and skipBlankLines
  124. * is true, don't report it
  125. */
  126. if (skipBlankLines && skipMatch.test(lines[i])) {
  127. totalLength += lineLength;
  128. continue;
  129. }
  130. fixRange = [rangeStart, rangeEnd];
  131. if (!ignoreComments || !commentLineNumbers.has(location.line)) {
  132. report(node, location, fixRange);
  133. }
  134. }
  135. totalLength += lineLength;
  136. }
  137. }
  138. };
  139. }
  140. };