Repository: nzakas/eslint-plugin-typescript
Pull request #21 (2017-01-13 05:30:19)

New: Add rule no-namespace
This includes

New rule no-namespace
Corresponding test
Corresponding documentation
Update to README.md to include the new rule

weirdpattern: Hi @dannyfritz, could you please reference this PR from #5
weirdpattern: thinking about splitting this into two separate rules by removing the allowLegacyExternalModule, will work on this tonight...
weirdpattern: Ok, this is ready for review

Patch:
diff --git a/README.md b/README.md
index 552ad51..553fa41 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,8 @@ Then configure the rules you want to use under the rules section.
 
 * `typescript/type-annotation-spacing` - enforces one space after the colon and zero spaces before the colon of a type annotation.
 * `typescript/explicit-member-accessibility` - enforces accessibility modifiers on class properties and methods.
-* `typescript/interface-name-prefix` - Enforces interface names are prefixed.
+* `typescript/interface-name-prefix` - enforces interface names are prefixed.
 * `typescript/no-triple-slash-reference` - enforces `/// <reference />` is not used.
 * `typescript/no-explicit-any` - enforces the any type is not used.
 * `typescript/no-angle-bracket-type-assertion` - enforces the use of `as Type` assertions instead of `<Type>` assertions.
+* `typescript/no-namespace` - disallows the use of custom TypeScript modules and namespaces.
\ No newline at end of file
diff --git a/docs/rules/no-namespace.md b/docs/rules/no-namespace.md
new file mode 100644
index 0000000..6d0456b
--- /dev/null
+++ b/docs/rules/no-namespace.md
@@ -0,0 +1,96 @@
+# Disallows the use of `custom TypeScript modules` and `namespaces`.
+
+Custom TypeScript modules (`module foo {}`) and namespaces (`namespace foo {}`) are considered outdated 
+ways to organize TypeScript code. ES2015 module syntax is now preferred (`import`/`export`).   
+
+This rule still allows the use of TypeScript module declarations to describe external APIs (`declare module 'foo' {}`).
+
+## Rule Details
+
+This rule aims to standardise the way modules are declared.
+
+## Options
+
+This rule, in its default state, does not require any argument. If you would like to enable one 
+or more of the following you may pass an object with the options set as follows:
+- `allowDeclarations` set to `true` will allow you to `declare` custom TypeScript modules and namespaces (Default: `false`).  
+- `allowDefinitionFiles` set to `true` will allow you to `declare` and use custom TypeScript modules and namespaces 
+inside definition files (Default: `false`).       
+
+Examples of **incorrect** code for the default `{ "allowDeclarations": false, "allowDefinitionFiles": false }` options:
+```ts
+module foo {}
+namespace foo {}
+
+declare module foo {}
+declare namespace foo {}
+
+// anything inside a d.ts file
+```
+
+Examples of **correct** code for the default `{ "allowDeclarations": false, "allowDefinitionFiles": false }` options:
+```ts
+declare module 'foo' {}
+```
+
+### allowDeclarations
+Examples of **incorrect** code for the `{ "allowDeclarations": true }` option:
+```ts
+module foo {}
+namespace foo {}
+```
+
+Examples of **correct** code for the `{ "allowDeclarations": true }` option:
+```ts
+declare module 'foo' {}
+declare module foo {}
+declare namespace foo {}
+```
+
+Examples of **incorrect** code for the `{ "allowDeclarations": false }` option:
+```ts
+module foo {}
+namespace foo {}
+declare module foo {}
+declare namespace foo {}
+```
+
+Examples of **correct** code for the `{ "allowDeclarations": false }` option:
+```ts
+declare module 'foo' {}
+```
+
+### allowDefinitionFiles
+Examples of **incorrect** code for the `{ "allowDefinitionFiles": true }` option:
+```ts
+// if outside a d.ts file
+module foo {}
+namespace foo {}
+
+// if outside a d.ts file and allowDeclarations = false
+module foo {}
+namespace foo {}
+declare module foo {}
+declare namespace foo {}
+```
+
+Examples of **correct** code for the `{ "allowDefinitionFiles": true }` option:
+```ts
+declare module 'foo' {}
+
+// anything inside a d.ts file
+```
+
+## When Not To Use It
+
+If you are using the ES2015 module syntax, then you will not need this rule.
+
+## Further Reading
+
+* [Modules](https://www.typescriptlang.org/docs/handbook/modules.html)  
+* [Namespaces](https://www.typescriptlang.org/docs/handbook/namespaces.html)  
+* [Namespaces and Modules](https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html)  
+
+## Compatibility
+
+* TSLint: [no-namespace](https://palantir.github.io/tslint/rules/no-namespace/)
diff --git a/lib/rules/no-angle-bracket-type-assertion.js b/lib/rules/no-angle-bracket-type-assertion.js
index 988ee1e..3045fd1 100644
--- a/lib/rules/no-angle-bracket-type-assertion.js
+++ b/lib/rules/no-angle-bracket-type-assertion.js
@@ -19,17 +19,18 @@ module.exports = {
 
     create: function(context) {
 
+        var sourceCode = context.getSourceCode();
+
         //----------------------------------------------------------------------
         // Public
         //----------------------------------------------------------------------
         return {
             TSTypeAssertionExpression: function(node) {
-                const type = node.typeAnnotation.typeAnnotation.typeName.name;
                 context.report({
                     node,
                     message: "Prefer 'as {{cast}}' instead of '<{{cast}}>' when doing type assertions",
                     data: {
-                        cast: type
+                        cast: sourceCode.getText(node.typeAnnotation.typeAnnotation)
                     }
                 });
             }
diff --git a/lib/rules/no-namespace.js b/lib/rules/no-namespace.js
new file mode 100644
index 0000000..ee87fdd
--- /dev/null
+++ b/lib/rules/no-namespace.js
@@ -0,0 +1,91 @@
+/**
+ * @fileoverview Disallows the use of custom TypeScript modules and namespaces.
+ * @author Patricio Trevino
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "Disallows the use of custom TypeScript modules and namespaces.",
+            category: "TypeScript"
+        },
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowDeclarations: {
+                        type: "boolean"
+                    },
+                    allowDefinitionFiles: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ]
+    },
+
+    create: function(context) {
+
+        var allowDeclarations = context.options[0] ? context.options[0].allowDeclarations : false;
+        var allowDefinitionFiles = context.options[0] ? context.options[0].allowDefinitionFiles : false;
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Determines if node is a TypeScript module declaration (instead of a namespace/module).
+         * @param {ASTNode} node the node to be evaluated.
+         * @returns {boolean} true when node is an external declaration.
+         * @private
+         */
+        function isTypeScriptModuleDeclaration(node) {
+            return node.name && node.name.type === "Literal";
+        }
+
+        /**
+         * Determines if node is a module/namespace declaration.
+         * @param {ASTNode} node the node to be evaluated.
+         * @returns {boolean} true when dealing with declarations.
+         * @private
+         */
+        function isDeclaration(node) {
+            var hasDeclareModifier = (node.modifiers || []).filter(m => m.type === "TSDeclareKeyword").length > 0;
+            return hasDeclareModifier && !isTypeScriptModuleDeclaration(node);
+        }
+
+        /**
+         * Determines if node is part of a declaration file (d.ts).
+         * @returns {boolean} true when dealing with declaration files.
+         * @private
+         */
+        function isDefinitionFile() {
+            var filename = context.getFilename();
+            return filename ? filename.slice(-5).toLowerCase() === ".d.ts" : false;
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+        return {
+            TSModuleDeclaration: function(node) {
+                if (isTypeScriptModuleDeclaration(node) ||
+                    (allowDefinitionFiles && isDefinitionFile()) ||
+                    (allowDeclarations && isDeclaration(node))) {
+                    return;
+                }
+
+                context.report({
+                    node,
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces"
+                });
+            }
+        };
+    }
+};


Test patch:
diff --git a/tests/lib/rules/no-angle-bracket-type-assertion.js b/tests/lib/rules/no-angle-bracket-type-assertion.js
index 3603793..775ef87 100644
--- a/tests/lib/rules/no-angle-bracket-type-assertion.js
+++ b/tests/lib/rules/no-angle-bracket-type-assertion.js
@@ -31,7 +31,74 @@ class Generic<T> implements Foo {}
 
 const foo = {} as Foo<int>;
 const bar = new Generic<int>() as Foo;
-const array : Array<string> = [];
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: "const array : Array<string> = [];",
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: "const array : Array<string> = [];",
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: "const a : number = 5 as number",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Prefer 'as number' instead of '<number>' when doing type assertions",
+                    row: 1,
+                    column: 20
+                }
+            ]
+        },
+        {
+            code: `
+const a : number = 5;
+const b : number = a as number;
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Prefer 'as number' instead of '<number>' when doing type assertions",
+                    row: 3,
+                    column: 20
+                }
+            ]
+        },
+        {
+            code: "const a : Array<number> = [1] as Array<number>;",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Prefer 'as Array<number>' instead of '<Array<number>>' when doing type assertions",
+                    row: 1,
+                    column: 27
+                }
+            ]
+        },
+        {
+            code: `
+class A {}
+class B extends A {}
+
+const b : B = new B();
+const a : A = b as A;
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+type A = {
+    num: number
+};
+
+const b = {
+    num: 5
+};
+
+const a: A = b as A;
             `,
             parser: "typescript-eslint-parser"
         }
@@ -48,7 +115,6 @@ class Generic<T> implements Foo {}
 
 const foo = <Foo>{};
 const bar = <Foo>new Generic<int>();
-const array : Array<string> = [];
             `,
             parser: "typescript-eslint-parser",
             errors: [
@@ -63,6 +129,80 @@ const array : Array<string> = [];
                     column: 13
                 },
             ]
+        },
+        {
+            code: "const a : number = <number>5",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Prefer 'as number' instead of '<number>' when doing type assertions",
+                    row: 1,
+                    column: 20
+                }
+            ]
+        },
+        {
+            code: `
+const a : number = 5;
+const b : number = <number>a;
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Prefer 'as number' instead of '<number>' when doing type assertions",
+                    row: 3,
+                    column: 20
+                }
+            ]
+        },
+        {
+            code: "const a : Array<number> = <Array<number>>[1];",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Prefer 'as Array<number>' instead of '<Array<number>>' when doing type assertions",
+                    row: 1,
+                    column: 27
+                }
+            ]
+        },
+        {
+            code: `
+class A {}
+class B extends A {}
+
+const b : B = new B();
+const a : A = <A>b;
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Prefer 'as A' instead of '<A>' when doing type assertions",
+                    row: 6,
+                    column: 15
+                }
+            ]
+        },
+        {
+            code: `
+type A = {
+    num: number
+};
+
+const b = {
+    num: 5
+};
+
+const a: A = <A>b;
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Prefer 'as A' instead of '<A>' when doing type assertions",
+                    row: 9,
+                    column: 14
+                }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/no-namespace.js b/tests/lib/rules/no-namespace.js
new file mode 100644
index 0000000..4db7215
--- /dev/null
+++ b/tests/lib/rules/no-namespace.js
@@ -0,0 +1,131 @@
+/**
+ * @fileoverview Disallows the use of custom TypeScript modules and namespaces.
+ * @author Patricio Trevino
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+var rule = require("../../../lib/rules/no-namespace"),
+    RuleTester = require("eslint").RuleTester;
+
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run("no-namespace", rule, {
+    valid: [
+        {
+            code: "declare module 'foo' { }",
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: "declare module foo { }",
+            options: [{ allowDeclarations: true }],
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: "declare namespace foo { }",
+            options: [{ allowDeclarations: true }],
+            parser: "typescript-eslint-parser"
+        }
+    ],
+    invalid: [
+        {
+            code: "module foo {}",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: "namespace foo {}",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: "module foo {}",
+            options: [{ allowDeclarations: false }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: "namespace foo {}",
+            options: [{ allowDeclarations: false }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: "declare module foo { }",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: "declare namespace foo { }",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: "declare module foo {}",
+            options: [{ allowDeclarations: false }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: "declare namespace foo {}",
+            options: [{ allowDeclarations: false }],
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        }
+    ]
+});