Skip to content
This repository was archived by the owner on Jan 19, 2019. It is now read-only.

Commit

Permalink
[FEAT] Add rule no-misused-new (#222)
Browse files Browse the repository at this point in the history
This PR implements rule [no-misused-new](https://palantir.github.io/tslint/rules/no-misused-new/)
  • Loading branch information
armano2 authored and bradzacher committed Dec 13, 2018
1 parent 2a5cd63 commit 19c4ff1
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ This guarantees 100% compatibility between the plugin and the parser.
* [`typescript/no-empty-interface`](./docs/rules/no-empty-interface.md) — Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint)
* [`typescript/no-explicit-any`](./docs/rules/no-explicit-any.md) — Disallow usage of the `any` type (`no-any` from TSLint)
* [`typescript/no-inferrable-types`](./docs/rules/no-inferrable-types.md) — Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint)
* [`typescript/no-misused-new`](./docs/rules/no-misused-new.md) — Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint)
* [`typescript/no-namespace`](./docs/rules/no-namespace.md) — Disallow the use of custom TypeScript modules and namespaces
* [`typescript/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) — Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint)
* [`typescript/no-parameter-properties`](./docs/rules/no-parameter-properties.md) — Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint)
Expand Down
41 changes: 41 additions & 0 deletions docs/rules/no-misused-new.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Enforce valid definition of `new` and `constructor`. (no-misused-new)

Warns on apparent attempts to define constructors for interfaces or `new` for classes.

## Rule Details

Examples of **incorrect** code for this rule.

```ts
class C {
new(): C;
}

interface I {
new(): I;
constructor(): void;
}
```

Examples of **correct** code for this rule.

```ts
class C {
constructor() {}
}
interface I {
new(): C;
}
```

## Options
```json
{
"typescript/no-misused-new": "error"
}
```


## Compatibility

* TSLint: [no-misused-new](https://palantir.github.io/tslint/rules/no-misused-new/)
109 changes: 109 additions & 0 deletions lib/rules/no-misused-new.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* @fileoverview Enforce valid definition of `new` and `constructor`.
* @author Armano <https://github.com/armano2>
*/
"use strict";

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

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

module.exports = {
meta: {
docs: {
description: "Enforce valid definition of `new` and `constructor`.",
extraDescription: [util.tslintRule("no-misused-new")],
category: "TypeScript",
url:
"https://github.com/bradzacher/eslint-plugin-typescript/blob/master/docs/rules/no-misused-new.md",
},
schema: [],
messages: {
errorMessageInterface:
"Interfaces cannot be constructed, only classes.",
errorMessageClass: "Class cannon have method named `new`.",
},
},

//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------

create(context) {
/**
* @param {ASTNode} node type to be inspected.
* @returns {string|null} name of simple type or null
*/
function getTypeReferenceName(node) {
if (node) {
switch (node.type) {
case "TSTypeAnnotation":
return getTypeReferenceName(node.typeAnnotation);
case "TSTypeReference":
return getTypeReferenceName(node.typeName);
case "Identifier":
return node.name;
default:
break;
}
}
return null;
}

/**
* @param {ASTNode} parent parent node.
* @param {ASTNode} returnType type to be compared
* @returns {boolean} returns true if type is parent type
*/
function isMatchingParentType(parent, returnType) {
if (parent && parent.id && parent.id.type === "Identifier") {
return getTypeReferenceName(returnType) === parent.id.name;
}
return false;
}

return {
"TSInterfaceBody > TSConstructSignature"(node) {
if (
isMatchingParentType(
node.parent.parent,
node.typeAnnotation
)
) {
// constructor
context.report({
node,
messageId: "errorMessageInterface",
});
}
},
"TSMethodSignature[key.name='constructor']"(node) {
context.report({
node,
messageId: "errorMessageInterface",
});
},
"ClassBody > MethodDefinition[key.name='new']"(node) {
if (
node.value &&
(node.value.type === "TSEmptyBodyFunctionExpression" ||
node.value.type === "TSEmptyBodyFunctionDeclaration")
) {
if (
isMatchingParentType(
node.parent.parent,
node.value.returnType
)
) {
context.report({
node,
messageId: "errorMessageClass",
});
}
}
},
};
},
};
141 changes: 141 additions & 0 deletions tests/lib/rules/no-misused-new.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* @fileoverview Enforce valid definition of `new` and `constructor`.
* @author Armano <https://github.com/armano2>
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/no-misused-new"),
RuleTester = require("eslint").RuleTester;

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({
parser: "typescript-eslint-parser",
});

ruleTester.run("no-misused-new", rule, {
valid: [
`
declare abstract class C {
foo () {
}
get new ();
bar();
}
`,
`
class C {
constructor();
}
`,
`
class C {
constructor() {}
}
`,
// OK if there's a body
`
class C {
new() {}
}
`,
// OK if return type is not the interface.
`
interface I {
new(): {};
}
`,
// 'new' OK in type literal (we don't know the type name)
`
type T = {
new(): T;
}
`,
],
invalid: [
{
code: `
interface I {
new(): I;
constructor(): void;
}
`,
errors: [
{
messageId: "errorMessageInterface",
line: 3,
column: 5,
},
{
messageId: "errorMessageInterface",
line: 4,
column: 5,
},
],
},
// Works for generic type.
{
code: `
interface G {
new<T>(): G<T>;
}
`,
errors: [
{
messageId: "errorMessageInterface",
line: 3,
column: 5,
},
],
},
// 'constructor' flagged.
{
code: `
type T = {
constructor(): void;
}
`,
errors: [
{
messageId: "errorMessageInterface",
line: 3,
column: 5,
},
],
},
{
code: `
class C {
new(): C;
}
`,
errors: [
{
messageId: "errorMessageClass",
line: 3,
column: 5,
},
],
},
{
code: `
declare abstract class C {
new(): C;
}
`,
errors: [
{
messageId: "errorMessageClass",
line: 3,
column: 5,
},
],
},
],
});

0 comments on commit 19c4ff1

Please sign in to comment.