diff --git a/docs/rules/max-len.md b/docs/rules/max-len.md index ec0c802a85e..4d0533bc5cc 100644 --- a/docs/rules/max-len.md +++ b/docs/rules/max-len.md @@ -23,6 +23,8 @@ This rule has a number or object option: * `"ignoreComments": true` ignores all trailing comments and comments on their own line * `"ignoreTrailingComments": true` ignores only trailing comments * `"ignoreUrls": true` ignores lines that contain a URL +* `"ignoreStrings": true` ignores lines that contain a double-quoted or single-quoted string +* `"ignoreTemplateLiterals": true` ignores lines that contain a template literal ### code @@ -111,6 +113,26 @@ Examples of **correct** code for this rule with the `{ "ignoreUrls": true }` opt var url = 'https://www.example.com/really/really/really/really/really/really/really/long'; ``` +### ignoreStrings + +Examples of **correct** code for this rule with the `{ "ignoreStrings": true }` option: + +```js +/*eslint max-len: ["error", { "ignoreStrings": true }]*/ + +var longString = 'this is a really really really really really long string!'; +``` + +### ignoreTemplateLiterals + +Examples of **correct** code for this rule with the `{ "ignoreTemplateLiterals": true }` option: + +```js +/*eslint max-len: ["error", { "ignoreTemplateLiterals": true }]*/ + +var longTemplateLiteral = `this is a really really really really really long template literal!`; +``` + ### ignorePattern Examples of **correct** code for this rule with the `{ "ignorePattern": true }` option: diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index 2461ee88b03..8ef12b4cd8e 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -30,9 +30,15 @@ const OPTIONS_SCHEMA = { ignoreComments: { type: "boolean" }, + ignoreStrings: { + type: "boolean" + }, ignoreUrls: { type: "boolean" }, + ignoreTemplateLiterals: { + type: "boolean" + }, ignoreTrailingComments: { type: "boolean" } @@ -121,6 +127,8 @@ module.exports = { const maxLength = options.code || 80, tabWidth = options.tabWidth || 4, ignoreComments = options.ignoreComments || false, + ignoreStrings = options.ignoreStrings || false, + ignoreTemplateLiterals = options.ignoreTemplateLiterals || false, ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false, ignoreUrls = options.ignoreUrls || false, maxCommentLength = options.comments; @@ -179,6 +187,59 @@ module.exports = { return line.slice(0, comment.loc.start.column).replace(/\s+$/, ""); } + /** + * Ensure that an array exists at [key] on `object`, and add `value` to it. + * + * @param {Object} object the object to mutate + * @param {string} key the object's key + * @param {*} value the value to add + * @returns {void} + * @private + */ + function ensureArrayAndPush(object, key, value) { + if (!Array.isArray(object[key])) { + object[key] = []; + } + object[key].push(value); + } + + /** + * Retrieves an array containing all strings (" or ') in the source code. + * + * @returns {ASTNode[]} An array of string nodes. + */ + function getAllStrings() { + return sourceCode.ast.tokens.filter(function(token) { + return token.type === "String"; + }); + } + + /** + * Retrieves an array containing all template literals in the source code. + * + * @returns {ASTNode[]} An array of template literal nodes. + */ + function getAllTemplateLiterals() { + return sourceCode.ast.tokens.filter(function(token) { + return token.type === "Template"; + }); + } + + + /** + * A reducer to group an AST node by line number, both start and end. + * + * @param {Object} acc the accumulator + * @param {ASTNode} node the AST node in question + * @returns {Object} the modified accumulator + * @private + */ + function groupByLineNumber(acc, node) { + ensureArrayAndPush(acc, node.loc.start.line, node); + ensureArrayAndPush(acc, node.loc.end.line, node); + return acc; + } + /** * Check the program for max length * @param {ASTNode} node Node to examine @@ -196,6 +257,12 @@ module.exports = { // we iterate over comments in parallel with the lines let commentsIndex = 0; + const strings = getAllStrings(sourceCode); + const stringsByLine = strings.reduce(groupByLineNumber, {}); + + const templateLiterals = getAllTemplateLiterals(sourceCode); + const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {}); + lines.forEach(function(line, i) { // i is zero-indexed, line numbers are one-indexed @@ -229,7 +296,10 @@ module.exports = { } } if (ignorePattern && ignorePattern.test(line) || - ignoreUrls && URL_REGEXP.test(line)) { + ignoreUrls && URL_REGEXP.test(line) || + ignoreStrings && stringsByLine[lineNumber] || + ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] + ) { // ignore this line return; diff --git a/tests/lib/rules/max-len.js b/tests/lib/rules/max-len.js index 81e5eb8c6c3..63312b8642a 100644 --- a/tests/lib/rules/max-len.js +++ b/tests/lib/rules/max-len.js @@ -15,6 +15,8 @@ const rule = require("../../../lib/rules/max-len"), // Tests //------------------------------------------------------------------------------ +const parserOptions = { ecmaVersion: 6 }; + const ruleTester = new RuleTester(); ruleTester.run("max-len", rule, { @@ -85,6 +87,30 @@ ruleTester.run("max-len", rule, { options: [40, 4, {ignoreComments: true, ignoreTrailingComments: false}] }, + // ignoreStrings and ignoreTemplateLiterals options + { + code: "var foo = veryLongIdentifier;\nvar bar = 'this is a very long string';", + options: [29, 4, { ignoreStrings: true }] + }, + { + code: "var foo = veryLongIdentifier;\nvar bar = \"this is a very long string\";", + options: [29, 4, { ignoreStrings: true }] + }, + { + code: "var str = \"this is a very long string\\\nwith continuation\";", + options: [29, 4, { ignoreStrings: true }] + }, + { + code: "var foo = veryLongIdentifier;\nvar bar = `this is a very long string`;", + options: [29, 4, { ignoreTemplateLiterals: true }], + parserOptions + }, + { + code: "var foo = veryLongIdentifier;\nvar bar = `this is a very long string\nand this is another line that is very long`;", + options: [29, 4, { ignoreTemplateLiterals: true }], + parserOptions + }, + // check indented comment lines - https://github.com/eslint/eslint/issues/6322 { code: "function foo() {\n" + @@ -409,6 +435,64 @@ ruleTester.run("max-len", rule, { column: 1 } ] + }, + + // ignoreStrings and ignoreTemplateLiterals options + { + code: "var foo = veryLongIdentifier;\nvar bar = 'this is a very long string';", + options: [29, { ignoreStrings: false, ignoreTemplateLiterals: true }], + errors: [ + { + message: "Line 2 exceeds the maximum line length of 29.", + type: "Program", + line: 2, + column: 1 + } + ] + }, + { + code: "var foo = veryLongIdentifier;\nvar bar = \"this is a very long string\";", + options: [29, { ignoreStrings: false, ignoreTemplateLiterals: true }], + errors: [ + { + message: "Line 2 exceeds the maximum line length of 29.", + type: "Program", + line: 2, + column: 1 + } + ] + }, + { + code: "var foo = veryLongIdentifier;\nvar bar = `this is a very long string`;", + options: [29, { ignoreStrings: false, ignoreTemplateLiterals: false }], + parserOptions, + errors: [ + { + message: "Line 2 exceeds the maximum line length of 29.", + type: "Program", + line: 2, + column: 1 + } + ] + }, + { + code: "var foo = veryLongIdentifier;\nvar bar = `this is a very long string\nand this is another line that is very long`;", + options: [29, { ignoreStrings: false, ignoreTemplateLiterals: false }], + parserOptions, + errors: [ + { + message: "Line 2 exceeds the maximum line length of 29.", + type: "Program", + line: 2, + column: 1 + }, + { + message: "Line 3 exceeds the maximum line length of 29.", + type: "Program", + line: 3, + column: 1 + } + ] } ] });