UNPKG

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