diff --git a/docs/_data/rules.json b/docs/_data/rules.json index 5d9d3357c99..fb60777226d 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -715,6 +715,18 @@ "typescriptOnly": true, "requiresTypeInfo": true }, + { + "ruleName": "no-interface-constructor", + "description": "Warns on apparent attempts to define constructors for interfaces.", + "rationale": "`interface I { new(): I }` declares a type where for `x: I`, `new x()` is also of type `I`.", + "optionsDescription": "Not configurable.", + "options": null, + "optionExamples": [ + "true" + ], + "type": "functionality", + "typescriptOnly": true + }, { "ruleName": "no-internal-module", "description": "Disallows internal `module`", diff --git a/docs/rules/no-interface-constructor/index.html b/docs/rules/no-interface-constructor/index.html new file mode 100644 index 00000000000..b2f5778f784 --- /dev/null +++ b/docs/rules/no-interface-constructor/index.html @@ -0,0 +1,14 @@ +--- +ruleName: no-interface-constructor +description: Warns on apparent attempts to define constructors for interfaces. +rationale: '`interface I { new(): I }` declares a type where for `x: I`, `new x()` is also of type `I`.' +optionsDescription: Not configurable. +options: null +optionExamples: + - 'true' +type: functionality +typescriptOnly: true +layout: rule +title: 'Rule: no-interface-constructor' +optionsJSON: 'null' +--- \ No newline at end of file diff --git a/src/rules/noInterfaceConstructorRule.ts b/src/rules/noInterfaceConstructorRule.ts new file mode 100644 index 00000000000..e9f1b922a5e --- /dev/null +++ b/src/rules/noInterfaceConstructorRule.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2017 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as ts from "typescript"; + +import * as Lint from "../index"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "no-interface-constructor", + description: "Warns on apparent attempts to define constructors for interfaces.", + rationale: "`interface I { new(): I }` declares a type where for `x: I`, `new x()` is also of type `I`.", + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], + type: "functionality", + typescriptOnly: true, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING = "Interfaces cannot be constructed, only classes. Did you mean `declare class`?"; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new Walker(sourceFile, this.getOptions())); + } +} + +class Walker extends Lint.RuleWalker { + public visitConstructSignature(node: ts.ConstructSignatureDeclaration) { + const iface = node.parent as ts.InterfaceDeclaration; + if (iface.kind !== ts.SyntaxKind.InterfaceDeclaration) { + return; + } + + const name = iface.name.text; + if (node.type.kind !== ts.SyntaxKind.TypeReference) { + return; + } + + const typeName = (node.type as ts.TypeReferenceNode).typeName; + if (typeName.kind === ts.SyntaxKind.Identifier && (typeName as ts.Identifier).text === name) { + this.addFailureAtNode(node, Rule.FAILURE_STRING); + } + } +} diff --git a/test/rules/no-interface-constructor/test.ts.lint b/test/rules/no-interface-constructor/test.ts.lint new file mode 100644 index 00000000000..31dec00ea98 --- /dev/null +++ b/test/rules/no-interface-constructor/test.ts.lint @@ -0,0 +1,22 @@ +interface I { + new(): I; + ~~~~~~~~~ [0] +} + +// Works for generic type. +interface G { + new(): G; + ~~~~~~~~~~~~~~~ [0] +} + +// OK if return type is not the interface. +interface J { + new(): I; +} + +// OK in type literal. +type T = { + new(): T; +} + +[0]: Interfaces cannot be constructed, only classes. Did you mean `declare class`? diff --git a/test/rules/no-interface-constructor/tslint.json b/test/rules/no-interface-constructor/tslint.json new file mode 100644 index 00000000000..25925cc0c45 --- /dev/null +++ b/test/rules/no-interface-constructor/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-interface-constructor": true + } +}