UNPKG

6.68 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("../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 docs: {
57 description: "require `return` statements to either always or never specify values",
58 category: "Best Practices",
59 recommended: false,
60 url: "https://eslint.org/docs/rules/consistent-return"
61 },
62
63 schema: [{
64 type: "object",
65 properties: {
66 treatUndefinedAsUnspecified: {
67 type: "boolean"
68 }
69 },
70 additionalProperties: false
71 }],
72
73 messages: {
74 missingReturn: "Expected to return a value at the end of {{name}}.",
75 missingReturnValue: "{{name}} expected a return value.",
76 unexpectedReturnValue: "{{name}} expected no return value."
77 }
78 },
79
80 create(context) {
81 const options = context.options[0] || {};
82 const treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true;
83 let funcInfo = null;
84
85 /**
86 * Checks whether of not the implicit returning is consistent if the last
87 * code path segment is reachable.
88 *
89 * @param {ASTNode} node - A program/function node to check.
90 * @returns {void}
91 */
92 function checkLastSegment(node) {
93 let loc, name;
94
95 /*
96 * Skip if it expected no return value or unreachable.
97 * When unreachable, all paths are returned or thrown.
98 */
99 if (!funcInfo.hasReturnValue ||
100 funcInfo.codePath.currentSegments.every(isUnreachable) ||
101 astUtils.isES5Constructor(node) ||
102 isClassConstructor(node)
103 ) {
104 return;
105 }
106
107 // Adjust a location and a message.
108 if (node.type === "Program") {
109
110 // The head of program.
111 loc = { line: 1, column: 0 };
112 name = "program";
113 } else if (node.type === "ArrowFunctionExpression") {
114
115 // `=>` token
116 loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc.start;
117 } else if (
118 node.parent.type === "MethodDefinition" ||
119 (node.parent.type === "Property" && node.parent.method)
120 ) {
121
122 // Method name.
123 loc = node.parent.key.loc.start;
124 } else {
125
126 // Function name or `function` keyword.
127 loc = (node.id || node).loc.start;
128 }
129
130 if (!name) {
131 name = astUtils.getFunctionNameWithKind(node);
132 }
133
134 // Reports.
135 context.report({
136 node,
137 loc,
138 messageId: "missingReturn",
139 data: { name }
140 });
141 }
142
143 return {
144
145 // Initializes/Disposes state of each code path.
146 onCodePathStart(codePath, node) {
147 funcInfo = {
148 upper: funcInfo,
149 codePath,
150 hasReturn: false,
151 hasReturnValue: false,
152 messageId: "",
153 node
154 };
155 },
156 onCodePathEnd() {
157 funcInfo = funcInfo.upper;
158 },
159
160 // Reports a given return statement if it's inconsistent.
161 ReturnStatement(node) {
162 const argument = node.argument;
163 let hasReturnValue = Boolean(argument);
164
165 if (treatUndefinedAsUnspecified && hasReturnValue) {
166 hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void";
167 }
168
169 if (!funcInfo.hasReturn) {
170 funcInfo.hasReturn = true;
171 funcInfo.hasReturnValue = hasReturnValue;
172 funcInfo.messageId = hasReturnValue ? "missingReturnValue" : "unexpectedReturnValue";
173 funcInfo.data = {
174 name: funcInfo.node.type === "Program"
175 ? "Program"
176 : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
177 };
178 } else if (funcInfo.hasReturnValue !== hasReturnValue) {
179 context.report({
180 node,
181 messageId: funcInfo.messageId,
182 data: funcInfo.data
183 });
184 }
185 },
186
187 // Reports a given program/function if the implicit returning is not consistent.
188 "Program:exit": checkLastSegment,
189 "FunctionDeclaration:exit": checkLastSegment,
190 "FunctionExpression:exit": checkLastSegment,
191 "ArrowFunctionExpression:exit": checkLastSegment
192 };
193 }
194};