UNPKG

5.98 kBJavaScriptView Raw
1/**
2 * @fileoverview Enforces that a return statement is present in property getters.
3 * @author Aladdin-ADD(hh_2013@foxmail.com)
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("../ast-utils");
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/;
18
19/**
20 * Checks a given code path segment is reachable.
21 *
22 * @param {CodePathSegment} segment - A segment to check.
23 * @returns {boolean} `true` if the segment is reachable.
24 */
25function isReachable(segment) {
26 return segment.reachable;
27}
28
29/**
30 * Gets a readable location.
31 *
32 * - FunctionExpression -> the function name or `function` keyword.
33 *
34 * @param {ASTNode} node - A function node to get.
35 * @returns {ASTNode|Token} The node or the token of a location.
36 */
37function getId(node) {
38 return node.id || node;
39}
40
41//------------------------------------------------------------------------------
42// Rule Definition
43//------------------------------------------------------------------------------
44
45module.exports = {
46 meta: {
47 docs: {
48 description: "enforce `return` statements in getters",
49 category: "Possible Errors",
50 recommended: false
51 },
52 fixable: null,
53 schema: [
54 {
55 type: "object",
56 properties: {
57 allowImplicit: {
58 type: "boolean"
59 }
60 },
61 additionalProperties: false
62 }
63 ]
64 },
65
66 create(context) {
67
68 const options = context.options[0] || { allowImplicit: false };
69
70 let funcInfo = {
71 upper: null,
72 codePath: null,
73 hasReturn: false,
74 shouldCheck: false,
75 node: null
76 };
77
78 /**
79 * Checks whether or not the last code path segment is reachable.
80 * Then reports this function if the segment is reachable.
81 *
82 * If the last code path segment is reachable, there are paths which are not
83 * returned or thrown.
84 *
85 * @param {ASTNode} node - A node to check.
86 * @returns {void}
87 */
88 function checkLastSegment(node) {
89 if (funcInfo.shouldCheck &&
90 funcInfo.codePath.currentSegments.some(isReachable)
91 ) {
92 context.report({
93 node,
94 loc: getId(node).loc.start,
95 message: funcInfo.hasReturn
96 ? "Expected {{name}} to always return a value."
97 : "Expected to return a value in {{name}}.",
98 data: {
99 name: astUtils.getFunctionNameWithKind(funcInfo.node)
100 }
101 });
102 }
103 }
104
105 /** Checks whether a node means a getter function.
106 * @param {ASTNode} node - a node to check.
107 * @returns {boolean} if node means a getter, return true; else return false.
108 */
109 function isGetter(node) {
110 const parent = node.parent;
111
112 if (TARGET_NODE_TYPE.test(node.type) && node.body.type === "BlockStatement") {
113 if (parent.kind === "get") {
114 return true;
115 }
116 if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") {
117
118 // Object.defineProperty()
119 if (parent.parent.parent.type === "CallExpression" &&
120 astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") {
121 return true;
122 }
123
124 // Object.defineProperties()
125 if (parent.parent.parent.type === "Property" &&
126 parent.parent.parent.parent.type === "ObjectExpression" &&
127 parent.parent.parent.parent.parent.type === "CallExpression" &&
128 astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") {
129 return true;
130 }
131 }
132 }
133 return false;
134 }
135 return {
136
137 // Stacks this function's information.
138 onCodePathStart(codePath, node) {
139 funcInfo = {
140 upper: funcInfo,
141 codePath,
142 hasReturn: false,
143 shouldCheck: isGetter(node),
144 node
145 };
146 },
147
148 // Pops this function's information.
149 onCodePathEnd() {
150 funcInfo = funcInfo.upper;
151 },
152
153 // Checks the return statement is valid.
154 ReturnStatement(node) {
155 if (funcInfo.shouldCheck) {
156 funcInfo.hasReturn = true;
157
158 // if allowImplicit: false, should also check node.argument
159 if (!options.allowImplicit && !node.argument) {
160 context.report({
161 node,
162 message: "Expected to return a value in {{name}}.",
163 data: {
164 name: astUtils.getFunctionNameWithKind(funcInfo.node)
165 }
166 });
167 }
168 }
169 },
170
171 // Reports a given function if the last path is reachable.
172 "FunctionExpression:exit": checkLastSegment,
173 "ArrowFunctionExpression:exit": checkLastSegment
174 };
175 }
176};