eol-last.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. /**
  2. * @fileoverview Require or disallow newline at the end of files
  3. * @author Nodeca Team <https://github.com/nodeca>
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. /** @type {import('../shared/types').Rule} */
  10. module.exports = {
  11. meta: {
  12. type: "layout",
  13. docs: {
  14. description: "Require or disallow newline at the end of files",
  15. recommended: false,
  16. url: "https://eslint.org/docs/rules/eol-last"
  17. },
  18. fixable: "whitespace",
  19. schema: [
  20. {
  21. enum: ["always", "never", "unix", "windows"]
  22. }
  23. ],
  24. messages: {
  25. missing: "Newline required at end of file but not found.",
  26. unexpected: "Newline not allowed at end of file."
  27. }
  28. },
  29. create(context) {
  30. //--------------------------------------------------------------------------
  31. // Public
  32. //--------------------------------------------------------------------------
  33. return {
  34. Program: function checkBadEOF(node) {
  35. const sourceCode = context.getSourceCode(),
  36. src = sourceCode.getText(),
  37. lastLine = sourceCode.lines[sourceCode.lines.length - 1],
  38. location = {
  39. column: lastLine.length,
  40. line: sourceCode.lines.length
  41. },
  42. LF = "\n",
  43. CRLF = `\r${LF}`,
  44. endsWithNewline = src.endsWith(LF);
  45. /*
  46. * Empty source is always valid: No content in file so we don't
  47. * need to lint for a newline on the last line of content.
  48. */
  49. if (!src.length) {
  50. return;
  51. }
  52. let mode = context.options[0] || "always",
  53. appendCRLF = false;
  54. if (mode === "unix") {
  55. // `"unix"` should behave exactly as `"always"`
  56. mode = "always";
  57. }
  58. if (mode === "windows") {
  59. // `"windows"` should behave exactly as `"always"`, but append CRLF in the fixer for backwards compatibility
  60. mode = "always";
  61. appendCRLF = true;
  62. }
  63. if (mode === "always" && !endsWithNewline) {
  64. // File is not newline-terminated, but should be
  65. context.report({
  66. node,
  67. loc: location,
  68. messageId: "missing",
  69. fix(fixer) {
  70. return fixer.insertTextAfterRange([0, src.length], appendCRLF ? CRLF : LF);
  71. }
  72. });
  73. } else if (mode === "never" && endsWithNewline) {
  74. const secondLastLine = sourceCode.lines[sourceCode.lines.length - 2];
  75. // File is newline-terminated, but shouldn't be
  76. context.report({
  77. node,
  78. loc: {
  79. start: { line: sourceCode.lines.length - 1, column: secondLastLine.length },
  80. end: { line: sourceCode.lines.length, column: 0 }
  81. },
  82. messageId: "unexpected",
  83. fix(fixer) {
  84. const finalEOLs = /(?:\r?\n)+$/u,
  85. match = finalEOLs.exec(sourceCode.text),
  86. start = match.index,
  87. end = sourceCode.text.length;
  88. return fixer.replaceTextRange([start, end], "");
  89. }
  90. });
  91. }
  92. }
  93. };
  94. }
  95. };