From 75641af1322790b69d30328c4fee69327e2f89ff Mon Sep 17 00:00:00 2001 From: Denis Sikuler Date: Fri, 11 Oct 2019 19:15:18 +0300 Subject: [PATCH] Update: Add consistent option to computed-property-spacing --- docs/developer-guide/working-with-rules.md | 2 + docs/rules/computed-property-spacing.md | 58 ++ lib/rules/computed-property-spacing.js | 95 ++- lib/source-code/source-code.js | 36 +- tests/lib/rules/computed-property-spacing.js | 706 +++++++++++++++++++ tests/lib/source-code/source-code.js | 137 ++++ 6 files changed, 1010 insertions(+), 24 deletions(-) diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md index 35f224b3229..f38d5c2097b 100644 --- a/docs/developer-guide/working-with-rules.md +++ b/docs/developer-guide/working-with-rules.md @@ -391,6 +391,8 @@ Once you have an instance of `SourceCode`, you can use the methods on it to work * `getCommentsAfter(nodeOrToken)` - returns an array of comment tokens that occur directly after the given node or token. * `getCommentsInside(node)` - returns an array of all comment tokens inside a given node. * `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none. +* `getTextBetweenTokens(first, second)` - returns text between two tokens. +* `getCleanTextBetweenTokens(first, second)` - returns text between two tokens without inline comments (`/* */`). * `isSpaceBetweenTokens(first, second)` - returns true if there is a whitespace character between the two tokens. * `getFirstToken(node, skipOptions)` - returns the first token representing the given node. * `getFirstTokens(node, countOptions)` - returns the first `count` tokens representing the given node. diff --git a/docs/rules/computed-property-spacing.md b/docs/rules/computed-property-spacing.md index 9641ee089e5..aa159f636d7 100644 --- a/docs/rules/computed-property-spacing.md +++ b/docs/rules/computed-property-spacing.md @@ -31,10 +31,12 @@ String option: * `"never"` (default) disallows spaces inside computed property brackets * `"always"` requires one or more spaces inside computed property brackets +* `"consistent"` requires consistent usage of spaces inside computed property brackets: there should be equal number of spaces after `[` and before `]` (the limit of spaces can be set by `maxSpaces` option, see below) Object option: * `"enforceForClassMembers": true` additionally applies this rule to class members (default is `false`) +* `"maxSpaces": 2` specifies maximum number of allowed spaced for `"consistent"` option (default is `1`) ### never @@ -90,6 +92,34 @@ var x = {[ b ]: a} obj[ foo[ bar ] ] ``` +### consistent + +Examples of **incorrect** code for this rule with the `"consistent"` option: + +```js +/*eslint computed-property-spacing: ["error", "consistent"]*/ +/*eslint-env es6*/ + +obj[foo ] +var x = {[ b]: a} +obj[ foo ] +obj[ 'foo' ] +obj[ foo[ bar ]] +var x = {[b ]: a} +``` + +Examples of **correct** code for this rule with the `"consistent"` option: + +```js +/*eslint computed-property-spacing: ["error", "consistent"]*/ +/*eslint-env es6*/ + +obj[ foo ] +obj['foo'] +var x = {[ b ]: a[ c[d] ]} +obj[ foo[bar] ] +``` + #### enforceForClassMembers By default, this rule does not check class declarations and class expressions, @@ -138,6 +168,34 @@ const Bar = class { } ``` +### maxSpaces + +Examples of **incorrect** code for this rule with `"consistent"` and `{ maxSpaces: 2 }`: + +```js +/*eslint computed-property-spacing: ["error", "consistent", { "maxSpaces": 2 }]*/ +/*eslint-env es6*/ + +obj[ foo ] +var x = {[ b ]: a} +obj[ foo ] +obj['foo' ] +obj[ foo[ bar ] ] +var x = {[ b ]: a} +``` + +Examples of **correct** code for this rule with `"consistent"` and `{ maxSpaces: 2 }`: + +```js +/*eslint computed-property-spacing: ["error", "consistent", { "maxSpaces": 2 }]*/ +/*eslint-env es6*/ + +obj[ foo ] +obj[ 'foo' ] +var x = {[ b ]: a[ c[ d ] ]} +obj[ foo[bar] ] +``` + ## When Not To Use It You can turn this rule off if you are not concerned with the consistency of computed properties. diff --git a/lib/rules/computed-property-spacing.js b/lib/rules/computed-property-spacing.js index bc8be964f4f..a1f20647694 100644 --- a/lib/rules/computed-property-spacing.js +++ b/lib/rules/computed-property-spacing.js @@ -25,7 +25,7 @@ module.exports = { schema: [ { - enum: ["always", "never"] + enum: ["always", "never", "consistent"] }, { type: "object", @@ -33,6 +33,11 @@ module.exports = { enforceForClassMembers: { type: "boolean", default: false + }, + maxSpaces: { + type: "integer", + minimum: 0, + default: 1 } }, additionalProperties: false @@ -44,14 +49,21 @@ module.exports = { unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.", missingSpaceBefore: "A space is required before '{{tokenValue}}'.", - missingSpaceAfter: "A space is required after '{{tokenValue}}'." + missingSpaceAfter: "A space is required after '{{tokenValue}}'.", + + inconsistentSpaces: "There should be equal number (no more than {{maxSpaces}}) of spaces around '{{expression}}'." } }, create(context) { const sourceCode = context.getSourceCode(); - const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" - const enforceForClassMembers = context.options[1] && context.options[1].enforceForClassMembers; + const options = context.options; + const checkType = options[0]; + const settings = options[1] || {}; + const propertyNameMustBeSpaced = checkType === "always"; // default is "never" + const consistentSpaces = checkType === "consistent"; + const enforceForClassMembers = settings.enforceForClassMembers; + const maxSpaces = ("maxSpaces" in settings) ? settings.maxSpaces : 1; //-------------------------------------------------------------------------- // Helpers @@ -139,6 +151,33 @@ module.exports = { }); } + /** + * Reports that there should be equal number of spaces around expression. + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} beforeToken The token before expression. + * @param {Token} firstToken The first token of expression. + * @param {Token} lastToken The last token of expression. + * @param {Token} afterToken The token after expression. + * @returns {void} + */ + function reportInconsistentSpaces(node, beforeToken, firstToken, lastToken, afterToken) { + context.report({ + node, + loc: firstToken.loc.start, + messageId: "inconsistentSpaces", + data: { + expression: sourceCode.getCleanTextBetweenTokens(beforeToken, afterToken).trim(), + maxSpaces + }, + fix(fixer) { + return [ + fixer.removeRange([beforeToken.range[1], firstToken.range[0]]), + fixer.removeRange([lastToken.range[1], afterToken.range[0]]) + ]; + } + }); + } + /** * Returns a function that checks the spacing of a node on the property name * that was passed in. @@ -156,28 +195,42 @@ module.exports = { const before = sourceCode.getTokenBefore(property), first = sourceCode.getFirstToken(property), last = sourceCode.getLastToken(property), - after = sourceCode.getTokenAfter(property); + after = sourceCode.getTokenAfter(property), + beforeFirstOnSameLine = astUtils.isTokenOnSameLine(before, first), + lastAfterOnSameLine = astUtils.isTokenOnSameLine(last, after); + let textBetweenBeforeFirst, + textBetweenLastAfter; - if (astUtils.isTokenOnSameLine(before, first)) { - if (propertyNameMustBeSpaced) { - if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) { - reportRequiredBeginningSpace(node, before); + if (consistentSpaces) { + if (beforeFirstOnSameLine || lastAfterOnSameLine) { + textBetweenBeforeFirst = sourceCode.getCleanTextBetweenTokens(before, first); + textBetweenLastAfter = sourceCode.getCleanTextBetweenTokens(last, after); + if (textBetweenBeforeFirst !== textBetweenLastAfter || !/^ *$/u.test(textBetweenLastAfter) || textBetweenLastAfter.length > maxSpaces) { + reportInconsistentSpaces(node, before, first, last, after); } - } else { - if (sourceCode.isSpaceBetweenTokens(before, first)) { - reportNoBeginningSpace(node, before, first); + } + } else { + if (beforeFirstOnSameLine) { + if (propertyNameMustBeSpaced) { + if (!sourceCode.isSpaceBetweenTokens(before, first)) { + reportRequiredBeginningSpace(node, before); + } + } else { + if (sourceCode.isSpaceBetweenTokens(before, first)) { + reportNoBeginningSpace(node, before, first); + } } } - } - if (astUtils.isTokenOnSameLine(last, after)) { - if (propertyNameMustBeSpaced) { - if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) { - reportRequiredEndingSpace(node, after); - } - } else { - if (sourceCode.isSpaceBetweenTokens(last, after)) { - reportNoEndingSpace(node, after, last); + if (lastAfterOnSameLine) { + if (propertyNameMustBeSpaced) { + if (!sourceCode.isSpaceBetweenTokens(last, after)) { + reportRequiredEndingSpace(node, after); + } + } else { + if (sourceCode.isSpaceBetweenTokens(last, after)) { + reportNoEndingSpace(node, after, last); + } } } } diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js index 450dc24a163..eb26a8f90f1 100644 --- a/lib/source-code/source-code.js +++ b/lib/source-code/source-code.js @@ -193,6 +193,16 @@ class SourceCode extends TokenStore { Object.freeze(this.lines); } + /** + * Removes inline comments (`\/* *\/`) from specified text. + * @param {string} text Text to process. + * @returns {string} Text without inline comments. + * @public + */ + static removeInlineComments(text) { + return text.replace(/\/\*.*?\*\//gu, ""); + } + /** * Split the source code into multiple lines based on the line delimiters. * @param {string} text Source code as a string. @@ -410,6 +420,28 @@ class SourceCode extends TokenStore { return result; } + /** + * Returns text between two tokens. + * @param {Token} first The token to get text after. + * @param {Token} second The token to get text before. + * @returns {string} Text between specified tokens. + * @public + */ + getTextBetweenTokens(first, second) { + return this.text.slice(first.range[1], second.range[0]); + } + + /** + * Returns text between two tokens without inline comments. + * @param {Token} first The token to get text after. + * @param {Token} second The token to get text before. + * @returns {string} Text between specified tokens without inline comments. + * @public + */ + getCleanTextBetweenTokens(first, second) { + return SourceCode.removeInlineComments(this.getTextBetweenTokens(first, second)); + } + /** * Determines if two tokens have at least one whitespace character * between them. This completely disregards comments in making the @@ -421,9 +453,7 @@ class SourceCode extends TokenStore { * @public */ isSpaceBetweenTokens(first, second) { - const text = this.text.slice(first.range[1], second.range[0]); - - return /\s/u.test(text.replace(/\/\*.*?\*\//gu, "")); + return /\s/u.test(this.getCleanTextBetweenTokens(first, second)); } /** diff --git a/tests/lib/rules/computed-property-spacing.js b/tests/lib/rules/computed-property-spacing.js index 3901d8c3fb3..0221fd39b69 100644 --- a/tests/lib/rules/computed-property-spacing.js +++ b/tests/lib/rules/computed-property-spacing.js @@ -74,6 +74,96 @@ ruleTester.run("computed-property-spacing", rule, { { code: "var foo = {};", options: ["never"] }, { code: "var foo = [];", options: ["never"] }, + // consistent + { code: "obj[foo]", options: ["consistent"] }, + { code: "obj[ foo ]", options: ["consistent"] }, + { code: "obj[\nfoo\n]", options: ["consistent"] }, + { code: "obj['foo']", options: ["consistent"] }, + { code: "obj[ 'foo' ]", options: ["consistent"] }, + { code: "obj['foo'+'bar']", options: ["consistent"] }, + { code: "obj['foo' + 'bar']", options: ["consistent"] }, + { code: "obj[ 'foo' + 'bar' ]", options: ["consistent"] }, + { code: "obj[obj2[foo]]", options: ["consistent"] }, + { code: "obj[ obj2[foo] ]", options: ["consistent"] }, + { code: "obj[obj2[ foo ]]", options: ["consistent"] }, + { code: "obj[ obj2[ foo ] ]", options: ["consistent"] }, + { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent"] }, + { code: "obj['map'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent"] }, + { code: "obj[ 'map' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent"] }, + { code: "obj['for'+'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent"] }, + { code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent"] }, + { code: "obj[ 'for' + 'Each' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent"] }, + { code: "var foo = obj[1]", options: ["consistent"] }, + { code: "var foo = obj[ 1 ]", options: ["consistent"] }, + { code: "var foo = obj['foo'];", options: ["consistent"] }, + { code: "var foo = obj[ 'foo' ];", options: ["consistent"] }, + { code: "var foo = obj[[1, 1]];", options: ["consistent"] }, + { code: "var foo = obj[ [1, 1] ];", options: ["consistent"] }, + + // consistent - objectLiteralComputedProperties + { code: "var x = {[\"a\"]: a}", options: ["consistent"], parserOptions: { ecmaVersion: 6 } }, + { code: "var x = {[ \"a\" ]: a}", options: ["consistent"], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = {[x]: a}", options: ["consistent"], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = {[ x ]: a}", options: ["consistent"], parserOptions: { ecmaVersion: 6 } }, + { code: "var x = {[\"a\"]() {}}", options: ["consistent"], parserOptions: { ecmaVersion: 6 } }, + { code: "var x = {[ \"a\" ]() {}}", options: ["consistent"], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = {[x]() {}}", options: ["consistent"], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = {[ x ]() {}}", options: ["consistent"], parserOptions: { ecmaVersion: 6 } }, + + // consistent - unrelated cases + { code: "var foo = {};", options: ["consistent"] }, + { code: "var foo = [];", options: ["consistent"] }, + + // consistent and maxSpaces + { code: "obj[foo]", options: ["consistent", { maxSpaces: 0 }] }, + { code: "obj[foo]", options: ["consistent", { maxSpaces: 1 }] }, + { code: "obj[foo]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ foo ]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ foo ]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[\nfoo\n]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj['foo']", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ 'foo' ]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj['foo'+'bar']", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj['foo' + 'bar']", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ 'foo' + 'bar' ]", options: ["consistent", { maxSpaces: 3 }] }, + { code: "obj[obj2[foo]]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ obj2[foo] ]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ obj2[foo] ]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[obj2[ foo ]]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[obj2[ foo ]]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ obj2[ foo ] ]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ obj2[ foo ] ]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ obj2[ foo ] ]", options: ["consistent", { maxSpaces: 4 }] }, + { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent", { maxSpaces: 20 }] }, + { code: "obj['map'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj[ 'map' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent", { maxSpaces: 2 }] }, + { code: "obj [ 'for' + 'Each' ] (function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["consistent", { maxSpaces: 2 }] }, + { code: "var foo = obj[1]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "var foo = obj[ 1 ]", options: ["consistent", { maxSpaces: 2 }] }, + { code: "var foo = obj['foo'];", options: ["consistent", { maxSpaces: 2 }] }, + { code: "var foo = obj[ 'foo' ];", options: ["consistent", { maxSpaces: 3 }] }, + { code: "var foo = obj[ 'foo' ];", options: ["consistent", { maxSpaces: 3 }] }, + { code: "var foo = obj[[1, 1]];", options: ["consistent", { maxSpaces: 2 }] }, + { code: "var foo = obj [ [1, 1] ] ;", options: ["consistent", { maxSpaces: 2 }] }, + { code: "var foo = obj [ [1, 1] ] ;", options: ["consistent", { maxSpaces: 2 }] }, + + // consistent and maxSpaces - objectLiteralComputedProperties + { code: "var x = {[\"a\"]: a}", options: ["consistent", { maxSpaces: 2 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var x = {[ \"a\" ]: a}", options: ["consistent", { maxSpaces: 2 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var x = {[ \"a\" ]: a}", options: ["consistent", { maxSpaces: 2 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = {[x]: a}", options: ["consistent", { maxSpaces: 2 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = { [ x ] : a}", options: ["consistent", { maxSpaces: 3 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = {[ x.y.z ]: a[ b[c[ d.e.f ]] ]}", options: ["consistent", { maxSpaces: 2 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var x = {[\"a\"]() {}}", options: ["consistent", { maxSpaces: 2 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var x = {[ \"a\" ]() {}}", options: ["consistent", { maxSpaces: 2 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = {[x]() {}}", options: ["consistent", { maxSpaces: 2 }], parserOptions: { ecmaVersion: 6 } }, + { code: "var y = {[ x ]() {}}", options: ["consistent", { maxSpaces: 5 }], parserOptions: { ecmaVersion: 6 } }, + + // consistent and maxSpaces - unrelated cases + { code: "var foo = {};", options: ["consistent", { maxSpaces: 12 }] }, + { code: "var foo = [];", options: ["consistent", { maxSpaces: 9 }] }, + //------------------------------------------------------------------------------ // Classes //------------------------------------------------------------------------------ @@ -108,6 +198,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", {}], parserOptions: { ecmaVersion: 6 } }, + { + code: "class A { [a](){} get [ b ](){} set [c](foo){} static [ d ](){} static get [e](){} static set [ f ](bar){} }", + options: ["consistent", {}], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class A { [a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [e](){} static set [ f ](bar){} }", + options: ["consistent", { maxSpaces: 3 }], + parserOptions: { ecmaVersion: 6 } + }, // explicitly disabled option { @@ -130,6 +230,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", { enforceForClassMembers: false }], parserOptions: { ecmaVersion: 6 } }, + { + code: "class A { [ a](){} get [ b ](){} set [b](foo){} static [c](){} static get [d](){} static set [d](bar){} }", + options: ["consistent", { enforceForClassMembers: false }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class A { [ a](){} get [ b ](){} set [b](foo){} static [ c](){} static get [d](){} static set [d](bar){} }", + options: ["consistent", { enforceForClassMembers: false, maxSpaces: 2 }], + parserOptions: { ecmaVersion: 6 } + }, // valid spacing { @@ -172,6 +282,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", { enforceForClassMembers: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "A = class { [a](){} get [ b ](){} set [c](foo){ i[ k ] = 1; } static [ d ](){} static get [e](){} static set [ f ](bar){} }", + options: ["consistent", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "A = class { [ a ]() { return some[ big.key.here ]; } get [ b ](){} set [ c ](foo){} static [ d ](){} static get [e](){} static set [ f ](bar){} }", + options: ["consistent", { enforceForClassMembers: true, maxSpaces: 3 }], + parserOptions: { ecmaVersion: 6 } + }, // non-computed { @@ -183,6 +303,16 @@ ruleTester.run("computed-property-spacing", rule, { code: "A = class {a(){}get b(){}set b(foo){}static c(){}static get d(){}static set d(bar){}}", options: ["always", { enforceForClassMembers: true }], parserOptions: { ecmaVersion: 6 } + }, + { + code: "A = class {a() {}get b(){} set b(foo){}static c(){} static get d(){}static set d (bar){}}", + options: ["consistent", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "A = class { a() {}get b(){} set b(foo){}static c(){} static get d(){} static set d (bar){}}", + options: ["consistent", { enforceForClassMembers: true, maxSpaces: 2 }], + parserOptions: { ecmaVersion: 6 } } ], @@ -311,6 +441,188 @@ ruleTester.run("computed-property-spacing", rule, { } ] }, + { + code: "var foo = obj[ 1];", + output: "var foo = obj[1];", + options: ["consistent"], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "1", maxSpaces: 1 }, + type: "MemberExpression", + column: 16, + line: 1 + } + ] + }, + { + code: "var foo = obj[ 1 ];", + output: "var foo = obj[1];", + options: ["consistent"], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "1", maxSpaces: 1 }, + type: "MemberExpression", + column: 16, + line: 1 + } + ] + }, + { + code: "var foo = obj[bar ];", + output: "var foo = obj[bar];", + options: ["consistent"], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 1 }, + type: "MemberExpression", + column: 15, + line: 1 + } + ] + }, + { + code: "var foo = obj [bar ] ;", + output: "var foo = obj [bar] ;", + options: ["consistent"], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 1 }, + type: "MemberExpression", + column: 16, + line: 1 + } + ] + }, + { + code: "var foo = obj[\tbar\t];", + output: "var foo = obj[bar];", + options: ["consistent"], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 1 }, + type: "MemberExpression", + column: 16, + line: 1 + } + ] + }, + { + code: "var foo = obj[ \tbar\t ];", + output: "var foo = obj[bar];", + options: ["consistent"], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 1 }, + type: "MemberExpression", + column: 17, + line: 1 + } + ] + }, + { + code: "var foo = obj[\t bar\t ];", + output: "var foo = obj[bar];", + options: ["consistent"], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 1 }, + type: "MemberExpression", + column: 17, + line: 1 + } + ] + }, + { + code: "var foo = obj[\t bar \t];", + output: "var foo = obj[bar];", + options: ["consistent"], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 1 }, + type: "MemberExpression", + column: 17, + line: 1 + } + ] + }, + { + code: "var foo = obj[\tbar\t];", + output: "var foo = obj[bar];", + options: ["consistent", { maxSpaces: 10 }], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 10 }, + type: "MemberExpression", + column: 16, + line: 1 + } + ] + }, + { + code: "var foo = obj[ \t bar \t ];", + output: "var foo = obj[bar];", + options: ["consistent", { maxSpaces: 10 }], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 10 }, + type: "MemberExpression", + column: 19, + line: 1 + } + ] + }, + { + code: "var foo = obj[ bar ];", + output: "var foo = obj[bar];", + options: ["consistent", { maxSpaces: 0 }], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 0 }, + type: "MemberExpression", + column: 16, + line: 1 + } + ] + }, + { + code: "var foo = obj[ bar ];", + output: "var foo = obj[bar];", + options: ["consistent", { maxSpaces: 2 }], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "bar", maxSpaces: 2 }, + type: "MemberExpression", + column: 18, + line: 1 + } + ] + }, + { + code: "var foo = obj [ x1[y2].z3 ] ;", + output: "var foo = obj [x1[y2].z3] ;", + options: ["consistent", { maxSpaces: 2 }], + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "x1[y2].z3", maxSpaces: 2 }, + type: "MemberExpression", + column: 18, + line: 1 + } + ] + }, // always - objectLiteralComputedProperties { @@ -435,6 +747,210 @@ ruleTester.run("computed-property-spacing", rule, { ] }, + // consistent - objectLiteralComputedProperties + { + code: "var x = {[ a]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 12, + line: 1 + } + ] + }, + { + code: "var x = {[\ta\t]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 12, + line: 1 + } + ] + }, + { + code: "var x = {[ \ta\t ]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 13, + line: 1 + } + ] + }, + { + code: "var x = {[\t a \t]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 13, + line: 1 + } + ] + }, + { + code: "var x = {[ a ]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 13, + line: 1 + } + ] + }, + { + code: "var x = {[ a\t]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 12, + line: 1 + } + ] + }, + { + code: "var x = {[\ta\n]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 12, + line: 1 + } + ] + }, + { + code: "var x = {[ a\n]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 12, + line: 1 + } + ] + }, + { + code: "var x = {[\n a ]: b}", + output: "var x = {[a]: b}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "Property", + column: 2, + line: 2 + } + ] + }, + { + code: "var x = {[ a ]: b}", + output: "var x = {[a]: b}", + options: ["consistent", { maxSpaces: 2 }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 2 }, + type: "Property", + column: 14, + line: 1 + } + ] + }, + { + code: "var x = {[ a ]: b}", + output: "var x = {[a]: b}", + options: ["consistent", { maxSpaces: 0 }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 0 }, + type: "Property", + column: 12, + line: 1 + } + ] + }, + { + code: "var x = {[ a ]: b}", + output: "var x = {[a]: b}", + options: ["consistent", { maxSpaces: 10 }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 10 }, + type: "Property", + column: 14, + line: 1 + } + ] + }, + { + code: "const a = {[x.y.z ]: b[ some.object.having.key]}", + output: "const a = {[x.y.z]: b[some.object.having.key]}", + options: ["consistent"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "x.y.z", maxSpaces: 1 }, + type: "Property", + column: 13, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "some.object.having.key", maxSpaces: 1 }, + type: "MemberExpression", + column: 25, + line: 1 + } + ] + }, + //------------------------------------------------------------------------------ // Classes //------------------------------------------------------------------------------ @@ -757,6 +1273,196 @@ ruleTester.run("computed-property-spacing", rule, { line: 1 } ] + }, + + // consistent + { + code: "class A { [ a](){} }", + output: "class A { [a](){} }", + options: ["consistent", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "MethodDefinition", + column: 13, + line: 1 + } + ] + }, + { + code: "A = class { [ a ]() { } b() {} static [c]() { } static [ d ]() {}}", + output: "A = class { [a]() { } b() {} static [c]() { } static [ d ]() {}}", + options: ["consistent", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "MethodDefinition", + column: 16, + line: 1 + } + ] + }, + { + code: "class A { get [a ](){} set [ a](foo){} get b() {} static set b(bar){} static get [ c](){} static set [ c ](baz){} }", + output: "class A { get [a](){} set [a](foo){} get b() {} static set b(bar){} static get [c](){} static set [c](baz){} }", + options: ["consistent", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "MethodDefinition", + column: 16, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "MethodDefinition", + column: 31, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "c", maxSpaces: 1 }, + type: "MethodDefinition", + column: 85, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "c", maxSpaces: 1 }, + type: "MethodDefinition", + column: 105, + line: 1 + } + ] + }, + { + code: "A = class { [ a](){} get [ b ](){} set [c ](foo){} static [d](){} static get [\te ](){} static set [ f\n](bar){} }", + output: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", + options: ["consistent", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 1 }, + type: "MethodDefinition", + column: 15, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "b", maxSpaces: 1 }, + type: "MethodDefinition", + column: 29, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "c", maxSpaces: 1 }, + type: "MethodDefinition", + column: 44, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "e", maxSpaces: 1 }, + type: "MethodDefinition", + column: 83, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "f", maxSpaces: 1 }, + type: "MethodDefinition", + column: 104, + line: 1 + } + ] + }, + { + code: "class A { [ a ](){} get [ b ](){} set [c](foo){} static [ d](){} static get [\te\t](){} static set [f](bar){} }", + output: "class A { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", + options: ["consistent", { enforceForClassMembers: true, maxSpaces: 0 }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "a", maxSpaces: 0 }, + type: "MethodDefinition", + column: 13, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "b", maxSpaces: 0 }, + type: "MethodDefinition", + column: 28, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "d", maxSpaces: 0 }, + type: "MethodDefinition", + column: 61, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "e", maxSpaces: 0 }, + type: "MethodDefinition", + column: 81, + line: 1 + } + ] + }, + { + code: "class A { [ a ]() { let i = {}; i[ this.omega] = 3; return i; } get [ b ]() { k[n] = null; } set [ \t c ](foo){} static [d](){} static get [ e ](){} static set [ f ](bar){ s[ some.key\t] = false; } }", + output: "class A { [ a ]() { let i = {}; i[this.omega] = 3; return i; } get [ b ]() { k[n] = null; } set [c](foo){} static [d](){} static get [e](){} static set [f](bar){ s[some.key] = false; } }", + options: ["consistent", { enforceForClassMembers: true, maxSpaces: 2 }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "inconsistentSpaces", + data: { expression: "this.omega", maxSpaces: 2 }, + type: "MemberExpression", + column: 36, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "c", maxSpaces: 2 }, + type: "MethodDefinition", + column: 104, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "e", maxSpaces: 2 }, + type: "MethodDefinition", + column: 146, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "f", maxSpaces: 2 }, + type: "MethodDefinition", + column: 169, + line: 1 + }, + { + messageId: "inconsistentSpaces", + data: { expression: "some.key", maxSpaces: 2 }, + type: "MemberExpression", + column: 184, + line: 1 + } + ] } ] }); diff --git a/tests/lib/source-code/source-code.js b/tests/lib/source-code/source-code.js index 57cc960d2f9..0c6ecda4c5f 100644 --- a/tests/lib/source-code/source-code.js +++ b/tests/lib/source-code/source-code.js @@ -227,6 +227,41 @@ describe("SourceCode", () => { }); + describe("SourceCode.removeInlineComments()", () => { + it("should remove inline comments from text", () => { + const codeWithComments = [ + "/* A starting comment. */", + "/** ", + " * Code is ", + " * really good ", + " */", + "(function iife() {", + "//* whatever", + "Foo.bar = function() { /* some comment here */ }", + "//* and here *", + "}());", + " /* Final comment */ " + ].join("\n"); + const codeWithoutComments = [ + "", + "/** ", + " * Code is ", + " * really good ", + " */", + "(function iife() {", + "//* whatever", + "Foo.bar = function() { }", + "//* and here *", + "}());", + " " + ].join("\n"); + + assert.strictEqual(SourceCode.removeInlineComments(codeWithComments), codeWithoutComments); + assert.strictEqual(SourceCode.removeInlineComments(codeWithoutComments), codeWithoutComments); + }); + }); + + describe("getJSDocComment()", () => { afterEach(() => { sinon.verifyAndRestore(); @@ -1789,6 +1824,108 @@ describe("SourceCode", () => { }); }); + describe("getTextBetweenTokens()", () => { + + leche.withData([ + ["let foo = bar;", " "], + ["let foo = bar;", " "], + ["let foo = bar;", " foo = ", 0, 3], + ["let foo = bar;", " foo = bar", 0, 4], + ["let /**/ foo = bar;", " /**/ "], + ["let /**/ foo = bar;", " /**/ foo = bar", 0, 4], + ["let /**/ foo = bar;", " bar", 2, 4], + ["let/**/foo = bar;", "/**/"], + ["a+b", ""], + ["a+b", "", 1, 2], + ["a/**/+b", "/**/"], + ["a/* Some comment */+b", "/* Some comment */"], + ["a /* Some comment */ + b", " /* Some comment */ "], + ["a /* Some comment */ + b", " /* Some comment */ + ", 0, 2], + ["a/* */+b", "/* */"], + ["a/**/ +b", "/**/ "], + ["a/**/ /**/+b", "/**/ /**/"], + ["a/**/\n/**/+b", "/**/\n/**/"], + ["a /* comment 1 */\n/* comment #2 */ + b", " /* comment 1 */\n/* comment #2 */ "], + ["a /* comment 1 */\n/* comment #2 */ + b", " /* comment 1 */\n/* comment #2 */ + ", 0, 2], + ["a +b", " "], + ["a[b] = c", "b", 1, 3], + ["a[b] = c;", "[b] ", 0, 4], + ["a[ b ] = 3;", " b ", 1, 3], + ["foo[ bar ] = 'value';", "[ bar ] ", 0, 4], + ["foo [ bar ] /* comment here */ = null ;", " "], + ["foo [ bar ] /* comment here */ = null ;", " bar ", 1, 3], + ["foo [ bar ] /* comment here */ = null ;", " /* comment here */ ", 3, 4], + ["foo [ bar ] /* comment here */ = null ;", " [ bar ] /* comment here */ = ", 0, 5] + ], (code, expected, firstTokenIndex, secondTokenIndex) => { + + it("should return text between tokens", () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast), + firstIndex = firstTokenIndex || 0, + secondIndex = typeof secondTokenIndex === "number" ? secondTokenIndex : firstIndex + 1, + tokens = sourceCode.ast.tokens; + + assert.strictEqual( + sourceCode.getTextBetweenTokens( + tokens[firstIndex], tokens[secondIndex] + ), + expected + ); + }); + }); + }); + + describe("getCleanTextBetweenTokens()", () => { + + leche.withData([ + ["let foo = bar;", " "], + ["let foo = bar;", " "], + ["let foo = bar;", " foo = ", 0, 3], + ["let foo = bar;", " foo = bar", 0, 4], + ["let /**/ foo = bar;", " "], + ["let /**/ foo = bar;", " foo = bar", 0, 4], + ["let /**/ foo = bar;", " bar", 2, 4], + ["let/**/foo = bar;", ""], + ["a+b", ""], + ["a+b", "", 1, 2], + ["a/**/+b", ""], + ["a/* Some comment */+b", ""], + ["a /* Some comment */ + b", " "], + ["a /* Some comment */ + b", " + ", 0, 2], + ["a/* */+b", ""], + ["a/**/ +b", " "], + ["a/**/ /**/+b", " "], + ["a/**/\n/**/+b", "\n"], + ["a /* comment 1 */\n/* comment #2 */ + b", " \n "], + ["a /* comment 1 */ \n \n /* comment #2 */ + b", " \n \n + ", 0, 2], + ["a +b", " "], + ["a[b] = c", "b", 1, 3], + ["a[b] = c;", "[b] ", 0, 4], + ["a[ b ] = 3;", " b ", 1, 3], + ["foo[ bar ] = /* commented 'value'*/ 0 ;", " 0 ", 4, 6], + ["foo/* comment here */[ bar ] = null ;", ""], + ["foo [ bar /* comment here */ ] = null ;", " bar ", 1, 3], + ["foo [ bar ] /* comment here */ /*and here*/ = null ;", " ", 3, 4], + ["foo [ /* comment here */ bar ] /* here */ = /* and here */ null ;", " [ bar ] = ", 0, 5] + ], (code, expected, firstTokenIndex, secondTokenIndex) => { + + it("should return text between tokens without inline comments", () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast), + firstIndex = firstTokenIndex || 0, + secondIndex = typeof secondTokenIndex === "number" ? secondTokenIndex : firstIndex + 1, + tokens = sourceCode.ast.tokens; + + assert.strictEqual( + sourceCode.getCleanTextBetweenTokens( + tokens[firstIndex], tokens[secondIndex] + ), + expected + ); + }); + }); + }); + describe("isSpaceBetweenTokens()", () => { leche.withData([