diff --git a/conf/eslint.json b/conf/eslint.json index fe1c4afb925..7a7bf19fea7 100755 --- a/conf/eslint.json +++ b/conf/eslint.json @@ -171,6 +171,7 @@ "key-spacing": "off", "keyword-spacing": "off", "linebreak-style": "off", + "line-comment-position": "off", "lines-around-comment": "off", "lines-around-directive": "off", "max-depth": "off", diff --git a/docs/rules/line-comment-position.md b/docs/rules/line-comment-position.md new file mode 100644 index 00000000000..8895f038099 --- /dev/null +++ b/docs/rules/line-comment-position.md @@ -0,0 +1,104 @@ +# enforce position of line comments (line-comment-position) + +Line comments can be positioned above or beside code. This rule helps teams maintain a consistent style. + +```js +// above comment +var foo = "bar"; // beside comment +``` + +## Rule Details + +This rule enforces consistent position of line comments. Block comments are not affected by this rule. By default, this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`, `falls through`. + + +## Options + +This rule takes one argument, which can be a string or an object. The string settings are the same as those of the `position` property (explained below). The object option has the following properties: + +### position + +The `position` option has two settings: + +* `above` (default) enforces line comments only above code, in its own line. +* `beside` enforces line comments only at the end of code lines. + +#### position: above + +Examples of **correct** code for the `{ "position": "above" }` option: + +```js +/*eslint line-comment-position: ["error", { "position": "above" }]*/ +// valid comment +1 + 1; +``` + + +Examples of **incorrect** code for the `{ "position": "above" }` option: + +```js +/*eslint line-comment-position: ["error", { "position": "above" }]*/ +1 + 1; // invalid comment +``` + +#### position: beside + +Examples of **correct** code for the `{ "position": "beside" }` option: + +```js +/*eslint line-comment-position: ["error", { "position": "beside" }]*/ +1 + 1; // valid comment +``` + + +Examples of **incorrect** code for the `{ "position": "above" }` option: + +```js +/*eslint line-comment-position: ["error", { "position": "above" }]*/ +// invalid comment +1 + 1; +``` + +### ignorePattern + +By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`, `falls through`. An alternative regular expression can be provided. + +Examples of **correct** code for the `ignorePattern` option: + +```js +/*eslint line-comment-position: ["error", { "ignorePattern": "pragma" }]*/ +1 + 1; // pragma valid comment +``` + +Examples of **incorrect** code for the `ignorePattern` option: + +```js +/*eslint line-comment-position: ["error", { "ignorePattern": "pragma" }]*/ +1 + 1; // invalid comment +``` + +### applyDefaultPatterns + +Default ignore patterns are applied even when `ignorePattern` is provided. If you want to omit default patterns, set this option to `false`. + +Examples of **correct** code for the `{ "applyDefaultPatterns": false }` option: + +```js +/*eslint line-comment-position: ["error", { "ignorePattern": "pragma", "applyDefaultPatterns": false }]*/ +1 + 1; // pragma valid comment +``` + +Examples of **incorrect** code for the `{ "applyDefaultPatterns": false }` option: + +```js +/*eslint line-comment-position: ["error", { "ignorePattern": "pragma", "applyDefaultPatterns": false }]*/ +1 + 1; // falls through +``` + +## When Not To Use It + +If you aren't concerned about having different line comment styles, then you can turn off this rule. + +## Compatibility + +**JSCS**: [validateCommentPosition](http://jscs.info/rule/validateCommentPosition) diff --git a/lib/rules/line-comment-position.js b/lib/rules/line-comment-position.js new file mode 100644 index 00000000000..cd4c9a9fd8a --- /dev/null +++ b/lib/rules/line-comment-position.js @@ -0,0 +1,101 @@ +/** + * @fileoverview Rule to enforce the position of line comments + * @author Alberto Rodríguez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: "enforce position of line comments", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + oneOf: [ + { + enum: ["above", "beside"] + }, + { + type: "object", + properties: { + position: { + enum: ["above", "beside"] + }, + ignorePattern: { + type: "string" + }, + applyDefaultPatterns: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + } + ] + }, + + create(context) { + const DEFAULT_IGNORE_PATTERN = "^\\s*(?:eslint|jshint\\s+|jslint\\s+|istanbul\\s+|globals?\\s+|exported\\s+|jscs|falls?\\s?through)"; + const options = context.options[0]; + + let above, + ignorePattern, + applyDefaultPatterns = true; + + if (!options || typeof option === "string") { + above = !options || options === "above"; + + } else { + above = options.position === "above"; + ignorePattern = options.ignorePattern; + applyDefaultPatterns = options.applyDefaultPatterns !== false; + } + + const defaultIgnoreRegExp = new RegExp(DEFAULT_IGNORE_PATTERN); + const customIgnoreRegExp = new RegExp(ignorePattern); + const sourceCode = context.getSourceCode(); + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + LineComment(node) { + if (applyDefaultPatterns && defaultIgnoreRegExp.test(node.value)) { + return; + } + + if (ignorePattern && customIgnoreRegExp.test(node.value)) { + return; + } + + const previous = sourceCode.getTokenOrCommentBefore(node); + const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line; + + if (above) { + if (isOnSameLine) { + context.report({ + node, + message: "Expected comment to be above code." + }); + } + } else { + if (!isOnSameLine) { + context.report({ + node, + message: "Expected comment to be beside code." + }); + } + } + } + }; + } +}; diff --git a/tests/lib/rules/line-comment-position.js b/tests/lib/rules/line-comment-position.js new file mode 100644 index 00000000000..034c05729a1 --- /dev/null +++ b/tests/lib/rules/line-comment-position.js @@ -0,0 +1,147 @@ +/** + * @fileoverview enforce position of line comments + * @author Alberto Rodríguez + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/line-comment-position"), + RuleTester = require("../../../lib/testers/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(); + +ruleTester.run("line-comment-position", rule, { + + valid: [ + "// valid comment\n1 + 1;", + "/* block comments are skipped */\n1 + 1;", + "1 + 1; /* block comments are skipped */", + "1 + 1; /* eslint eqeqeq: 'error' */", + "1 + 1; /* eslint-disable */", + "1 + 1; /* eslint-enable */", + "1 + 1; // eslint-disable-line", + "// eslint-disable-next-line\n1 + 1;", + "1 + 1; // global MY_GLOBAL, ANOTHER", + "1 + 1; // globals MY_GLOBAL: true", + "1 + 1; // exported MY_GLOBAL, ANOTHER", + "1 + 1; // fallthrough", + "1 + 1; // fall through", + "1 + 1; // falls through", + "1 + 1; // jslint vars: true", + "1 + 1; // jshint ignore:line", + "1 + 1; // istanbul ignore next", + { + code: "1 + 1; // linter excepted comment", + options: [{position: "above", ignorePattern: "linter" } ] + }, + { + code: "1 + 1; // valid comment", + options: ["beside"] + }, + { + code: "// jscs: disable\n1 + 1;", + options: ["beside"] + }, + { + code: "// jscs: enable\n1 + 1;", + options: ["beside"] + }, + { + code: "/* block comments are skipped */\n1 + 1;", + options: ["beside"] + }, + { + code: "/*block comment*/\n/*block comment*/\n1 + 1;", + options: ["beside"] + }, + { + code: "1 + 1; /* block comment are skipped */", + options: ["beside"] + }, + { + code: "1 + 1; // jshint strict: true", + options: ["beside"] + }, + { + code: "// pragma valid comment\n1 + 1;", + options: [{position: "beside", ignorePattern: "pragma|linter" } ] + } + ], + + invalid: [ + { + code: "1 + 1; // invalid comment", + errors: [{ + message: "Expected comment to be above code.", + type: "Line", + line: 1, + column: 8 + }] + }, + { + code: "1 + 1; // globalization is a word", + errors: [{ + message: "Expected comment to be above code.", + type: "Line", + line: 1, + column: 8 + }] + }, + { + code: "// jscs: disable\n1 + 1;", + options: [{ position: "beside", applyDefaultPatterns: false }], + errors: [{ + message: "Expected comment to be beside code.", + type: "Line", + line: 1, + column: 1 + }] + }, + { + code: "1 + 1; // mentioning falls through", + errors: [{ + message: "Expected comment to be above code.", + type: "Line", + line: 1, + column: 8 + }] + }, + { + code: "// invalid comment\n1 + 1;", + options: ["beside"], + errors: [{ + message: "Expected comment to be beside code.", + type: "Line", + line: 1, + column: 1 + }] + }, + { + code: "// pragma\n// invalid\n1 + 1;", + options: [{ position: "beside", ignorePattern: "pragma" }], + errors: [{ + message: "Expected comment to be beside code.", + type: "Line", + line: 2, + column: 1 + }] + }, + { + code: "1 + 1; // linter\n2 + 2; // invalid comment", + options: [{position: "above", ignorePattern: "linter" } ], + errors: [{ + message: "Expected comment to be above code.", + type: "Line", + line: 2, + column: 8 + }] + } + ] +});