From cdb56c35002f7824b42bd964bbc8895807b4e4d2 Mon Sep 17 00:00:00 2001 From: Martin Probst Date: Tue, 26 Apr 2016 07:39:37 -0700 Subject: [PATCH] Support disallowing namespaces (noNamespace). (#1133) This allows code bases to outlaw pre-ES6 modules and namespaces. It's often still useful to declare namespaces when interacting with non-TypeScript code, so this has an option to allow "declare namespace" style usage. --- README.md | 2 + src/language/utils.ts | 11 ++++++ src/rules/noInternalModuleRule.ts | 9 +---- src/rules/noNamespaceRule.ts | 38 +++++++++++++++++++ src/tsconfig.json | 1 + .../allow-declarations/test.ts.lint | 3 ++ .../allow-declarations/tslint.json | 5 +++ test/rules/no-namespace/default/test.ts.lint | 21 ++++++++++ test/rules/no-namespace/default/tslint.json | 5 +++ test/tsconfig.json | 1 + 10 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/rules/noNamespaceRule.ts create mode 100644 test/rules/no-namespace/allow-declarations/test.ts.lint create mode 100644 test/rules/no-namespace/allow-declarations/tslint.json create mode 100644 test/rules/no-namespace/default/test.ts.lint create mode 100644 test/rules/no-namespace/default/tslint.json diff --git a/README.md b/README.md index 0db12e8b1ee..c2bf190b8d3 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,8 @@ A sample configuration file with all options is available [here](https://github. * `no-inferrable-types` disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. * `no-internal-module` disallows internal `module` (use `namespace` instead). * `no-invalid-this` disallows using the `this` keyword outside of classes. +* `no-namespace` disallows both internal `module`s and `namespace`, but allows ES6-style external modules. + * `allow-declarations` Allow `declare module ... {}` to describe external APIs. * `no-null-keyword` disallows use of the `null` keyword literal. * `no-reference` disallows `/// ` imports (use ES6-style imports instead). * `no-require-imports` disallows invocation of `require()` (use ES6-style imports instead). diff --git a/src/language/utils.ts b/src/language/utils.ts index c32c7131acb..a6c07ee9f6e 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -123,3 +123,14 @@ export function isNodeFlagSet(node: ts.Node, flagToCheck: ts.NodeFlags): boolean return (node.flags & flagToCheck) !== 0; /* tslint:enable:no-bitwise */ } + + +/** + * Returns true if decl is a nested module declaration, i.e. represents a segment of a dotted module path. + */ +export function isNestedModuleDeclaration(decl: ts.ModuleDeclaration) { + // in a declaration expression like 'module a.b.c' - 'a' is the top level module declaration node and 'b' and 'c' + // are nested therefore we can depend that a node's position will only match with its name's position for nested + // nodes + return decl.name.pos === decl.pos; +} diff --git a/src/rules/noInternalModuleRule.ts b/src/rules/noInternalModuleRule.ts index 33d6f204de2..47e9c626abe 100644 --- a/src/rules/noInternalModuleRule.ts +++ b/src/rules/noInternalModuleRule.ts @@ -38,19 +38,12 @@ class NoInternalModuleWalker extends Lint.RuleWalker { // an internal module declaration is not a namespace or a nested declaration // for external modules, node.name.kind will be a LiteralExpression instead of Identifier return !Lint.isNodeFlagSet(node, ts.NodeFlags.Namespace) - && !isNestedDeclaration(node) + && !Lint.isNestedModuleDeclaration(node) && node.name.kind === ts.SyntaxKind.Identifier && !isGlobalAugmentation(node); } } -function isNestedDeclaration(node: ts.ModuleDeclaration) { - // in a declaration expression like 'module a.b.c' - 'a' is the top level module declaration node and 'b' and 'c' - // are nested therefore we can depend that a node's position will only match with its name's position for nested - // nodes - return node.name.pos === node.pos; -} - function isGlobalAugmentation(node: ts.ModuleDeclaration) { // augmenting global uses a sepcial syntax that is allowed // see https://github.com/Microsoft/TypeScript/pull/6213 diff --git a/src/rules/noNamespaceRule.ts b/src/rules/noNamespaceRule.ts new file mode 100644 index 00000000000..47fff03db71 --- /dev/null +++ b/src/rules/noNamespaceRule.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright 2016 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 "../lint"; + +export class Rule extends Lint.Rules.AbstractRule { + public static FAILURE_STRING = "'namespace' and 'module' are disallowed"; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new NoNamespaceWalker(sourceFile, this.getOptions())); + } +} + +class NoNamespaceWalker extends Lint.RuleWalker { + public visitModuleDeclaration(decl: ts.ModuleDeclaration) { + super.visitModuleDeclaration(decl); + // declare module 'foo' {} is an external module, not a namespace. + if (decl.name.kind === ts.SyntaxKind.StringLiteral) { return; } + if (Lint.isNodeFlagSet(decl, ts.NodeFlags.Ambient) && this.hasOption("allow-declarations")) { return; } + if (Lint.isNestedModuleDeclaration(decl)) { return; } + this.addFailure(this.createFailure(decl.getStart(), decl.getWidth(), Rule.FAILURE_STRING)); + } +} diff --git a/src/tsconfig.json b/src/tsconfig.json index 5e82e44d27e..de8ab98f545 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -93,6 +93,7 @@ "rules/noInferrableTypesRule.ts", "rules/noInternalModuleRule.ts", "rules/noInvalidThisRule.ts", + "rules/noNamespaceRule.ts", "rules/noNullKeywordRule.ts", "rules/noReferenceRule.ts", "rules/noRequireImportsRule.ts", diff --git a/test/rules/no-namespace/allow-declarations/test.ts.lint b/test/rules/no-namespace/allow-declarations/test.ts.lint new file mode 100644 index 00000000000..8caa74ce5dc --- /dev/null +++ b/test/rules/no-namespace/allow-declarations/test.ts.lint @@ -0,0 +1,3 @@ +declare namespace Foo { + +} diff --git a/test/rules/no-namespace/allow-declarations/tslint.json b/test/rules/no-namespace/allow-declarations/tslint.json new file mode 100644 index 00000000000..b90b9030e34 --- /dev/null +++ b/test/rules/no-namespace/allow-declarations/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-namespace": [true, "allow-declarations"] + } +} diff --git a/test/rules/no-namespace/default/test.ts.lint b/test/rules/no-namespace/default/test.ts.lint new file mode 100644 index 00000000000..36688eb8f2b --- /dev/null +++ b/test/rules/no-namespace/default/test.ts.lint @@ -0,0 +1,21 @@ +namespace Foo { +~~~~~~~~~~~~~~~ +} +~ ['namespace' and 'module' are disallowed] +namespace Foo.Bar { +~~~~~~~~~~~~~~~~~~~ +} +~ ['namespace' and 'module' are disallowed] + +module Foo { +~~~~~~~~~~~~ +} +~ ['namespace' and 'module' are disallowed] + +declare namespace Foo { +~~~~~~~~~~~~~~~~~~~~~~~ +} +~ ['namespace' and 'module' are disallowed] + +declare module 'foo' { +} diff --git a/test/rules/no-namespace/default/tslint.json b/test/rules/no-namespace/default/tslint.json new file mode 100644 index 00000000000..6958cd0e9b5 --- /dev/null +++ b/test/rules/no-namespace/default/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-namespace": [true] + } +} diff --git a/test/tsconfig.json b/test/tsconfig.json index 5faeb31c5a2..21d5b1f6914 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -90,6 +90,7 @@ "../src/rules/noInferrableTypesRule.ts", "../src/rules/noInternalModuleRule.ts", "../src/rules/noInvalidThisRule.ts", + "../src/rules/noNamespaceRule.ts", "../src/rules/noNullKeywordRule.ts", "../src/rules/noReferenceRule.ts", "../src/rules/noRequireImportsRule.ts",