--- /dev/null
+/**
+ * @fileoverview Rule to flag consistent return values
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const lodash = require("lodash");
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given node is an `Identifier` node which was named a given name.
+ * @param {ASTNode} node A node to check.
+ * @param {string} name An expected name of the node.
+ * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
+ */
+function isIdentifier(node, name) {
+ return node.type === "Identifier" && node.name === name;
+}
+
+/**
+ * Checks whether or not a given code path segment is unreachable.
+ * @param {CodePathSegment} segment A CodePathSegment to check.
+ * @returns {boolean} `true` if the segment is unreachable.
+ */
+function isUnreachable(segment) {
+ return !segment.reachable;
+}
+
+/**
+ * Checks whether a given node is a `constructor` method in an ES6 class
+ * @param {ASTNode} node A node to check
+ * @returns {boolean} `true` if the node is a `constructor` method
+ */
+function isClassConstructor(node) {
+ return node.type === "FunctionExpression" &&
+ node.parent &&
+ node.parent.type === "MethodDefinition" &&
+ node.parent.kind === "constructor";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "suggestion",
+
+ docs: {
+ description: "require `return` statements to either always or never specify values",
+ category: "Best Practices",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/consistent-return"
+ },
+
+ schema: [{
+ type: "object",
+ properties: {
+ treatUndefinedAsUnspecified: {
+ type: "boolean",
+ default: false
+ }
+ },
+ additionalProperties: false
+ }],
+
+ messages: {
+ missingReturn: "Expected to return a value at the end of {{name}}.",
+ missingReturnValue: "{{name}} expected a return value.",
+ unexpectedReturnValue: "{{name}} expected no return value."
+ }
+ },
+
+ create(context) {
+ const options = context.options[0] || {};
+ const treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true;
+ let funcInfo = null;
+
+ /**
+ * Checks whether of not the implicit returning is consistent if the last
+ * code path segment is reachable.
+ * @param {ASTNode} node A program/function node to check.
+ * @returns {void}
+ */
+ function checkLastSegment(node) {
+ let loc, name;
+
+ /*
+ * Skip if it expected no return value or unreachable.
+ * When unreachable, all paths are returned or thrown.
+ */
+ if (!funcInfo.hasReturnValue ||
+ funcInfo.codePath.currentSegments.every(isUnreachable) ||
+ astUtils.isES5Constructor(node) ||
+ isClassConstructor(node)
+ ) {
+ return;
+ }
+
+ // Adjust a location and a message.
+ if (node.type === "Program") {
+
+ // The head of program.
+ loc = { line: 1, column: 0 };
+ name = "program";
+ } else if (node.type === "ArrowFunctionExpression") {
+
+ // `=>` token
+ loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc.start;
+ } else if (
+ node.parent.type === "MethodDefinition" ||
+ (node.parent.type === "Property" && node.parent.method)
+ ) {
+
+ // Method name.
+ loc = node.parent.key.loc.start;
+ } else {
+
+ // Function name or `function` keyword.
+ loc = (node.id || node).loc.start;
+ }
+
+ if (!name) {
+ name = astUtils.getFunctionNameWithKind(node);
+ }
+
+ // Reports.
+ context.report({
+ node,
+ loc,
+ messageId: "missingReturn",
+ data: { name }
+ });
+ }
+
+ return {
+
+ // Initializes/Disposes state of each code path.
+ onCodePathStart(codePath, node) {
+ funcInfo = {
+ upper: funcInfo,
+ codePath,
+ hasReturn: false,
+ hasReturnValue: false,
+ messageId: "",
+ node
+ };
+ },
+ onCodePathEnd() {
+ funcInfo = funcInfo.upper;
+ },
+
+ // Reports a given return statement if it's inconsistent.
+ ReturnStatement(node) {
+ const argument = node.argument;
+ let hasReturnValue = Boolean(argument);
+
+ if (treatUndefinedAsUnspecified && hasReturnValue) {
+ hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void";
+ }
+
+ if (!funcInfo.hasReturn) {
+ funcInfo.hasReturn = true;
+ funcInfo.hasReturnValue = hasReturnValue;
+ funcInfo.messageId = hasReturnValue ? "missingReturnValue" : "unexpectedReturnValue";
+ funcInfo.data = {
+ name: funcInfo.node.type === "Program"
+ ? "Program"
+ : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
+ };
+ } else if (funcInfo.hasReturnValue !== hasReturnValue) {
+ context.report({
+ node,
+ messageId: funcInfo.messageId,
+ data: funcInfo.data
+ });
+ }
+ },
+
+ // Reports a given program/function if the implicit returning is not consistent.
+ "Program:exit": checkLastSegment,
+ "FunctionDeclaration:exit": checkLastSegment,
+ "FunctionExpression:exit": checkLastSegment,
+ "ArrowFunctionExpression:exit": checkLastSegment
+ };
+ }
+};