UNPKG

6.75 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag consistent return values
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const lodash = require("lodash");
12
13const astUtils = require("./utils/ast-utils");
14
15//------------------------------------------------------------------------------
16// Helpers
17//------------------------------------------------------------------------------
18
19/**
20 * Checks whether or not a given node is an `Identifier` node which was named a given name.
21 * @param {ASTNode} node - A node to check.
22 * @param {string} name - An expected name of the node.
23 * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
24 */
25function isIdentifier(node, name) {
26 return node.type === "Identifier" && node.name === name;
27}
28
29/**
30 * Checks whether or not a given code path segment is unreachable.
31 * @param {CodePathSegment} segment - A CodePathSegment to check.
32 * @returns {boolean} `true` if the segment is unreachable.
33 */
34function isUnreachable(segment) {
35 return !segment.reachable;
36}
37
38/**
39 * Checks whether a given node is a `constructor` method in an ES6 class
40 * @param {ASTNode} node A node to check
41 * @returns {boolean} `true` if the node is a `constructor` method
42 */
43function isClassConstructor(node) {
44 return node.type === "FunctionExpression" &&
45 node.parent &&
46 node.parent.type === "MethodDefinition" &&
47 node.parent.kind === "constructor";
48}
49
50//------------------------------------------------------------------------------
51// Rule Definition
52//------------------------------------------------------------------------------
53
54module.exports = {
55 meta: {
56 type: "suggestion",
57
58 docs: {
59 description: "require `return` statements to either always or never specify values",
60 category: "Best Practices",
61 recommended: false,
62 url: "https://eslint.org/docs/rules/consistent-return"
63 },
64
65 schema: [{
66 type: "object",
67 properties: {
68 treatUndefinedAsUnspecified: {
69 type: "boolean",
70 default: false
71 }
72 },
73 additionalProperties: false
74 }],
75
76 messages: {
77 missingReturn: "Expected to return a value at the end of {{name}}.",
78 missingReturnValue: "{{name}} expected a return value.",
79 unexpectedReturnValue: "{{name}} expected no return value."
80 }
81 },
82
83 create(context) {
84 const options = context.options[0] || {};
85 const treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true;
86 let funcInfo = null;
87
88 /**
89 * Checks whether of not the implicit returning is consistent if the last
90 * code path segment is reachable.
91 *
92 * @param {ASTNode} node - A program/function node to check.
93 * @returns {void}
94 */
95 function checkLastSegment(node) {
96 let loc, name;
97
98 /*
99 * Skip if it expected no return value or unreachable.
100 * When unreachable, all paths are returned or thrown.
101 */
102 if (!funcInfo.hasReturnValue ||
103 funcInfo.codePath.currentSegments.every(isUnreachable) ||
104 astUtils.isES5Constructor(node) ||
105 isClassConstructor(node)
106 ) {
107 return;
108 }
109
110 // Adjust a location and a message.
111 if (node.type === "Program") {
112
113 // The head of program.
114 loc = { line: 1, column: 0 };
115 name = "program";
116 } else if (node.type === "ArrowFunctionExpression") {
117
118 // `=>` token
119 loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc.start;
120 } else if (
121 node.parent.type === "MethodDefinition" ||
122 (node.parent.type === "Property" && node.parent.method)
123 ) {
124
125 // Method name.
126 loc = node.parent.key.loc.start;
127 } else {
128
129 // Function name or `function` keyword.
130 loc = (node.id || node).loc.start;
131 }
132
133 if (!name) {
134 name = astUtils.getFunctionNameWithKind(node);
135 }
136
137 // Reports.
138 context.report({
139 node,
140 loc,
141 messageId: "missingReturn",
142 data: { name }
143 });
144 }
145
146 return {
147
148 // Initializes/Disposes state of each code path.
149 onCodePathStart(codePath, node) {
150 funcInfo = {
151 upper: funcInfo,
152 codePath,
153 hasReturn: false,
154 hasReturnValue: false,
155 messageId: "",
156 node
157 };
158 },
159 onCodePathEnd() {
160 funcInfo = funcInfo.upper;
161 },
162
163 // Reports a given return statement if it's inconsistent.
164 ReturnStatement(node) {
165 const argument = node.argument;
166 let hasReturnValue = Boolean(argument);
167
168 if (treatUndefinedAsUnspecified && hasReturnValue) {
169 hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void";
170 }
171
172 if (!funcInfo.hasReturn) {
173 funcInfo.hasReturn = true;
174 funcInfo.hasReturnValue = hasReturnValue;
175 funcInfo.messageId = hasReturnValue ? "missingReturnValue" : "unexpectedReturnValue";
176 funcInfo.data = {
177 name: funcInfo.node.type === "Program"
178 ? "Program"
179 : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
180 };
181 } else if (funcInfo.hasReturnValue !== hasReturnValue) {
182 context.report({
183 node,
184 messageId: funcInfo.messageId,
185 data: funcInfo.data
186 });
187 }
188 },
189
190 // Reports a given program/function if the implicit returning is not consistent.
191 "Program:exit": checkLastSegment,
192 "FunctionDeclaration:exit": checkLastSegment,
193 "FunctionExpression:exit": checkLastSegment,
194 "ArrowFunctionExpression:exit": checkLastSegment
195 };
196 }
197};