123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- /**
- * @fileoverview Rule to flag consistent return values
- * @author Nicholas C. Zakas
- */
- "use strict";
-
- //------------------------------------------------------------------------------
- // Requirements
- //------------------------------------------------------------------------------
-
- const lodash = require("lodash");
-
- const astUtils = require("../util/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"
- }
- },
- 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
- };
- }
- };
|