Skip to content

Commit

Permalink
feat(eslint-plugin): add new rule restrict-plus-operands (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
armano2 authored and JamesHenry committed Jan 22, 2019
1 parent 4e781f1 commit c541ede
Show file tree
Hide file tree
Showing 5 changed files with 421 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ You can also enable all the recommended rules at once. Add `plugin:@typescript-e
| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | :heavy_check_mark: | |
| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: |
| [`@typescript-eslint/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) | :heavy_check_mark: | :wrench: |
| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string. (`restrict-plus-operands` from TSLint) | | |
| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: |

<!-- end rule list -->
25 changes: 25 additions & 0 deletions packages/eslint-plugin/docs/rules/restrict-plus-operands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# When adding two variables, operands must both be of type number or of type string. (restrict-plus-operands)

Examples of **correct** code:

```ts
var foo = parseInt('5.5', 10) + 10;
```

Examples of **incorrect** code:

```ts
var foo = '5.5' + 5;
```

## Options

```json
{
"@typescript-eslint/restrict-plus-operands": "error"
}
```

## Compatibility

- TSLint: [restrict-plus-operands](https://palantir.github.io/tslint/rules/restrict-plus-operands/)
104 changes: 104 additions & 0 deletions packages/eslint-plugin/lib/rules/restrict-plus-operands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* @fileoverview When adding two variables, operands must both be of type number or of type string.
* @author James Henry
* @author Armano <https://github.com/armano2>
*/
'use strict';

const util = require('../util');

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'problem',
docs: {
description:
'When adding two variables, operands must both be of type number or of type string.',
extraDescription: [util.tslintRule('restrict-plus-operands')],
category: 'TypeScript',
url: util.metaDocsUrl('restrict-plus-operands')
},
messages: {
notNumbers:
"Operands of '+' operation must either be both strings or both numbers.",
notStrings:
"Operands of '+' operation must either be both strings or both numbers. Consider using a template literal."
},
schema: []
},

create(context) {
const service = util.getParserServices(context);

const typeChecker = service.program.getTypeChecker();

/**
* Helper function to get base type of node
* @param {ts.Type} type type to be evaluated
* @returns {*} string, number or invalid
*/
function getBaseTypeOfLiteralType(type) {
if (type.isNumberLiteral()) {
return 'number';
}
if (type.isStringLiteral()) {
return 'string';
}
if (type.isUnion()) {
const types = type.types.map(getBaseTypeOfLiteralType);

return types.every(value => value === types[0]) ? types[0] : 'invalid';
}

const stringType = typeChecker.typeToString(type);

if (stringType === 'number' || stringType === 'string') {
return stringType;
}
return 'invalid';
}

/**
* Helper function to get base type of node
* @param {ASTNode} node the node to be evaluated.
* @returns {*} string, number or invalid
*/
function getNodeType(node) {
const tsNode = service.esTreeNodeToTSNodeMap.get(node);
const type = typeChecker.getTypeAtLocation(tsNode);

return getBaseTypeOfLiteralType(type);
}

//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
"BinaryExpression[operator='+']"(node) {
const leftType = getNodeType(node.left);
const rightType = getNodeType(node.right);

if (
leftType === 'invalid' ||
rightType === 'invalid' ||
leftType !== rightType
) {
if (leftType === 'string' || rightType === 'string') {
context.report({
node,
messageId: 'notStrings'
});
} else {
context.report({
node,
messageId: 'notNumbers'
});
}
}
}
};
}
};
9 changes: 9 additions & 0 deletions packages/eslint-plugin/tests/fixtures/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"lib": ["es2015", "es2017"]
}
}
Loading

0 comments on commit c541ede

Please sign in to comment.