diff --git a/packages/eslint-plugin/src/rules/space-infix-ops.ts b/packages/eslint-plugin/src/rules/space-infix-ops.ts index e81d8ef25bb..3a0a79ff1a3 100644 --- a/packages/eslint-plugin/src/rules/space-infix-ops.ts +++ b/packages/eslint-plugin/src/rules/space-infix-ops.ts @@ -8,6 +8,8 @@ import * as util from '../util'; export type Options = util.InferOptionsTypeFromRule; export type MessageIds = util.InferMessageIdsTypeFromRule; +const UNIONS = ['|', '&']; + export default util.createRule({ name: 'space-infix-ops', meta: { @@ -34,6 +36,41 @@ export default util.createRule({ const rules = baseRule.create(context); const sourceCode = context.getSourceCode(); + const report = ( + node: TSESTree.Node | TSESTree.Token, + operator: TSESTree.Token, + ): void => { + context.report({ + node: node, + loc: operator.loc, + messageId: 'missingSpace', + data: { + operator: operator.value, + }, + fix(fixer) { + const previousToken = sourceCode.getTokenBefore(operator); + const afterToken = sourceCode.getTokenAfter(operator); + let fixString = ''; + + if (operator.range[0] - previousToken!.range[1] === 0) { + fixString = ' '; + } + + fixString += operator.value; + + if (afterToken!.range[0] - operator.range[1] === 0) { + fixString += ' '; + } + + return fixer.replaceText(operator, fixString); + }, + }); + }; + + function isSpaceChar(token: TSESTree.Token): boolean { + return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '='; + } + function checkAndReportAssignmentSpace( node: TSESTree.Node, leftNode: TSESTree.Token, @@ -46,42 +83,17 @@ export default util.createRule({ const operator = sourceCode.getFirstTokenBetween( leftNode, rightNode, - token => - token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=', + isSpaceChar, ); + const prev = sourceCode.getTokenBefore(operator!); const next = sourceCode.getTokenAfter(operator!); if ( - operator && - (!sourceCode.isSpaceBetweenTokens(prev!, operator) || - !sourceCode.isSpaceBetweenTokens(operator, next!)) + !sourceCode.isSpaceBetween!(prev!, operator!) || + !sourceCode.isSpaceBetween!(operator!, next!) ) { - context.report({ - node: node, - loc: operator.loc, - messageId: 'missingSpace', - data: { - operator: operator.value, - }, - fix(fixer) { - const previousToken = sourceCode.getTokenBefore(operator); - const afterToken = sourceCode.getTokenAfter(operator); - let fixString = ''; - - if (operator.range[0] - previousToken!.range[1] === 0) { - fixString = ' '; - } - - fixString += operator.value; - - if (afterToken!.range[0] - operator.range[1] === 0) { - fixString += ' '; - } - - return fixer.replaceText(operator, fixString); - }, - }); + report(node, operator!); } } @@ -119,11 +131,37 @@ export default util.createRule({ checkAndReportAssignmentSpace(node, leftNode, rightNode); } + /** + * Check if it is missing spaces between type annotations chaining + * @param typeAnnotation TypeAnnotations list + */ + function checkForTypeAnnotationSpace( + typeAnnotation: TSESTree.TSIntersectionType | TSESTree.TSUnionType, + ): void { + const types = typeAnnotation.types; + + types.forEach(type => { + const operator = sourceCode.getTokenBefore(type); + + if (operator != null && UNIONS.includes(operator.value)) { + const prev = sourceCode.getTokenBefore(operator); + const next = sourceCode.getTokenAfter(operator); + + if ( + !sourceCode.isSpaceBetween!(prev!, operator) || + !sourceCode.isSpaceBetween!(operator, next!) + ) { + report(typeAnnotation, operator); + } + } + }); + } + /** * Check if it has an assignment char and report if it's faulty * @param node The node to report */ - function checkForTypeAliasAssignmentSpace( + function checkForTypeAliasAssignment( node: TSESTree.TSTypeAliasDeclaration, ): void { const leftNode = sourceCode.getTokenByRangeStart(node.id.range[0])!; @@ -138,7 +176,9 @@ export default util.createRule({ ...rules, TSEnumMember: checkForEnumAssignmentSpace, ClassProperty: checkForClassPropertyAssignmentSpace, - TSTypeAliasDeclaration: checkForTypeAliasAssignmentSpace, + TSTypeAliasDeclaration: checkForTypeAliasAssignment, + TSUnionType: checkForTypeAnnotationSpace, + TSIntersectionType: checkForTypeAnnotationSpace, }; }, }); diff --git a/packages/eslint-plugin/tests/rules/space-infix-ops.test.ts b/packages/eslint-plugin/tests/rules/space-infix-ops.test.ts index 6beb4ae06fa..5ad92555d88 100644 --- a/packages/eslint-plugin/tests/rules/space-infix-ops.test.ts +++ b/packages/eslint-plugin/tests/rules/space-infix-ops.test.ts @@ -54,11 +54,110 @@ ruleTester.run('space-infix-ops', rule, { } `, }, + { + code: ` + type Test = string; + `, + }, { code: ` type Test = string | boolean; `, }, + { + code: ` + type Test = string & boolean; + `, + }, + { + code: ` + class Test { + private value:number | string = 1; + } + `, + }, + { + code: ` + class Test { + private value:number & string = 1; + } + `, + }, + { + code: ` + type Test = + | string + | boolean; + `, + }, + { + code: ` + type Test = + & string + & boolean; + `, + }, + { + code: ` + interface Test { + prop: + & string + & boolean; + } + `, + }, + { + code: ` + interface Test { + prop: + | string + | boolean; + } + `, + }, + { + code: ` + interface Test { + props: string; + } + `, + }, + { + code: ` + interface Test { + props: string | boolean; + } + `, + }, + { + code: ` + interface Test { + props: string & boolean; + } + `, + }, + { + code: ` + const x: string & number; + `, + }, + { + code: ` + class Test { + value: string & number; + } + `, + }, + { + code: ` + function foo() {} + `, + }, + { + code: ` + function bar(): string & number {} + `, + }, ], invalid: [ { @@ -192,5 +291,447 @@ ruleTester.run('space-infix-ops', rule, { }, ], }, + { + code: ` + type Test = string| number; + `, + output: ` + type Test = string | number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 27, + line: 2, + }, + ], + }, + { + code: ` + type Test = string |number; + `, + output: ` + type Test = string | number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 28, + line: 2, + }, + ], + }, + { + code: ` + type Test = string &number; + `, + output: ` + type Test = string & number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 28, + line: 2, + }, + ], + }, + { + code: ` + type Test = string& number; + `, + output: ` + type Test = string & number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 27, + line: 2, + }, + ], + }, + { + code: ` + type Test = + |string + | number; + `, + output: ` + type Test = + | string + | number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 9, + line: 3, + }, + ], + }, + { + code: ` + type Test = + &string + & number; + `, + output: ` + type Test = + & string + & number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 9, + line: 3, + }, + ], + }, + { + code: ` + interface Test { + prop: string| number; + } + `, + output: ` + interface Test { + prop: string | number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 23, + line: 3, + }, + ], + }, + { + code: ` + interface Test { + prop: string |number; + } + `, + output: ` + interface Test { + prop: string | number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 24, + line: 3, + }, + ], + }, + { + code: ` + interface Test { + prop: string &number; + } + `, + output: ` + interface Test { + prop: string & number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 24, + line: 3, + }, + ], + }, + { + code: ` + interface Test { + prop: string& number; + } + `, + output: ` + interface Test { + prop: string & number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 23, + line: 3, + }, + ], + }, + { + code: ` + interface Test { + prop: + |string + | number; + } + `, + output: ` + interface Test { + prop: + | string + | number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 13, + line: 4, + }, + ], + }, + { + code: ` + interface Test { + prop: + &string + & number; + } + `, + output: ` + interface Test { + prop: + & string + & number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 13, + line: 4, + }, + ], + }, + { + code: ` + const x: string &number; + `, + output: ` + const x: string & number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 25, + line: 2, + }, + ], + }, + { + code: ` + const x: string& number; + `, + output: ` + const x: string & number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 24, + line: 2, + }, + ], + }, + { + code: ` + const x: string| number; + `, + output: ` + const x: string | number; + `, + errors: [ + { + messageId: 'missingSpace', + column: 24, + line: 2, + }, + ], + }, + { + code: ` + class Test { + value: string |number; + } + `, + output: ` + class Test { + value: string | number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 25, + line: 3, + }, + ], + }, + { + code: ` + class Test { + value: string& number; + } + `, + output: ` + class Test { + value: string & number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 24, + line: 3, + }, + ], + }, + { + code: ` + class Test { + value: string| number; + } + `, + output: ` + class Test { + value: string | number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 24, + line: 3, + }, + ], + }, + { + code: ` + function foo() {} + `, + output: ` + function foo() {} + `, + errors: [ + { + messageId: 'missingSpace', + column: 39, + line: 2, + }, + ], + }, + { + code: ` + function foo() {} + `, + output: ` + function foo() {} + `, + errors: [ + { + messageId: 'missingSpace', + column: 38, + line: 2, + }, + ], + }, + { + code: ` + function foo() {} + `, + output: ` + function foo() {} + `, + errors: [ + { + messageId: 'missingSpace', + column: 39, + line: 2, + }, + ], + }, + { + code: ` + function foo() {} + `, + output: ` + function foo() {} + `, + errors: [ + { + messageId: 'missingSpace', + column: 38, + line: 2, + }, + ], + }, + { + code: ` + function bar(): string &number {} + `, + output: ` + function bar(): string & number {} + `, + errors: [ + { + messageId: 'missingSpace', + column: 32, + line: 2, + }, + ], + }, + { + code: ` + function bar(): string& number {} + `, + output: ` + function bar(): string & number {} + `, + errors: [ + { + messageId: 'missingSpace', + column: 31, + line: 2, + }, + ], + }, + { + code: ` + function bar(): string |number {} + `, + output: ` + function bar(): string | number {} + `, + errors: [ + { + messageId: 'missingSpace', + column: 32, + line: 2, + }, + ], + }, + { + code: ` + function bar(): string| number {} + `, + output: ` + function bar(): string | number {} + `, + errors: [ + { + messageId: 'missingSpace', + column: 31, + line: 2, + }, + ], + }, ], });