Repository: nzakas/eslint-plugin-typescript
Pull request #37 (2017-08-10 05:50:51)

New: Add adjacent-overload-signatures
This includes

New rule adjacent-overload-signatures
Corresponding test
Corresponding documentation
Update to README.md to include the new rule

Related to #5 (TSLint compatibility)

weirdpattern: Hmm.. I will rebase this tonight and fix the lint issue.. I probably fat-fingered something... 
weirdpattern: Ok, rebased and linted...

Patch:
diff --git a/README.md b/README.md
index 639d9e7..46f0b61 100644
--- a/README.md
+++ b/README.md
@@ -61,3 +61,4 @@ Then configure the rules you want to use under the rules section.
 * `typescript/no-type-literal` - disallows the use of type aliases.
 * `typescript/member-ordering` - enforces a standard member declaration order.
 * `typescript/no-unused-vars` - prevents TypeScript-specific constructs from being erroneously flagged as unused
+* `typescript/adjacent-overload-signatures` - enforces member overloads to be consecutive.
diff --git a/docs/rules/adjacent-overload-signatures.md b/docs/rules/adjacent-overload-signatures.md
new file mode 100644
index 0000000..c89a622
--- /dev/null
+++ b/docs/rules/adjacent-overload-signatures.md
@@ -0,0 +1,79 @@
+# Enforces member overloads to be consecutive.
+
+Grouping overloaded members (methods and functions) together can improve readability of the code.
+
+## Rule Details
+
+This rule aims to standardise the way overloaded members are organized.
+
+The following patterns are considered warnings:
+
+```ts
+declare namespace Foo {
+    export function foo(s: string): void;
+    export function foo(n: number): void;
+    export function bar(): void;
+    export function foo(sn: string | number): void;
+}
+
+type Foo = {
+  foo(s: string): void;
+  foo(n: number): void;
+  bar(): void;
+  foo(sn: string | number): void;
+}
+
+interface Foo {
+  foo(s: string): void;
+  foo(n: number): void;
+  bar(): void;
+  foo(sn: string | number): void;
+}
+
+class Foo {
+  foo(s: string): void;
+  foo(n: number): void;
+  bar(): void {}
+  foo(sn: string | number): void {}
+}
+```
+
+The following patterns are not warnings:
+
+```ts
+declare namespace Foo {
+    export function foo(s: string): void;
+    export function foo(n: number): void;
+    export function foo(sn: string | number): void;
+    export function bar(): void;    
+}
+
+type Foo = {
+  foo(s: string): void;
+  foo(n: number): void;  
+  foo(sn: string | number): void;
+  bar(): void;
+}
+
+interface Foo {
+  foo(s: string): void;
+  foo(n: number): void;  
+  foo(sn: string | number): void;
+  bar(): void;
+}
+
+class Foo {
+  foo(s: string): void;
+  foo(n: number): void;  
+  foo(sn: string | number): void {}
+  bar(): void {}
+}
+```
+
+## When Not To Use It
+
+If you don't care about the general structure of the code, then you will not need this rule.
+
+## Compatibility
+
+* TSLint: [adjacent-overload-signatures](https://palantir.github.io/tslint/rules/adjacent-overload-signatures/)
\ No newline at end of file
diff --git a/lib/rules/adjacent-overload-signatures.js b/lib/rules/adjacent-overload-signatures.js
new file mode 100644
index 0000000..962ce69
--- /dev/null
+++ b/lib/rules/adjacent-overload-signatures.js
@@ -0,0 +1,99 @@
+/**
+ * @fileoverview Enforces member overloads to be consecutive.
+ * @author Patricio Trevino
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "Enforces member overloads to be consecutive",
+            category: "TypeScript"
+        },
+        schema: []
+    },
+
+    create(context) {
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Gets the name of the member being processed.
+         * @param {ASTNode} member the member being processed.
+         * @returns {string} the name of the member or null if it's a member not relevant to the rule.
+         * @private
+         */
+        function getMemberName(member) {
+            switch (member.type) {
+                case "ExportNamedDeclaration": {
+                    return member.declaration.id.name;
+                }
+                case "DeclareFunction":
+                case "FunctionDeclaration":
+                case "TSNamespaceFunctionDeclaration": {
+                    return member.id.name;
+                }
+                case "TSMethodSignature": {
+                    return member.key && (member.key.name || member.key.value) ||
+                           member.name && (member.name.name || member.name.value);
+                }
+                case "TSCallSignature": {
+                    return "call";
+                }
+                case "MethodDefinition": {
+                    return member.key.name || member.key.value;
+                }
+                default: {
+                    return null;
+                }
+            }
+        }
+
+        /**
+         * Check the body for overload methods.
+         * @param {ASTNode} node the body to be inspected.
+         * @returns {void}
+         * @private
+         */
+        function checkBodyForOverloadMethods(node) {
+            const members = node.body || node.members;
+
+            if (members) {
+                const seen = [];
+                let index, name, lastName;
+
+                members.forEach(member => {
+                    name = getMemberName(member);
+                    index = seen.indexOf(name);
+                    if (index > -1 && lastName !== name) {
+                        context.report({
+                            node: member,
+                            message: `All '${name}' signatures should be adjacent`
+                        });
+                    } else if (index === -1) {
+                        seen.push(name);
+                    }
+
+                    lastName = name;
+                });
+            }
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+        return {
+            TSModuleBlock: checkBodyForOverloadMethods,
+            TSTypeLiteral: checkBodyForOverloadMethods,
+            TSInterfaceBody: checkBodyForOverloadMethods,
+            ClassBody: checkBodyForOverloadMethods,
+            Program: checkBodyForOverloadMethods
+        };
+    }
+};


