diff --git a/README.md b/README.md index 6a6d9ae..35f2fa3 100644 --- a/README.md +++ b/README.md @@ -73,5 +73,6 @@ Then configure the rules you want to use under the rules section. * [`typescript/no-use-before-define`](./docs/rules/no-use-before-define.md) — Disallow the use of variables before they are defined * [`typescript/no-var-requires`](./docs/rules/no-var-requires.md) — Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) * [`typescript/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) — Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) +* [`typescript/space-infix-ops`](./docs/rules/space-infix-ops.md) — Require spacing around infix operators in TypeScript-specific constructs (to be used in tandem with the code `space-infix-ops` rule) * [`typescript/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) — Require consistent spacing around type annotations diff --git a/docs/rules/space-infix-ops.md b/docs/rules/space-infix-ops.md new file mode 100644 index 0000000..39f9907 --- /dev/null +++ b/docs/rules/space-infix-ops.md @@ -0,0 +1,40 @@ +# Require spacing around infix operators in TypeScript-specific constructs (to be used in tandem with the code `space-infix-ops` rule) (space-infix-ops) + +While formatting preferences are very personal, a number of style guides require spaces around operators, such as: + +```ts +type id = number; +type Perhaps = T | null; +``` + +This rule is intended to be used together with the core rule [`space-infix-ops`](https://eslint.org/docs/rules/space-infix-ops). + +## Rule Details + +This rule aims to enforce spaces around infix operators in TypeScript-specific constructs. + +Examples of **incorrect** code: + +```ts +type id=number; +type id =number; +type id= number; +type Perhaps=T | null; +type Perhaps =T | null; +type Perhaps= T | null; +``` + +Examples of **correct** code: +```ts +type id = number; +type Perhaps = T | null; +``` + +## When Not To Use It + +If you don't want to enforce spacing for infix ops, you can safely turn this rule off. + +## Further Reading + +* [Advanced Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html) +* [core `space-infix-ops` rule](https://eslint.org/docs/rules/space-infix-ops) diff --git a/lib/rules/space-infix-ops.js b/lib/rules/space-infix-ops.js new file mode 100644 index 0000000..8da9872 --- /dev/null +++ b/lib/rules/space-infix-ops.js @@ -0,0 +1,110 @@ +/** + * @fileoverview Require spaces around infix operators + * @author Michael Ficarra + * @author Bence Dányi + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: + "Require spacing around infix operators in TypeScript-specific constructs (to be used in tandem with the code `space-infix-ops` rule)", + category: "Stylistic Issues", + recommended: false, + url: + "https://github.com/nzakas/eslint-plugin-typescript/blob/master/docs/rules/space-infix-ops.md" + }, + fixable: "whitespace" + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + /** + * Returns the first token which violates the rule + * @param {ASTNode} left - The left node of the main node + * @param {ASTNode} right - The right node of the main node + * @returns {Object} The violator token or null + * @private + */ + function getFirstNonSpacedToken(left, right) { + const tokens = sourceCode.getTokensBetween(left, right, 1); + + for (let i = 1, l = tokens.length - 1; i < l; ++i) { + const op = tokens[i]; + + if ( + op.type === "Punctuator" && + op.value === "=" && + (tokens[i - 1].range[1] >= op.range[0] || + op.range[1] >= tokens[i + 1].range[0]) + ) { + return op; + } + } + return null; + } + + /** + * Reports an AST node as a rule violation + * @param {ASTNode} mainNode - The node to report + * @param {Object} culpritToken - The token which has a problem + * @returns {void} + * @private + */ + function report(mainNode, culpritToken) { + context.report({ + node: mainNode, + loc: culpritToken.loc.start, + message: "Infix operators must be spaced.", + fix(fixer) { + const previousToken = sourceCode.getTokenBefore( + culpritToken + ); + const afterToken = sourceCode.getTokenAfter(culpritToken); + let fixString = ""; + + if (culpritToken.range[0] - previousToken.range[1] === 0) { + fixString = " "; + } + + fixString += culpritToken.value; + + if (afterToken.range[0] - culpritToken.range[1] === 0) { + fixString += " "; + } + + return fixer.replaceText(culpritToken, fixString); + } + }); + } + + /** + * Check if the node is a type alias + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkAlias(node) { + if (node.parent.kind !== "type") return; + const leftNode = node.typeParameters + ? node.typeParameters + : node.id; + const rightNode = node.init; + const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode); + + if (nonSpacedNode) { + report(node, nonSpacedNode); + } + } + + return { + VariableDeclarator: checkAlias + }; + } +}; diff --git a/tests/lib/rules/space-infix-ops.js b/tests/lib/rules/space-infix-ops.js new file mode 100644 index 0000000..6f23878 --- /dev/null +++ b/tests/lib/rules/space-infix-ops.js @@ -0,0 +1,86 @@ +/** + * @fileoverview Enforces spacing around type annotations + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/space-infix-ops"), + RuleTester = require("eslint").RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: "typescript-eslint-parser" +}); + +ruleTester.run("space-infix-ops", rule, { + valid: ["type Foo = string;", "type Foo = T;"], + invalid: [ + { + code: "type Foo=number;", + errors: [ + { + message: "Infix operators must be spaced.", + line: 1, + column: 9 + } + ] + }, + { + code: "type Foo =number;", + errors: [ + { + message: "Infix operators must be spaced.", + line: 1, + column: 10 + } + ] + }, + { + code: "type Foo= number;", + errors: [ + { + message: "Infix operators must be spaced.", + line: 1, + column: 9 + } + ] + }, + { + code: "type Foo=T;", + errors: [ + { + message: "Infix operators must be spaced.", + line: 1, + column: 12 + } + ] + }, + { + code: "type Foo =T;", + errors: [ + { + message: "Infix operators must be spaced.", + line: 1, + column: 13 + } + ] + }, + { + code: "type Foo= T;", + errors: [ + { + message: "Infix operators must be spaced.", + line: 1, + column: 12 + } + ] + } + ] +});