diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js index e9441ad1708..d860ae0f04b 100644 --- a/lib/rules/keyword-spacing.js +++ b/lib/rules/keyword-spacing.js @@ -109,6 +109,8 @@ module.exports = { create(context) { const sourceCode = context.getSourceCode(); + const tokensToIgnore = new WeakSet(); + /** * Reports a given token if there are not space(s) before the token. * @param {Token} token A token to report. @@ -121,6 +123,7 @@ module.exports = { if (prevToken && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && !isOpenParenOfTemplate(prevToken) && + !tokensToIgnore.has(prevToken) && astUtils.isTokenOnSameLine(prevToken, token) && !sourceCode.isSpaceBetweenTokens(prevToken, token) ) { @@ -147,6 +150,7 @@ module.exports = { if (prevToken && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && !isOpenParenOfTemplate(prevToken) && + !tokensToIgnore.has(prevToken) && astUtils.isTokenOnSameLine(prevToken, token) && sourceCode.isSpaceBetweenTokens(prevToken, token) ) { @@ -173,6 +177,7 @@ module.exports = { if (nextToken && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && !isCloseParenOfTemplate(nextToken) && + !tokensToIgnore.has(nextToken) && astUtils.isTokenOnSameLine(token, nextToken) && !sourceCode.isSpaceBetweenTokens(token, nextToken) ) { @@ -199,6 +204,7 @@ module.exports = { if (nextToken && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && !isCloseParenOfTemplate(nextToken) && + !tokensToIgnore.has(nextToken) && astUtils.isTokenOnSameLine(token, nextToken) && sourceCode.isSpaceBetweenTokens(token, nextToken) ) { @@ -584,7 +590,14 @@ module.exports = { ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, MethodDefinition: checkSpacingForProperty, PropertyDefinition: checkSpacingForProperty, - Property: checkSpacingForProperty + Property: checkSpacingForProperty, + + // To avoid conflicts with `space-infix-ops`, e.g. `a > this.b` + "BinaryExpression[operator='>']"(node) { + const operatorToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken); + + tokensToIgnore.add(operatorToken); + } }; } }; diff --git a/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-no-space.js b/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-no-space.js new file mode 100644 index 00000000000..9021044801c --- /dev/null +++ b/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-no-space.js @@ -0,0 +1,125 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@5.0.0 + * + * Source code: + * this.blah + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TSTypeAssertion", + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "Thing", + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 }, + }, + }, + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 }, + }, + }, + expression: { + type: "MemberExpression", + object: { + type: "ThisExpression", + range: [7, 11], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 11 }, + }, + }, + property: { + type: "Identifier", + name: "blah", + range: [12, 16], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 16 }, + }, + }, + computed: false, + optional: false, + range: [7, 16], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 16 }, + }, + }, + range: [0, 16], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 16 }, + }, + }, + range: [0, 16], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 16 }, + }, + }, + ], + sourceType: "script", + range: [0, 16], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 16 } }, + tokens: [ + { + type: "Punctuator", + value: "<", + range: [0, 1], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + }, + { + type: "Identifier", + value: "Thing", + range: [1, 6], + loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 6 } }, + }, + { + type: "Punctuator", + value: ">", + range: [6, 7], + loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } }, + }, + { + type: "Keyword", + value: "this", + range: [7, 11], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 11 }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [11, 12], + loc: { + start: { line: 1, column: 11 }, + end: { line: 1, column: 12 }, + }, + }, + { + type: "Identifier", + value: "blah", + range: [12, 16], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 16 }, + }, + }, + ], + comments: [], +}); diff --git a/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-space.js b/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-space.js new file mode 100644 index 00000000000..00ae0f60ed5 --- /dev/null +++ b/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-space.js @@ -0,0 +1,125 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@5.0.0 + * + * Source code: + * this.blah + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TSTypeAssertion", + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "Thing", + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 }, + }, + }, + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 }, + }, + }, + expression: { + type: "MemberExpression", + object: { + type: "ThisExpression", + range: [8, 12], + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 12 }, + }, + }, + property: { + type: "Identifier", + name: "blah", + range: [13, 17], + loc: { + start: { line: 1, column: 13 }, + end: { line: 1, column: 17 }, + }, + }, + computed: false, + optional: false, + range: [8, 17], + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 17 }, + }, + }, + range: [0, 17], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 17 }, + }, + }, + range: [0, 17], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 17 }, + }, + }, + ], + sourceType: "script", + range: [0, 17], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } }, + tokens: [ + { + type: "Punctuator", + value: "<", + range: [0, 1], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + }, + { + type: "Identifier", + value: "Thing", + range: [1, 6], + loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 6 } }, + }, + { + type: "Punctuator", + value: ">", + range: [6, 7], + loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } }, + }, + { + type: "Keyword", + value: "this", + range: [8, 12], + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 12 }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [12, 13], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 13 }, + }, + }, + { + type: "Identifier", + value: "blah", + range: [13, 17], + loc: { + start: { line: 1, column: 13 }, + end: { line: 1, column: 17 }, + }, + }, + ], + comments: [], +}); diff --git a/tests/lib/rules/keyword-spacing.js b/tests/lib/rules/keyword-spacing.js index 0dfcae6431c..05e58d7e1ae 100644 --- a/tests/lib/rules/keyword-spacing.js +++ b/tests/lib/rules/keyword-spacing.js @@ -11,7 +11,8 @@ const parser = require("../../fixtures/fixture-parser"), rule = require("../../../lib/rules/keyword-spacing"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + fixtureParser = require("../../fixtures/fixture-parser"); //------------------------------------------------------------------------------ // Helpers @@ -259,6 +260,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` { code: "async function wrap() { a =await a }", parserOptions: { ecmaVersion: 8 } }, { code: "async function wrap() { a = await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, + { code: "async function wrap() { a+await a }", parserOptions: { ecmaVersion: 8 } }, + { code: "async function wrap() { a + await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, + { code: "async function wrap() { aawait a }", parserOptions: { ecmaVersion: 8 } }, + { code: "async function wrap() { a > await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, // not conflict with `space-unary-ops` { code: "async function wrap() { !await'a' }", parserOptions: { ecmaVersion: 8 } }, @@ -366,6 +373,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` { code: "a =class {}", parserOptions: { ecmaVersion: 6 } }, { code: "a = class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "a+class {}", parserOptions: { ecmaVersion: 6 } }, + { code: "a + class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "aclass {}", parserOptions: { ecmaVersion: 6 } }, + { code: "a > class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, // not conflict with `space-unary-ops` { code: "!class {}", parserOptions: { ecmaVersion: 6 } }, @@ -501,6 +514,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =delete foo.a", { code: "a = delete foo.a", options: [NEITHER] }, + "a+delete foo.a", + { code: "a + delete foo.a", options: [NEITHER] }, + "adelete foo.a", + { code: "a > delete foo.a", options: [NEITHER] }, // not conflict with `space-unary-ops` "!delete(foo.a)", @@ -698,6 +717,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =function() {}", { code: "a = function() {}", options: [NEITHER] }, + "a+function() {}", + { code: "a + function() {}", options: [NEITHER] }, + "afunction() {}", + { code: "a > function() {}", options: [NEITHER] }, // not conflict with `space-unary-ops` "!function() {}", @@ -860,6 +885,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =new foo()", { code: "a = new foo()", options: [NEITHER] }, + "a+new foo()", + { code: "a + new foo()", options: [NEITHER] }, + "anew foo()", + { code: "a > new foo()", options: [NEITHER] }, // not conflict with `space-unary-ops` "!new(foo)()", @@ -888,7 +919,16 @@ ruleTester.run("keyword-spacing", rule, { //---------------------------------------------------------------------- "function foo() { {} return +a }", + { + code: "function foo() { return

; }", + parserOptions: { ecmaFeatures: { jsx: true } } + }, { code: "function foo() { {}return+a }", options: [NEITHER] }, + { + code: "function foo() { return

; }", + options: [{ after: false }], + parserOptions: { ecmaFeatures: { jsx: true } } + }, { code: "function foo() { {} return +a }", options: [override("return", BOTH)] }, { code: "function foo() { {}return+a }", options: [override("return", NEITHER)] }, "function foo() {\nreturn\n}", @@ -995,6 +1035,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` { code: "class A extends B { constructor() { b =super() } }", parserOptions: { ecmaVersion: 6 } }, { code: "class A extends B { constructor() { b = super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "class A extends B { constructor() { b+super() } }", parserOptions: { ecmaVersion: 6 } }, + { code: "class A extends B { constructor() { b + super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "class A extends B { constructor() { bsuper() } }", parserOptions: { ecmaVersion: 6 } }, + { code: "class A extends B { constructor() { b > super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, // not conflict with `space-unary-ops` { code: "class A extends B { constructor() { !super() } }", parserOptions: { ecmaVersion: 6 } }, @@ -1034,6 +1080,16 @@ ruleTester.run("keyword-spacing", rule, { { code: "{} this[a]", options: [override("this", BOTH)] }, { code: "{}this[a]", options: [override("this", NEITHER)] }, + { + code: " this.blah", + parser: fixtureParser("keyword-spacing", "prefix-cast-operator-space") + }, + { + code: "this.blah", + options: [override("this", { before: false })], + parser: fixtureParser("keyword-spacing", "prefix-cast-operator-no-space") + }, + // not conflict with `array-bracket-spacing` "[this]", { code: "[ this ]", options: [NEITHER] }, @@ -1071,6 +1127,18 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =this", { code: "a = this", options: [NEITHER] }, + "a+this", + { code: "a + this", options: [NEITHER] }, + "athis", + { code: "a > this", options: [NEITHER] }, + "this+a", + { code: "this + a", options: [NEITHER] }, + "thisa", + { code: "this > a", options: [NEITHER] }, // not conflict with `space-unary-ops` "!this", @@ -1166,6 +1234,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =typeof foo", { code: "a = typeof foo", options: [NEITHER] }, + "a+typeof foo", + { code: "a + typeof foo", options: [NEITHER] }, + "atypeof foo", + { code: "a > typeof foo", options: [NEITHER] }, // not conflict with `space-unary-ops` "!typeof+foo", @@ -1243,6 +1317,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =void foo", { code: "a = void foo", options: [NEITHER] }, + "a+void foo", + { code: "a + void foo", options: [NEITHER] }, + "avoid foo", + { code: "a > void foo", options: [NEITHER] }, // not conflict with `space-unary-ops` "!void+foo", @@ -2783,12 +2863,25 @@ ruleTester.run("keyword-spacing", rule, { output: "function foo() { {} return +a }", errors: expectedBeforeAndAfter("return") }, + { + code: "function foo() { return

; }", + output: "function foo() { return

; }", + parserOptions: { ecmaFeatures: { jsx: true } }, + errors: expectedAfter("return") + }, { code: "function foo() { {} return +a }", output: "function foo() { {}return+a }", options: [NEITHER], errors: unexpectedBeforeAndAfter("return") }, + { + code: "function foo() { return

; }", + output: "function foo() { return

; }", + options: [{ after: false }], + parserOptions: { ecmaFeatures: { jsx: true } }, + errors: unexpectedAfter("return") + }, { code: "function foo() { {}return+a }", output: "function foo() { {} return +a }", @@ -3038,6 +3131,19 @@ ruleTester.run("keyword-spacing", rule, { options: [override("this", NEITHER)], errors: unexpectedBefore("this") }, + { + code: " this.blah", + output: "this.blah", + options: [override("this", { before: false })], + parser: fixtureParser("keyword-spacing", "prefix-cast-operator-space"), + errors: unexpectedBefore("this") + }, + { + code: "this.blah", + output: " this.blah", + parser: fixtureParser("keyword-spacing", "prefix-cast-operator-no-space"), + errors: expectedBefore("this") + }, //---------------------------------------------------------------------- // throw