UNPKG

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