diff --git a/packages/eslint-plugin/src/rules/space-infix-ops.ts b/packages/eslint-plugin/src/rules/space-infix-ops.ts index e81d8ef25bb7..bae05a3501ad 100644 --- a/packages/eslint-plugin/src/rules/space-infix-ops.ts +++ b/packages/eslint-plugin/src/rules/space-infix-ops.ts @@ -1,4 +1,5 @@ import { + AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; @@ -34,6 +35,34 @@ 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 checkAndReportAssignmentSpace( node: TSESTree.Node, leftNode: TSESTree.Token, @@ -47,8 +76,9 @@ export default util.createRule({ leftNode, rightNode, token => - token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=', + token.type === AST_TOKEN_TYPES.Punctuator && token.value==="=", ); + const prev = sourceCode.getTokenBefore(operator!); const next = sourceCode.getTokenAfter(operator!); @@ -57,31 +87,7 @@ export default util.createRule({ (!sourceCode.isSpaceBetweenTokens(prev!, operator) || !sourceCode.isSpaceBetweenTokens(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 +125,40 @@ export default util.createRule({ checkAndReportAssignmentSpace(node, leftNode, rightNode); } + + /** + * Check if it is missing spaces between type annotaions chaining + * @param typeAnnotation TypeAnnotations list + */ + function checkForTypeAnnotationSpace(typeAnnotation: TSESTree.TSIntersectionType | TSESTree.TSUnionType): void { + const UNIONS = [ + "|", + "&", + ] + const types = typeAnnotation.types; + + types.forEach((type) => { + const operator = sourceCode.getTokenBefore(type); + + if (!!operator && UNIONS.includes(operator.value)) { + const prev = sourceCode.getTokenBefore(operator); + const next = sourceCode.getTokenAfter(operator); + + if ( + !sourceCode.isSpaceBetweenTokens(prev!, operator) || + !sourceCode.isSpaceBetweenTokens(operator, next!) + ) { + report(operator, operator) + } + } + }); + } + /** * Check if it has an assignment char and report if it's faulty * @param node The node to report */ - function checkForTypeAliasAssignmentSpace( + function checkForTypeAliasAssignmentAndTypeAnnotationSpace( node: TSESTree.TSTypeAliasDeclaration, ): void { const leftNode = sourceCode.getTokenByRangeStart(node.id.range[0])!; @@ -132,13 +167,44 @@ export default util.createRule({ ); checkAndReportAssignmentSpace(node, leftNode, rightNode); + if ( + node.typeAnnotation.type === AST_NODE_TYPES.TSUnionType + || node.typeAnnotation.type === AST_NODE_TYPES.TSIntersectionType + ) { + checkForTypeAnnotationSpace(node.typeAnnotation) + } } + /** + * Check if it has an assignment char and report if it's faulty + * @param node The node to report + */ + function checkForIntercaceDeclarationSpace( + node: TSESTree.TSInterfaceDeclaration, + ): void { + const properties = node.body.body; + + properties.forEach((prop: TSESTree.TypeElement) => { + if (prop.type === AST_NODE_TYPES.TSPropertySignature) { + const propTypeAnnotation = prop.typeAnnotation!; + + const typeAnnotation = propTypeAnnotation.typeAnnotation + if ( + typeAnnotation.type === AST_NODE_TYPES.TSUnionType + || typeAnnotation.type === AST_NODE_TYPES.TSIntersectionType + ) { + checkForTypeAnnotationSpace(typeAnnotation) + } + } + }); + } + return { ...rules, TSEnumMember: checkForEnumAssignmentSpace, ClassProperty: checkForClassPropertyAssignmentSpace, - TSTypeAliasDeclaration: checkForTypeAliasAssignmentSpace, + TSTypeAliasDeclaration: checkForTypeAliasAssignmentAndTypeAnnotationSpace, + TSInterfaceDeclaration: checkForIntercaceDeclarationSpace, }; }, }); 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 6beb4ae06fac..d3e141d29daf 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,88 @@ 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; + } + `, + }, ], invalid: [ { @@ -192,5 +269,225 @@ 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, + }, + ], + }, ], });