Test patch:
diff --git a/tests/lib/rules/adjacent-overload-signatures.js b/tests/lib/rules/adjacent-overload-signatures.js
new file mode 100644
index 0000000..1d9abb3
--- /dev/null
+++ b/tests/lib/rules/adjacent-overload-signatures.js
@@ -0,0 +1,633 @@
+/**
+ * @fileoverview Enforces member overloads to be consecutive.
+ * @author Patricio Trevino
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/adjacent-overload-signatures"),
+    RuleTester = require("eslint").RuleTester;
+
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+
+ruleTester.run("adjacent-overload-signatures", rule, {
+    valid: [
+        {
+            code: `
+function foo(s: string) {}
+function foo(n: number) {}
+function foo(sn: string | number) {}
+function bar(): void {}
+function baz(): void {}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+declare function foo(s: string);
+declare function foo(n: number);
+declare function foo(sn: string | number);
+declare function bar(): void;
+declare function baz(): void;
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+declare module "Foo" {
+    export function foo(s: string): void;
+    export function foo(n: number): void;
+    export function foo(sn: string | number): void;
+    export function bar(): void;
+    export function baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+declare namespace Foo {
+    export function foo(s: string): void;
+    export function foo(n: number): void;
+    export function foo(sn: string | number): void;
+    export function bar(): void;
+    export function baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+type Foo = {
+    foo(s: string): void;
+    foo(n: number): void;
+    foo(sn: string | number): void;
+    bar(): void;
+    baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+type Foo = {
+    foo(s: string): void;
+    ["foo"](n: number): void;
+    foo(sn: string | number): void;
+    bar(): void;
+    baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+interface Foo {
+    (s: string): void;
+    (n: number): void;
+    (sn: string | number): void;
+    foo(n: number): void;
+    bar(): void;
+    baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+interface Foo {
+    foo(s: string): void;
+    foo(n: number): void;
+    foo(sn: string | number): void;
+    bar(): void;
+    baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+interface Foo {
+    foo(s: string): void;
+    ["foo"](n: number): void;
+    foo(sn: string | number): void;
+    bar(): void;
+    baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+interface Foo {
+    foo(): void;
+    bar: {
+        baz(s: string): void;
+        baz(n: number): void;
+        baz(sn: string | number): void;
+    }
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    constructor(s: string);
+    constructor(n: number);
+    constructor(sn: string | number) {}
+    bar(): void {}
+    baz(): void {}
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    foo(s: string): void;
+    foo(n: number): void;
+    foo(sn: string | number): void {}
+    bar(): void {}
+    baz(): void {}
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    foo(s: string): void;
+    ["foo"](n: number): void;
+    foo(sn: string | number): void {}
+    bar(): void {}
+    baz(): void {}
+}
+            `,
+            parser: "typescript-eslint-parser"
+        },
+        {
+            code: `
+class Foo {
+    name: string;
+    foo(s: string): void;
+    foo(n: number): void;
+    foo(sn: string | number): void {}
+    bar(): void {}
+    baz(): void {}
+}
+            `,
+            parser: "typescript-eslint-parser"
+        }
+    ],
+    invalid: [
+        {
+            code: `
+function foo(s: string) {}
+function foo(n: number) {}
+function bar(): void {}
+function baz(): void {}
+function foo(sn: string | number) {}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 6,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: `
+function foo(s: string) {}
+function foo(n: number) {}
+const a = "";
+const b = "";
+function foo(sn: string | number) {}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 6,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: `
+declare function foo(s: string);
+declare function foo(n: number);
+declare function bar(): void;
+declare function baz(): void;
+declare function foo(sn: string | number);
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 6,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: `
+declare function foo(s: string);
+declare function foo(n: number);
+const a = "";
+const b = "";
+declare function foo(sn: string | number);
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 6,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: `
+declare module "Foo" {
+    export function foo(s: string): void;
+    export function foo(n: number): void;
+    export function bar(): void;
+    export function baz(): void;    
+    export function foo(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+declare module "Foo" {
+    export function foo(s: string): void;
+    export function foo(n: number): void;
+    export function foo(sn: string | number): void;
+    function baz(s: string): void;
+    export function bar(): void;
+    function baz(n: number): void;
+    function baz(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'baz' signatures should be adjacent",
+                    line: 8,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+declare namespace Foo {
+    export function foo(s: string): void;    
+    export function foo(n: number): void;
+    export function bar(): void;
+    export function baz(): void;
+    export function foo(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+declare namespace Foo {
+    export function foo(s: string): void;
+    export function foo(n: number): void;
+    export function foo(sn: string | number): void;
+    function baz(s: string): void;
+    export function bar(): void;
+    function baz(n: number): void;
+    function baz(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'baz' signatures should be adjacent",
+                    line: 8,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+type Foo = {
+    foo(s: string): void;
+    foo(n: number): void;
+    bar(): void;
+    baz(): void;
+    foo(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+type Foo = {
+    foo(s: string): void;
+    ["foo"](n: number): void;
+    bar(): void;
+    baz(): void;
+    foo(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+type Foo = {
+    foo(s: string): void;
+    name: string;
+    foo(n: number): void;
+    foo(sn: string | number): void;
+    bar(): void;
+    baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 5,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+interface Foo {
+    (s: string): void;
+    foo(n: number): void;
+    (n: number): void;
+    (sn: string | number): void;
+    bar(): void;
+    baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'call' signatures should be adjacent",
+                    line: 5,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+interface Foo {
+    foo(s: string): void;
+    foo(n: number): void;
+    bar(): void;
+    baz(): void;
+    foo(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+interface Foo {
+    foo(s: string): void;
+    ["foo"](n: number): void;
+    bar(): void;
+    baz(): void;
+    foo(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+interface Foo {
+    foo(s: string): void;
+    "foo"(n: number): void;
+    bar(): void;
+    baz(): void;
+    foo(sn: string | number): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+interface Foo {
+    foo(s: string): void;
+    name: string;
+    foo(n: number): void;
+    foo(sn: string | number): void;
+    bar(): void;
+    baz(): void;
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 5,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+interface Foo {
+    foo(): void;
+    bar: {
+        baz(s: string): void;
+        baz(n: number): void;
+        foo(): void;
+        baz(sn: string | number): void;
+    }
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'baz' signatures should be adjacent",
+                    line: 8,
+                    column: 9
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(s: string);
+    constructor(n: number);
+    bar(): void {}
+    baz(): void {}
+    constructor(sn: string | number) {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'constructor' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    foo(s: string): void;
+    foo(n: number): void;
+    bar(): void {}
+    baz(): void {}
+    foo(sn: string | number): void {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    foo(s: string): void;
+    ["foo"](n: number): void;
+    bar(): void {}
+    baz(): void {}
+    foo(sn: string | number): void {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    foo(s: string): void;
+    "foo"(n: number): void;
+    bar(): void {}
+    baz(): void {}
+    foo(sn: string | number): void {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 7,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    constructor(s: string);
+    name: string;
+    constructor(n: number);
+    constructor(sn: string | number) {}
+    bar(): void {}
+    baz(): void {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'constructor' signatures should be adjacent",
+                    line: 5,
+                    column: 5
+                }
+            ]
+        },
+        {
+            code: `
+class Foo {
+    foo(s: string): void;
+    name: string;
+    foo(n: number): void;
+    foo(sn: string | number): void {}
+    bar(): void {}
+    baz(): void {}
+}
+            `,
+            parser: "typescript-eslint-parser",
+            errors: [
+                {
+                    message: "All 'foo' signatures should be adjacent",
+                    line: 5,
+                    column: 5
+                }
+            ]
+        }
+    ]
+});