Repository: nzakas/eslint-plugin-typescript
Pull request #22 (2017-01-20 05:28:59)

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

weirdpattern: Hi @dannyfritz, could you please reference this PR from #5?
JamesHenry: LGTM! Thanks a lot @weirdpattern 

Patch:
diff --git a/README.md b/README.md
index 553fa41..d68d2d0 100644
--- a/README.md
+++ b/README.md
@@ -55,4 +55,5 @@ Then configure the rules you want to use under the rules section.
 * `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
+* `typescript/no-namespace` - disallows the use of custom TypeScript modules and namespaces.
+* `typescript/prefer-namespace-keyword` - enforces the use of the keyword `namespace` over `module` to declare custom TypeScript modules.
\ No newline at end of file
diff --git a/docs/rules/prefer-namespace-keyword.md b/docs/rules/prefer-namespace-keyword.md
new file mode 100644
index 0000000..e45f77b
--- /dev/null
+++ b/docs/rules/prefer-namespace-keyword.md
@@ -0,0 +1,22 @@
+# Enforces the use of the keyword `namespace` over `module` to declare custom TypeScript modules.
+
+In an effort to prevent further confusion between custom TypeScript modules and the new ES2015 modules, starting
+with TypeScript v1.5 the keyword `namespace` is now the preferred way to declare custom TypeScript modules. 
+
+## Rule Details
+
+This rule aims to standardise the way modules are declared.
+
+## 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-internal-module](https://palantir.github.io/tslint/rules/no-internal-module/)
diff --git a/lib/rules/prefer-namespace-keyword.js b/lib/rules/prefer-namespace-keyword.js
new file mode 100644
index 0000000..b990747
--- /dev/null
+++ b/lib/rules/prefer-namespace-keyword.js
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Enforces the use of the keyword `namespace` over `module` to declare custom TypeScript modules.
+ * @author Patricio Trevino
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "Enforces the use of the keyword namespace over module to declare custom TypeScript modules.",
+            category: "TypeScript"
+        },
+        fixable: "code",
+        schema: []
+    },
+
+    create: function(context) {
+
+        var sourceCode = context.getSourceCode();
+
+        //----------------------------------------------------------------------
+        // 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";
+        }
+
+        /**
+         * Gets the start index of the keyword `module`.
+         * @param {TSNode} node the node to be evaluated.
+         * @returns {number} the start index.
+         * @private
+         */
+        function getStartIndex(node) {
+            if (node.modifiers && node.modifiers.length > 0 && node.modifiers[0].type === "TSDeclareKeyword") {
+                return node.range[0] + "declare".length + 1;
+            }
+            return node.range[0];
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+        return {
+            TSModuleDeclaration: function(node) {
+                var declaration = sourceCode.getText(node);
+                if (isTypeScriptModuleDeclaration(node) || /\bnamespace\b/.test(declaration)) {
+                    return;
+                }
+
+                context.report({
+                    node,
+                    message: "Use namespace instead of module to declare custom TypeScript modules",
+                    fix(fixer) {
+                        var start = getStartIndex(node);
+                        return fixer.replaceTextRange([start, start + "module".length], "namespace");
+                    }
+                });
+            }
+        };
+    }
+};


Test patch:
diff --git a/tests/lib/rules/prefer-namespace-keyword.js b/tests/lib/rules/prefer-namespace-keyword.js
new file mode 100644
index 0000000..9b8d639
--- /dev/null
+++ b/tests/lib/rules/prefer-namespace-keyword.js
@@ -0,0 +1,87 @@
+/**
+ * @fileoverview Enforces the use of the keyword `namespace` over `module` to declare custom TypeScript modules.
+ * @author Patricio Trevino
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+var rule = require("../../../lib/rules/prefer-namespace-keyword"),
+    RuleTester = require("eslint").RuleTester;
+
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run("prefer-namespace-keyword", rule, {
+    valid: [
+        {
+            code: "declare module 'foo' { }",
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: "namespace foo { }",
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: "declare namespace foo { }",
+            parser: "typescript-eslint-parser"
+        }
+    ],
+    invalid: [
+        {
+            code: "module foo { }",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Use namespace instead of module to declare custom TypeScript modules",
+                    output: "namespace foo { }",
+                    row: 1,
+                    column: 1,
+                }
+            ]
+        },
+        {
+            code: "declare module foo { }",
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Use namespace instead of module to declare custom TypeScript modules",
+                    output: "declare namespace foo { }",
+                    row: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: `
+declare module foo {
+    declare module bar { } 
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "Use namespace instead of module to declare custom TypeScript modules",
+                    output: "declare namespace foo { }",
+                    row: 2,
+                    column: 1
+                },
+                {
+                    message: "Use namespace instead of module to declare custom TypeScript modules",
+                    output: `
+declare namespace foo {
+    declare namespace bar { } 
+}
+            `,
+                    row: 3,
+                    column: 5
+                }
+            ]
+        }
+    ]
+});