diff --git a/README.md b/README.md index 71ed7bc3..bfc0fb3d 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ playground for learning and exploring. * **[MD043](doc/Rules.md#md043)** *required-headings/required-headers* - Required heading structure * **[MD044](doc/Rules.md#md044)** *proper-names* - Proper names should have the correct capitalization * **[MD045](doc/Rules.md#md045)** *no-alt-text* - Images should have alternate text (alt text) +* **[MD046](doc/Rules.md#md046)** *new-line-eof* - New lines at the end of file See [Rules.md](doc/Rules.md) for more details. @@ -101,7 +102,7 @@ Tags group related rules and can be used to enable/disable multiple rules at onc * **accessibility** - MD045 * **atx** - MD018, MD019 * **atx_closed** - MD020, MD021 -* **blank_lines** - MD012, MD022, MD031, MD032 +* **blank_lines** - MD012, MD022, MD031, MD032, MD046 * **blockquote** - MD027, MD028 * **bullet** - MD004, MD005, MD006, MD007, MD032 * **code** - MD014, MD031, MD038, MD040 diff --git a/doc/Rules.md b/doc/Rules.md index 6aae1816..3b5ce415 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -1544,3 +1544,30 @@ Or with reference syntax as: Guidance for writing alternate text is available from the [W3C](https://www.w3.org/WAI/alt/), [Wikipedia](https://en.wikipedia.org/wiki/Alt_attribute), and [other locations](https://www.phase2technology.com/blog/no-more-excuses-definitive-guide-alt-text-field). + + + +## MD046 - New lines at the end of file + +Tags: blank_lines + +Aliases: new-line-eof + +This rule is triggered when there is no new line at the end of the file. + +Example that triggers the rule: + +```markdown +# File ending without new line + +This file ends without new line. +``` + +To fix the violation, add new line at the end of the file: + +```markdown +# File ending with new line + +This file ends with new line. + +``` diff --git a/lib/md046.js b/lib/md046.js new file mode 100644 index 00000000..17812573 --- /dev/null +++ b/lib/md046.js @@ -0,0 +1,22 @@ +// @ts-check + +"use strict"; + +const shared = require("./shared"); +const { isBlankLine } = shared; + +module.exports = { + "names": [ "MD046", "new-line-eof" ], + "description": "New lines at the end of file", + "tags": [ "blank_lines" ], + "function": function rule(params, onError) { + const lastLineNumber = params.lines.length; + const lastLine = params.lines[lastLineNumber - 1]; + if (!isBlankLine(lastLine)) { + onError({ + "lineNumber": lastLineNumber, + "detail": "file does not end with new line" + }); + } + } +}; diff --git a/lib/rules.js b/lib/rules.js index 33924106..8f78449d 100644 --- a/lib/rules.js +++ b/lib/rules.js @@ -48,7 +48,8 @@ const rules = [ require("./md042"), require("./md043"), require("./md044"), - require("./md045") + require("./md045"), + require("./md046") ]; rules.forEach((rule) => { const name = rule.names[0].toLowerCase(); diff --git a/schema/markdownlint-config-schema.json b/schema/markdownlint-config-schema.json index a68c84e7..e6d7b77d 100644 --- a/schema/markdownlint-config-schema.json +++ b/schema/markdownlint-config-schema.json @@ -1255,6 +1255,16 @@ "type": "boolean", "default": true }, + "MD046": { + "description": "MD046/new-line-eof - New lines at the end of file", + "type": "boolean", + "default": true + }, + "new-line-eof": { + "description": "MD046/new-line-eof - New lines at the end of file", + "type": "boolean", + "default": true + }, "headings": { "description": "headings - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043", "type": "boolean", diff --git a/test/atx_heading_spacing.md b/test/atx_heading_spacing.md index 58443724..e668fa62 100644 --- a/test/atx_heading_spacing.md +++ b/test/atx_heading_spacing.md @@ -2,4 +2,4 @@ ## Heading 2 {MD019} -## Heading 3 {MD019} \ No newline at end of file +## Heading 3 {MD019} diff --git a/test/bulleted_list_4_space_indent.md b/test/bulleted_list_4_space_indent.md index 1c5e75db..8079d913 100644 --- a/test/bulleted_list_4_space_indent.md +++ b/test/bulleted_list_4_space_indent.md @@ -1,3 +1,3 @@ * Test X * Test Y {MD007} - * Test Z {MD007} \ No newline at end of file + * Test Z {MD007} diff --git a/test/bulleted_list_not_at_beginning_of_line.md b/test/bulleted_list_not_at_beginning_of_line.md index 84bd0ba9..3a735f1b 100644 --- a/test/bulleted_list_not_at_beginning_of_line.md +++ b/test/bulleted_list_not_at_beginning_of_line.md @@ -11,4 +11,4 @@ Some text Some more text * Item {MD006} - * Item \ No newline at end of file + * Item diff --git a/test/consecutive_blank_lines.md b/test/consecutive_blank_lines.md index 9d403503..95fde905 100644 --- a/test/consecutive_blank_lines.md +++ b/test/consecutive_blank_lines.md @@ -8,4 +8,4 @@ Some text {MD012:3} with two blank lines in it -Some more text \ No newline at end of file +Some more text diff --git a/test/consistent_bullet_styles_asterisk.md b/test/consistent_bullet_styles_asterisk.md index e8ccdee3..ced4b371 100644 --- a/test/consistent_bullet_styles_asterisk.md +++ b/test/consistent_bullet_styles_asterisk.md @@ -1,3 +1,3 @@ * Item * Item - * Item \ No newline at end of file + * Item diff --git a/test/consistent_bullet_styles_dash.md b/test/consistent_bullet_styles_dash.md index b6c50290..2d89e193 100644 --- a/test/consistent_bullet_styles_dash.md +++ b/test/consistent_bullet_styles_dash.md @@ -1,3 +1,3 @@ - Item - Item - - Item \ No newline at end of file + - Item diff --git a/test/consistent_bullet_styles_plus.md b/test/consistent_bullet_styles_plus.md index 7930238c..8f4f5208 100644 --- a/test/consistent_bullet_styles_plus.md +++ b/test/consistent_bullet_styles_plus.md @@ -1,3 +1,3 @@ + Item + Item - + Item \ No newline at end of file + + Item diff --git a/test/detailed-results-MD030-warning-message.md b/test/detailed-results-MD030-warning-message.md index b7760bd1..9f69121c 100644 --- a/test/detailed-results-MD030-warning-message.md +++ b/test/detailed-results-MD030-warning-message.md @@ -10,4 +10,4 @@ - a -1. a \ No newline at end of file +1. a diff --git a/test/fenced_code_without_blank_lines.md b/test/fenced_code_without_blank_lines.md index b4cf01a1..248d4a7c 100644 --- a/test/fenced_code_without_blank_lines.md +++ b/test/fenced_code_without_blank_lines.md @@ -39,4 +39,4 @@ text ``` code at end of file without newline -``` \ No newline at end of file +``` {MD046} \ No newline at end of file diff --git a/test/first_heading_bad_atx.md b/test/first_heading_bad_atx.md index 37f21480..318140e3 100644 --- a/test/first_heading_bad_atx.md +++ b/test/first_heading_bad_atx.md @@ -1 +1 @@ -## Heading \ No newline at end of file +## Heading diff --git a/test/first_heading_bad_setext.md b/test/first_heading_bad_setext.md index c9ebf7c5..b9e54368 100644 --- a/test/first_heading_bad_setext.md +++ b/test/first_heading_bad_setext.md @@ -1,2 +1,2 @@ Heading -------- \ No newline at end of file +------- diff --git a/test/first_heading_good_atx.md b/test/first_heading_good_atx.md index d9b3ffed..7c1f8312 100644 --- a/test/first_heading_good_atx.md +++ b/test/first_heading_good_atx.md @@ -1 +1 @@ -# Heading \ No newline at end of file +# Heading diff --git a/test/first_heading_good_setext.md b/test/first_heading_good_setext.md index 46ccb11b..5c4577b4 100644 --- a/test/first_heading_good_setext.md +++ b/test/first_heading_good_setext.md @@ -1,2 +1,2 @@ Heading -======= \ No newline at end of file +======= diff --git a/test/heading_duplicate_content.md b/test/heading_duplicate_content.md index 662aaa6f..eeb83bde 100644 --- a/test/heading_duplicate_content.md +++ b/test/heading_duplicate_content.md @@ -8,4 +8,4 @@ ## Heading 3 -{MD024:5} {MD024:7} \ No newline at end of file +{MD024:5} {MD024:7} diff --git a/test/heading_multiple_toplevel.md b/test/heading_multiple_toplevel.md index fa6467aa..3ae67562 100644 --- a/test/heading_multiple_toplevel.md +++ b/test/heading_multiple_toplevel.md @@ -1,3 +1,3 @@ # Heading 1 -# Heading 2 {MD025} \ No newline at end of file +# Heading 2 {MD025} diff --git a/test/heading_mutliple_h1_no_toplevel.md b/test/heading_mutliple_h1_no_toplevel.md index a8453007..4ccb03a1 100644 --- a/test/heading_mutliple_h1_no_toplevel.md +++ b/test/heading_mutliple_h1_no_toplevel.md @@ -2,4 +2,4 @@ Some introductory text # Heading 1 -# Heading 2 \ No newline at end of file +# Heading 2 diff --git a/test/heading_trailing_punctuation.md b/test/heading_trailing_punctuation.md index 81d4fabf..3aedc267 100644 --- a/test/heading_trailing_punctuation.md +++ b/test/heading_trailing_punctuation.md @@ -8,4 +8,4 @@ ## Heading 5 {MD026}; -## Heading 6 {MD026}? \ No newline at end of file +## Heading 6 {MD026}? diff --git a/test/headings_bad.md b/test/headings_bad.md index 1fa4b013..dc5ba916 100644 --- a/test/headings_bad.md +++ b/test/headings_bad.md @@ -4,4 +4,4 @@ ## Heading 2 -#### Heading 4 {MD001} \ No newline at end of file +#### Heading 4 {MD001} diff --git a/test/headings_good.md b/test/headings_good.md index f2576043..c9aaa2be 100644 --- a/test/headings_good.md +++ b/test/headings_good.md @@ -2,4 +2,4 @@ ## Heading 2 -## Heading 3 \ No newline at end of file +## Heading 3 diff --git a/test/headings_surrounding_space_setext.md b/test/headings_surrounding_space_setext.md index 8aabebc6..b75cd0f9 100644 --- a/test/headings_surrounding_space_setext.md +++ b/test/headings_surrounding_space_setext.md @@ -12,4 +12,4 @@ Heading 4 Some text Heading 5 ---------- \ No newline at end of file +--------- diff --git a/test/inconsistent_bullet_indent_same_level.md b/test/inconsistent_bullet_indent_same_level.md index 4679c876..7851e6c8 100644 --- a/test/inconsistent_bullet_indent_same_level.md +++ b/test/inconsistent_bullet_indent_same_level.md @@ -1,4 +1,4 @@ * Item * Item {MD007} * Item {MD005} - * Item {MD007} \ No newline at end of file + * Item {MD007} diff --git a/test/inconsistent_bullet_styles_asterisk.md b/test/inconsistent_bullet_styles_asterisk.md index 9825cacb..795b16b9 100644 --- a/test/inconsistent_bullet_styles_asterisk.md +++ b/test/inconsistent_bullet_styles_asterisk.md @@ -1,3 +1,3 @@ * Item + Item {MD004} - - Item {MD004} \ No newline at end of file + - Item {MD004} diff --git a/test/inconsistent_bullet_styles_dash.md b/test/inconsistent_bullet_styles_dash.md index 4f3b756e..b9577773 100644 --- a/test/inconsistent_bullet_styles_dash.md +++ b/test/inconsistent_bullet_styles_dash.md @@ -1,3 +1,3 @@ - Item * Item {MD004} - + Item {MD004} \ No newline at end of file + + Item {MD004} diff --git a/test/inconsistent_bullet_styles_plus.md b/test/inconsistent_bullet_styles_plus.md index bd4799f8..e3e3ea5d 100644 --- a/test/inconsistent_bullet_styles_plus.md +++ b/test/inconsistent_bullet_styles_plus.md @@ -1,3 +1,3 @@ + Item * Item {MD004} - - Item {MD004} \ No newline at end of file + - Item {MD004} diff --git a/test/lists_without_blank_lines.md b/test/lists_without_blank_lines.md index 7f370d48..2725b22a 100644 --- a/test/lists_without_blank_lines.md +++ b/test/lists_without_blank_lines.md @@ -72,4 +72,4 @@ code text -* list (on last line without newline) \ No newline at end of file +* list (on last line without newline) {MD046} \ No newline at end of file diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 742e8cce..0c696667 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -248,7 +248,7 @@ module.exports.resultFormattingV1 = function resultFormattingV1(test) { const options = { "strings": { "truncate": - "# Multiple spaces inside hashes on closed atx style heading #" + "# Multiple spaces inside hashes on closed atx style heading #\n" }, "files": [ "./test/atx_heading_spacing.md", @@ -350,7 +350,7 @@ module.exports.resultFormattingV2 = function resultFormattingV2(test) { const options = { "strings": { "truncate": - "# Multiple spaces inside hashes on closed atx style heading #" + "# Multiple spaces inside hashes on closed atx style heading #\n" }, "files": [ "./test/atx_heading_spacing.md", @@ -446,14 +446,14 @@ module.exports.stringInputLineEndings = function stringInputLineEndings(test) { test.expect(2); const options = { "strings": { - "cr": "One\rTwo\r#Three", - "lf": "One\nTwo\n#Three", - "crlf": "One\r\nTwo\r\n#Three", - "mixed": "One\rTwo\n#Three", - "crnel": "One\r\u0085Two\r\u0085#Three", - "snl": "One\u2424Two\u2424#Three", - "lsep": "One\u2028Two\u2028#Three", - "nel": "One\u0085Two\u0085#Three" + "cr": "One\rTwo\r#Three\n", + "lf": "One\nTwo\n#Three\n", + "crlf": "One\r\nTwo\r\n#Three\n", + "mixed": "One\rTwo\n#Three\n", + "crnel": "One\r\u0085Two\r\u0085#Three\n", + "snl": "One\u2424Two\u2424#Three\n", + "lsep": "One\u2028Two\u2028#Three\n", + "nel": "One\u0085Two\u0085#Three\n" }, "config": defaultConfig, "resultVersion": 0 @@ -913,7 +913,7 @@ module.exports.noInlineConfig = function noInlineConfig(test) { "", "", "", - "\tTab" + "\tTab\n" ].join("\n") }, "noInlineConfig": true, @@ -1089,7 +1089,7 @@ module.exports.missingStringValue = function missingStringValue(test) { }; module.exports.readme = function readme(test) { - test.expect(109); + test.expect(111); const tagToRules = {}; rules.forEach(function forRule(rule) { rule.tags.forEach(function forTag(tag) { @@ -1155,7 +1155,7 @@ module.exports.readme = function readme(test) { }; module.exports.doc = function doc(test) { - test.expect(312); + test.expect(319); fs.readFile("doc/Rules.md", shared.utf8Encoding, function readFile(err, contents) { test.ifError(err); @@ -1883,7 +1883,7 @@ module.exports.configBadHybridSync = function configBadHybridSync(test) { module.exports.allBuiltInRulesHaveValidUrl = function allBuiltInRulesHaveValidUrl(test) { - test.expect(123); + test.expect(126); rules.forEach(function forRule(rule) { test.ok(rule.information); test.ok(Object.getPrototypeOf(rule.information) === URL.prototype); @@ -2240,7 +2240,7 @@ module.exports.customRulesNpmPackage = function customRulesNpmPackage(test) { const options = { "customRules": [ require("./rules/npm") ], "strings": { - "string": "# Text\n\n---\n\nText" + "string": "# Text\n\n---\n\nText\n" }, "resultVersion": 0 }; @@ -2539,7 +2539,7 @@ module.exports.customRulesOnErrorLazy = function customRulesOnErrorLazy(test) { } ], "strings": { - "string": "# Heading" + "string": "# Heading\n" } }; markdownlint(options, function callback(err, actualResult) { @@ -2626,7 +2626,7 @@ module.exports.markdownItPluginsSingle = test.expect(2); markdownlint({ "strings": { - "string": "# Heading\n\nText [ link ](https://example.com)" + "string": "# Heading\n\nText [ link ](https://example.com)\n" }, "markdownItPlugins": [ [ @@ -2651,7 +2651,7 @@ module.exports.markdownItPluginsMultiple = test.expect(4); markdownlint({ "strings": { - "string": "# Heading\n\nText H~2~0 text 29^th^ text" + "string": "# Heading\n\nText H~2~0 text 29^th^ text\n" }, "markdownItPlugins": [ [ pluginSub ], @@ -2704,7 +2704,7 @@ $$ 1 $$$$ 2 -$$` +$$\n` }, "markdownItPlugins": [ [ pluginKatex ] ], "resultVersion": 0 @@ -2719,3 +2719,25 @@ $$` test.done(); }); }; + +module.exports.newLineAtTheEndOfFile = + function newLineAtTheEndOfFile(test) { + test.expect(2); + markdownlint({ + "files": "./test/new_line_EOF.md" + }, function callback(err, actual) { + test.ifError(err); + const expected = { "./test/new_line_EOF.md": + [ + { "lineNumber": 3, + "ruleNames": [ "MD046", "new-line-eof" ], + "ruleDescription": "New lines at the end of file", + "ruleInformation": `${homepage}/blob/v${version}/doc/Rules.md#md046`, + "errorDetail": "file does not end with new line", + "errorContext": null, + "errorRange": null } + ] }; + test.deepEqual(actual, expected, "Unexpected issues."); + test.done(); + }); + }; diff --git a/test/md022-line-number-out-of-range.md b/test/md022-line-number-out-of-range.md index 7489accb..4ba28051 100644 --- a/test/md022-line-number-out-of-range.md +++ b/test/md022-line-number-out-of-range.md @@ -1 +1 @@ --- \ No newline at end of file +-- diff --git a/test/mixed_heading_types_atx.md b/test/mixed_heading_types_atx.md index d94af2a6..b82f5462 100644 --- a/test/mixed_heading_types_atx.md +++ b/test/mixed_heading_types_atx.md @@ -3,4 +3,4 @@ ## Heading 2 {MD003} ## Heading 3 {MD003} ------------------ \ No newline at end of file +----------------- diff --git a/test/mixed_heading_types_atx_closed.md b/test/mixed_heading_types_atx_closed.md index cc29cf88..e786dd65 100644 --- a/test/mixed_heading_types_atx_closed.md +++ b/test/mixed_heading_types_atx_closed.md @@ -3,4 +3,4 @@ ## Heading 2 {MD003} Heading 3 {MD003} ------------------ \ No newline at end of file +----------------- diff --git a/test/mixed_heading_types_setext.md b/test/mixed_heading_types_setext.md index bcf67836..47515ac8 100644 --- a/test/mixed_heading_types_setext.md +++ b/test/mixed_heading_types_setext.md @@ -3,4 +3,4 @@ Heading 1 ## Heading 2 {MD003} -## Heading 3 {MD003} ## \ No newline at end of file +## Heading 3 {MD003} ## diff --git a/test/new_line_EOF.json b/test/new_line_EOF.json new file mode 100644 index 00000000..8ab95915 --- /dev/null +++ b/test/new_line_EOF.json @@ -0,0 +1,4 @@ +{ + "default": true, + "MD046": false +} diff --git a/test/new_line_EOF.md b/test/new_line_EOF.md new file mode 100644 index 00000000..01488118 --- /dev/null +++ b/test/new_line_EOF.md @@ -0,0 +1,3 @@ +# File ending without new line + +This file ends without new line. \ No newline at end of file diff --git a/test/required-headings-missing-last.md b/test/required-headings-missing-last.md index dd4ceb8e..b81f72b8 100644 --- a/test/required-headings-missing-last.md +++ b/test/required-headings-missing-last.md @@ -4,4 +4,4 @@ One Two --- -{MD043} \ No newline at end of file +{MD043} {MD046} \ No newline at end of file diff --git a/test/whitespace_issues.md b/test/whitespace_issues.md index 8f4209eb..7c50dfb2 100644 --- a/test/whitespace_issues.md +++ b/test/whitespace_issues.md @@ -1,3 +1,3 @@ Some text {MD009} Some more text {MD010} -Some more text \ No newline at end of file +Some more text