Skip to content

Commit b2c17b5

Browse files
armano2JamesHenry
authored andcommitted
[FEAT] Add rule no-misused-new (typescript-eslint#222)
This PR implements rule [no-misused-new](https://palantir.github.io/tslint/rules/no-misused-new/)
1 parent 757daaf commit b2c17b5

File tree

4 files changed

+292
-0
lines changed

4 files changed

+292
-0
lines changed

packages/eslint-plugin-typescript/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ This guarantees 100% compatibility between the plugin and the parser.
6565
* [`typescript/no-empty-interface`](./docs/rules/no-empty-interface.md) — Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint)
6666
* [`typescript/no-explicit-any`](./docs/rules/no-explicit-any.md) — Disallow usage of the `any` type (`no-any` from TSLint)
6767
* [`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)
68+
* [`typescript/no-misused-new`](./docs/rules/no-misused-new.md) — Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint)
6869
* [`typescript/no-namespace`](./docs/rules/no-namespace.md) — Disallow the use of custom TypeScript modules and namespaces
6970
* [`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)
7071
* [`typescript/no-parameter-properties`](./docs/rules/no-parameter-properties.md) — Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Enforce valid definition of `new` and `constructor`. (no-misused-new)
2+
3+
Warns on apparent attempts to define constructors for interfaces or `new` for classes.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule.
8+
9+
```ts
10+
class C {
11+
new(): C;
12+
}
13+
14+
interface I {
15+
new(): I;
16+
constructor(): void;
17+
}
18+
```
19+
20+
Examples of **correct** code for this rule.
21+
22+
```ts
23+
class C {
24+
constructor() {}
25+
}
26+
interface I {
27+
new(): C;
28+
}
29+
```
30+
31+
## Options
32+
```json
33+
{
34+
"typescript/no-misused-new": "error"
35+
}
36+
```
37+
38+
39+
## Compatibility
40+
41+
* TSLint: [no-misused-new](https://palantir.github.io/tslint/rules/no-misused-new/)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* @fileoverview Enforce valid definition of `new` and `constructor`.
3+
* @author Armano <https://github.com/armano2>
4+
*/
5+
"use strict";
6+
7+
const util = require("../util");
8+
9+
//------------------------------------------------------------------------------
10+
// Rule Definition
11+
//------------------------------------------------------------------------------
12+
13+
module.exports = {
14+
meta: {
15+
docs: {
16+
description: "Enforce valid definition of `new` and `constructor`.",
17+
extraDescription: [util.tslintRule("no-misused-new")],
18+
category: "TypeScript",
19+
url:
20+
"https://github.com/bradzacher/eslint-plugin-typescript/blob/master/docs/rules/no-misused-new.md",
21+
},
22+
schema: [],
23+
messages: {
24+
errorMessageInterface:
25+
"Interfaces cannot be constructed, only classes.",
26+
errorMessageClass: "Class cannon have method named `new`.",
27+
},
28+
},
29+
30+
//----------------------------------------------------------------------
31+
// Public
32+
//----------------------------------------------------------------------
33+
34+
create(context) {
35+
/**
36+
* @param {ASTNode} node type to be inspected.
37+
* @returns {string|null} name of simple type or null
38+
*/
39+
function getTypeReferenceName(node) {
40+
if (node) {
41+
switch (node.type) {
42+
case "TSTypeAnnotation":
43+
return getTypeReferenceName(node.typeAnnotation);
44+
case "TSTypeReference":
45+
return getTypeReferenceName(node.typeName);
46+
case "Identifier":
47+
return node.name;
48+
default:
49+
break;
50+
}
51+
}
52+
return null;
53+
}
54+
55+
/**
56+
* @param {ASTNode} parent parent node.
57+
* @param {ASTNode} returnType type to be compared
58+
* @returns {boolean} returns true if type is parent type
59+
*/
60+
function isMatchingParentType(parent, returnType) {
61+
if (parent && parent.id && parent.id.type === "Identifier") {
62+
return getTypeReferenceName(returnType) === parent.id.name;
63+
}
64+
return false;
65+
}
66+
67+
return {
68+
"TSInterfaceBody > TSConstructSignature"(node) {
69+
if (
70+
isMatchingParentType(
71+
node.parent.parent,
72+
node.typeAnnotation
73+
)
74+
) {
75+
// constructor
76+
context.report({
77+
node,
78+
messageId: "errorMessageInterface",
79+
});
80+
}
81+
},
82+
"TSMethodSignature[key.name='constructor']"(node) {
83+
context.report({
84+
node,
85+
messageId: "errorMessageInterface",
86+
});
87+
},
88+
"ClassBody > MethodDefinition[key.name='new']"(node) {
89+
if (
90+
node.value &&
91+
(node.value.type === "TSEmptyBodyFunctionExpression" ||
92+
node.value.type === "TSEmptyBodyFunctionDeclaration")
93+
) {
94+
if (
95+
isMatchingParentType(
96+
node.parent.parent,
97+
node.value.returnType
98+
)
99+
) {
100+
context.report({
101+
node,
102+
messageId: "errorMessageClass",
103+
});
104+
}
105+
}
106+
},
107+
};
108+
},
109+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* @fileoverview Enforce valid definition of `new` and `constructor`.
3+
* @author Armano <https://github.com/armano2>
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
const rule = require("../../../lib/rules/no-misused-new"),
12+
RuleTester = require("eslint").RuleTester;
13+
14+
//------------------------------------------------------------------------------
15+
// Tests
16+
//------------------------------------------------------------------------------
17+
18+
const ruleTester = new RuleTester({
19+
parser: "typescript-eslint-parser",
20+
});
21+
22+
ruleTester.run("no-misused-new", rule, {
23+
valid: [
24+
`
25+
declare abstract class C {
26+
foo () {
27+
}
28+
get new ();
29+
bar();
30+
}
31+
`,
32+
`
33+
class C {
34+
constructor();
35+
}
36+
`,
37+
`
38+
class C {
39+
constructor() {}
40+
}
41+
`,
42+
// OK if there's a body
43+
`
44+
class C {
45+
new() {}
46+
}
47+
`,
48+
// OK if return type is not the interface.
49+
`
50+
interface I {
51+
new(): {};
52+
}
53+
`,
54+
// 'new' OK in type literal (we don't know the type name)
55+
`
56+
type T = {
57+
new(): T;
58+
}
59+
`,
60+
],
61+
invalid: [
62+
{
63+
code: `
64+
interface I {
65+
new(): I;
66+
constructor(): void;
67+
}
68+
`,
69+
errors: [
70+
{
71+
messageId: "errorMessageInterface",
72+
line: 3,
73+
column: 5,
74+
},
75+
{
76+
messageId: "errorMessageInterface",
77+
line: 4,
78+
column: 5,
79+
},
80+
],
81+
},
82+
// Works for generic type.
83+
{
84+
code: `
85+
interface G {
86+
new<T>(): G<T>;
87+
}
88+
`,
89+
errors: [
90+
{
91+
messageId: "errorMessageInterface",
92+
line: 3,
93+
column: 5,
94+
},
95+
],
96+
},
97+
// 'constructor' flagged.
98+
{
99+
code: `
100+
type T = {
101+
constructor(): void;
102+
}
103+
`,
104+
errors: [
105+
{
106+
messageId: "errorMessageInterface",
107+
line: 3,
108+
column: 5,
109+
},
110+
],
111+
},
112+
{
113+
code: `
114+
class C {
115+
new(): C;
116+
}
117+
`,
118+
errors: [
119+
{
120+
messageId: "errorMessageClass",
121+
line: 3,
122+
column: 5,
123+
},
124+
],
125+
},
126+
{
127+
code: `
128+
declare abstract class C {
129+
new(): C;
130+
}
131+
`,
132+
errors: [
133+
{
134+
messageId: "errorMessageClass",
135+
line: 3,
136+
column: 5,
137+
},
138+
],
139+
},
140+
],
141+
});

0 commit comments

Comments
 (0)