no-trailing-spaces.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /**
  2. * @fileoverview Disallow trailing spaces at the end of lines.
  3. * @author Nodeca Team <https://github.com/nodeca>
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Typedefs
  9. //------------------------------------------------------------------------------
  10. /**
  11. * @import { SourceLocation, SourceRange } from "@eslint/core";
  12. */
  13. //------------------------------------------------------------------------------
  14. // Requirements
  15. //------------------------------------------------------------------------------
  16. const astUtils = require("./utils/ast-utils");
  17. //------------------------------------------------------------------------------
  18. // Rule Definition
  19. //------------------------------------------------------------------------------
  20. /** @type {import('../types').Rule.RuleModule} */
  21. module.exports = {
  22. meta: {
  23. deprecated: {
  24. message: "Formatting rules are being moved out of ESLint core.",
  25. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  26. deprecatedSince: "8.53.0",
  27. availableUntil: "11.0.0",
  28. replacedBy: [
  29. {
  30. message:
  31. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  32. url: "https://eslint.style/guide/migration",
  33. plugin: {
  34. name: "@stylistic/eslint-plugin",
  35. url: "https://eslint.style",
  36. },
  37. rule: {
  38. name: "no-trailing-spaces",
  39. url: "https://eslint.style/rules/no-trailing-spaces",
  40. },
  41. },
  42. ],
  43. },
  44. type: "layout",
  45. docs: {
  46. description: "Disallow trailing whitespace at the end of lines",
  47. recommended: false,
  48. url: "https://eslint.org/docs/latest/rules/no-trailing-spaces",
  49. },
  50. fixable: "whitespace",
  51. schema: [
  52. {
  53. type: "object",
  54. properties: {
  55. skipBlankLines: {
  56. type: "boolean",
  57. default: false,
  58. },
  59. ignoreComments: {
  60. type: "boolean",
  61. default: false,
  62. },
  63. },
  64. additionalProperties: false,
  65. },
  66. ],
  67. messages: {
  68. trailingSpace: "Trailing spaces not allowed.",
  69. },
  70. },
  71. create(context) {
  72. const sourceCode = context.sourceCode;
  73. const BLANK_CLASS =
  74. "[ \t\u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u3000]",
  75. SKIP_BLANK = `^${BLANK_CLASS}*$`,
  76. NONBLANK = `${BLANK_CLASS}+$`;
  77. const options = context.options[0] || {},
  78. skipBlankLines = options.skipBlankLines || false,
  79. ignoreComments = options.ignoreComments || false;
  80. /**
  81. * Report the error message
  82. * @param {ASTNode} node node to report
  83. * @param {SourceLocation} location range information
  84. * @param {SourceRange} fixRange Range based on the whole program
  85. * @returns {void}
  86. */
  87. function report(node, location, fixRange) {
  88. /*
  89. * Passing node is a bit dirty, because message data will contain big
  90. * text in `source`. But... who cares :) ?
  91. * One more kludge will not make worse the bloody wizardry of this
  92. * plugin.
  93. */
  94. context.report({
  95. node,
  96. loc: location,
  97. messageId: "trailingSpace",
  98. fix(fixer) {
  99. return fixer.removeRange(fixRange);
  100. },
  101. });
  102. }
  103. /**
  104. * Given a list of comment nodes, return the line numbers for those comments.
  105. * @param {Array} comments An array of comment nodes.
  106. * @returns {number[]} An array of line numbers containing comments.
  107. */
  108. function getCommentLineNumbers(comments) {
  109. const lines = new Set();
  110. comments.forEach(comment => {
  111. const endLine =
  112. comment.type === "Block"
  113. ? comment.loc.end.line - 1
  114. : comment.loc.end.line;
  115. for (let i = comment.loc.start.line; i <= endLine; i++) {
  116. lines.add(i);
  117. }
  118. });
  119. return lines;
  120. }
  121. //--------------------------------------------------------------------------
  122. // Public
  123. //--------------------------------------------------------------------------
  124. return {
  125. Program: function checkTrailingSpaces(node) {
  126. /*
  127. * Let's hack. Since Espree does not return whitespace nodes,
  128. * fetch the source code and do matching via regexps.
  129. */
  130. const re = new RegExp(NONBLANK, "u"),
  131. skipMatch = new RegExp(SKIP_BLANK, "u"),
  132. lines = sourceCode.lines,
  133. linebreaks = sourceCode
  134. .getText()
  135. .match(astUtils.createGlobalLinebreakMatcher()),
  136. comments = sourceCode.getAllComments(),
  137. commentLineNumbers = getCommentLineNumbers(comments);
  138. let totalLength = 0;
  139. for (let i = 0, ii = lines.length; i < ii; i++) {
  140. const lineNumber = i + 1;
  141. /*
  142. * Always add linebreak length to line length to accommodate for line break (\n or \r\n)
  143. * Because during the fix time they also reserve one spot in the array.
  144. * Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF)
  145. */
  146. const linebreakLength =
  147. linebreaks && linebreaks[i] ? linebreaks[i].length : 1;
  148. const lineLength = lines[i].length + linebreakLength;
  149. const matches = re.exec(lines[i]);
  150. if (matches) {
  151. const location = {
  152. start: {
  153. line: lineNumber,
  154. column: matches.index,
  155. },
  156. end: {
  157. line: lineNumber,
  158. column: lineLength - linebreakLength,
  159. },
  160. };
  161. const rangeStart = totalLength + location.start.column;
  162. const rangeEnd = totalLength + location.end.column;
  163. const containingNode =
  164. sourceCode.getNodeByRangeIndex(rangeStart);
  165. if (
  166. containingNode &&
  167. containingNode.type === "TemplateElement" &&
  168. rangeStart > containingNode.parent.range[0] &&
  169. rangeEnd < containingNode.parent.range[1]
  170. ) {
  171. totalLength += lineLength;
  172. continue;
  173. }
  174. /*
  175. * If the line has only whitespace, and skipBlankLines
  176. * is true, don't report it
  177. */
  178. if (skipBlankLines && skipMatch.test(lines[i])) {
  179. totalLength += lineLength;
  180. continue;
  181. }
  182. const fixRange = [rangeStart, rangeEnd];
  183. if (
  184. !ignoreComments ||
  185. !commentLineNumbers.has(lineNumber)
  186. ) {
  187. report(node, location, fixRange);
  188. }
  189. }
  190. totalLength += lineLength;
  191. }
  192. },
  193. };
  194. },
  195. };