Repository: nzakas/eslint-plugin-typescript
Pull request #103 (2017-12-24 04:30:49)

New: add explicit-function-return-type rule
Adds a new rule that ensures that all function declarations, function expressions, arrow functions, class methods etc. include a type annotation for the return value (including `void` for functions that don't return a value).

The following patterns are considered warnings:
```ts
// Should indicate that no value is returned (void)
function test() {
    return;
}

// Should indicate that a number is returned
var fn = function() {
    return 1;
}

// Should indicate that a string is returned
var arrowFn = () => 'test';

class Test {
    // Should indicate that no value is returned (void)
    method() {
        return;
    }
}
```

The following patterns are not warnings:

```ts
// No return value should be expected (void)
function test(): void {
    return;
}

// A return value of type number
var fn = function(): number {
    return 1;
}

// A return value of type string
var arrowFn = (): string => 'test';

class Test {
    // No return value should be expected (void)
    method(): void {
        return;
    }
}
```

ajafff: Isn't this already covered by the `typedef` rule? 
scottohara: In the initial commit, I missed the fact that class constructors and property setters cannot have a return type annotation in Typescript;  so these two specific types of `FunctionExpression` nodes are now ignored by the rule.

e.g.
```ts
// Valid
class Test {
  constructor() {...}  // OK...constructors don't need a return type
  set foo() {...} // OK...property setters don't need a return type
  get foo(): number {...}  // OK...property getter has a return type
}

// Invalid
class Test {
  constructor() {...}  // OK...constructors don't need a return type
  set foo() {...} // OK...property setters don't need a return type
  get foo() {...} // Error...property getter has no return type
}
```
scottohara: @ajafff - what is the `typedef` rule?
ajafff: @scottohara TSLint has a rule called `typedef` that enforces explicit type annotations in the certain locations.
I just noticed after commenting that this PR is for the eslint plugin. 
scottohara: I should also point out that, like #102, this rule in its current form will trigger false positives on any `*.js` files (for the obvious reason that return type annotations are invalid JS syntax).

Whatever fix is agreed as appropriate for #102 (such as limiting the rule to only `*.ts` files, e.g. `/\.ts$/.test(context.getFilename())`) will equally need to be applied here.
j-f1: It should also run on `.tsx` files.

Patch:
diff --git a/README.md b/README.md
index 43363b6..7939658 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,7 @@ Then configure the rules you want to use under the rules section.
 <!-- begin rule list -->
 * [`typescript/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) — Require that member overloads be consecutive
 * [`typescript/class-name-casing`](./docs/rules/class-name-casing.md) — Require PascalCased class and interface names (`class-name` from TSLint)
+* [`typescript/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) — Require explicit return types on functions and class methods
 * [`typescript/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) — Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint)
 * [`typescript/interface-name-prefix`](./docs/rules/interface-name-prefix.md) — Require that interface names be prefixed with `I` (`interface-name` from TSLint)
 * [`typescript/member-delimiter-style`](./docs/rules/member-delimiter-style.md) — Require a specific member delimiter style for interfaces and type literals
diff --git a/docs/rules/explicit-function-return-type.md b/docs/rules/explicit-function-return-type.md
new file mode 100644
index 0000000..459b184
--- /dev/null
+++ b/docs/rules/explicit-function-return-type.md
@@ -0,0 +1,67 @@
+# Require explicit return types on functions and class methods (explicit-function-return-type)
+
+Explicit types for function return values makes it clear to any calling code what type is returned.
+This ensures that the return value is assigned to a variable of the correct type; or in the case
+where there is no return value, that the calling code doesn't try to use the undefined value when it
+shouldn't.
+
+## Rule Details
+
+This rule aims to ensure that the values returned from functions are of the expected type.
+
+The following patterns are considered warnings:
+
+```ts
+// Should indicate that no value is returned (void)
+function test() {
+    return;
+}
+
+// Should indicate that a number is returned
+var fn = function() {
+    return 1;
+}
+
+// Should indicate that a string is returned
+var arrowFn = () => 'test';
+
+class Test {
+    // Should indicate that no value is returned (void)
+    method() {
+        return;
+    }
+}
+```
+
+The following patterns are not warnings:
+
+```ts
+// No return value should be expected (void)
+function test(): void {
+    return;
+}
+
+// A return value of type number
+var fn = function(): number {
+    return 1;
+}
+
+// A return value of type string
+var arrowFn = (): string => 'test';
+
+class Test {
+    // No return value should be expected (void)
+    method(): void {
+        return;
+    }
+}
+```
+
+## When Not To Use It
+
+If you don't wish to prevent calling code from using function return values in unexpected ways, then
+you will not need this rule.
+
+## Further Reading
+
+* TypeScript [Functions](https://www.typescriptlang.org/docs/handbook/functions.html#function-types)
diff --git a/lib/rules/explicit-function-return-type.js b/lib/rules/explicit-function-return-type.js
new file mode 100644
index 0000000..202e995
--- /dev/null
+++ b/lib/rules/explicit-function-return-type.js
@@ -0,0 +1,88 @@
+/**
+ * @fileoverview Enforces explicit return type for functions
+ * @author Scott O'Hara
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description:
+                "Require explicit return types on functions and class methods",
+            category: "TypeScript"
+        },
+        schema: []
+    },
+
+    create(context) {
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Checks if the parent of a function expression is a constructor.
+         * @param {ASTNode} parent The parent of a function expression node
+         * @returns {boolean} `true` if the parent is a constructor
+         * @private
+         */
+        function isConstructor(parent) {
+            return (
+                parent.type === "MethodDefinition" &&
+                parent.kind === "constructor"
+            );
+        }
+
+        /**
+         * Checks if the parent of a function expression is a setter.
+         * @param {ASTNode} parent The parent of a function expression node
+         * @returns {boolean} `true` if the parent is a setter
+         * @private
+         */
+        function isSetter(parent) {
+            return parent.type === "MethodDefinition" && parent.kind === "set";
+        }
+
+        /**
+         * Check if the context file name is *.ts or *.tsx
+         * @param {string} fileName The context file name
+         * @returns {boolean} `true` if the file name ends in *.ts or *.tsx
+         * @private
+         */
+        function isTypescript(fileName) {
+            return /\.(ts|tsx)$/.test(fileName);
+        }
+
+        /**
+         * Checks if a function declaration/expression has a return type.
+         * @param {ASTNode} node The node representing a function.
+         * @returns {void}
+         * @private
+         */
+        function checkFunctionReturnType(node) {
+            if (
+                !node.returnType &&
+                !isConstructor(node.parent) &&
+                !isSetter(node.parent) &&
+                isTypescript(context.getFilename())
+            ) {
+                context.report({
+                    node,
+                    message: `Missing return type on function.`
+                });
+            }
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+        return {
+            FunctionDeclaration: checkFunctionReturnType,
+            FunctionExpression: checkFunctionReturnType,
+            ArrowFunctionExpression: checkFunctionReturnType
+        };
+    }
+};


Test patch:
diff --git a/tests/lib/rules/explicit-function-return-type.js b/tests/lib/rules/explicit-function-return-type.js
new file mode 100644
index 0000000..f6287a4
--- /dev/null
+++ b/tests/lib/rules/explicit-function-return-type.js
@@ -0,0 +1,142 @@
+/**
+ * @fileoverview Enforces explicit return type for functions
+ * @author Scott O'Hara
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/explicit-function-return-type"),
+    RuleTester = require("eslint").RuleTester;
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+    parser: "typescript-eslint-parser"
+});
+
+ruleTester.run("explicit-function-return-type", rule, {
+    valid: [
+        {
+            filename: "test.ts",
+            code: `
+function test(): void {
+    return;
+}
+            `
+        },
+        {
+            filename: "test.ts",
+            code: `
+var fn = function(): number {
+    return 1;
+};
+            `
+        },
+        {
+            filename: "test.ts",
+            code: `
+var arrowFn = (): string => 'test';
+            `
+        },
+        {
+            filename: "test.ts",
+            code: `
+class Test {
+  constructor() {}
+  get prop(): number {
+    return 1;
+  }
+  set prop() {}
+  method(): void {
+    return;
+  }
+}
+            `
+        },
+        {
+            filename: "test.js",
+            code: `
+function test() {
+    return;
+}
+            `
+        }
+    ],
+    invalid: [
+        {
+            filename: "test.ts",
+            code: `
+function test() {
+    return;
+}
+            `,
+            errors: [
+                {
+                    message: "Missing return type on function.",
+                    line: 2,
+                    column: 1
+                }
+            ]
+        },
+        {
+            filename: "test.ts",
+            code: `
+var fn = function() {
+    return 1;
+};
+            `,
+            errors: [
+                {
+                    message: "Missing return type on function.",
+                    line: 2,
+                    column: 10
+                }
+            ]
+        },
+        {
+            filename: "test.ts",
+            code: `
+var arrowFn = () => 'test';
+            `,
+            errors: [
+                {
+                    message: "Missing return type on function.",
+                    line: 2,
+                    column: 15
+                }
+            ]
+        },
+        {
+            filename: "test.ts",
+            code: `
+class Test {
+  constructor() {}
+  get prop() {
+      return 1;
+  }
+  set prop() {}
+  method() {
+    return;
+  }
+}
+            `,
+            errors: [
+                {
+                    message: "Missing return type on function.",
+                    line: 4,
+                    column: 11
+                },
+                {
+                    message: "Missing return type on function.",
+                    line: 8,
+                    column: 9
+                }
+            ]
+        }
+    ]
+});