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