From 338b23f56b18795901a737563edbfcf11bd107a1 Mon Sep 17 00:00:00 2001 From: Doum Date: Tue, 11 May 2021 18:42:35 -0400 Subject: [PATCH] feat(space-infix-ops): Add support for Union and intersection of type declarations --- .../src/rules/space-infix-ops.ts | 121 +++++-- .../tests/rules/space-infix-ops.test.ts | 297 ++++++++++++++++++ 2 files changed, 391 insertions(+), 27 deletions(-) diff --git a/packages/eslint-plugin/src/rules/space-infix-ops.ts b/packages/eslint-plugin/src/rules/space-infix-ops.ts index e81d8ef25bb7..d0459a80b59b 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,37 @@ 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, @@ -49,6 +81,7 @@ export default util.createRule({ token => token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=', ); + const prev = sourceCode.getTokenBefore(operator!); const next = sourceCode.getTokenAfter(operator!); @@ -57,31 +90,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 +128,38 @@ 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 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 +168,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, + }, + ], + }, ], });