UNPKG

17.1 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const utils_1 = require("@typescript-eslint/utils");
23const scope_manager_1 = require("@typescript-eslint/scope-manager");
24const util = __importStar(require("../util"));
25const explicitReturnTypeUtils_1 = require("../util/explicitReturnTypeUtils");
26exports.default = util.createRule({
27 name: 'explicit-module-boundary-types',
28 meta: {
29 type: 'problem',
30 docs: {
31 description: "Require explicit return and argument types on exported functions' and classes' public class methods",
32 recommended: false,
33 },
34 messages: {
35 missingReturnType: 'Missing return type on function.',
36 missingArgType: "Argument '{{name}}' should be typed.",
37 missingArgTypeUnnamed: '{{type}} argument should be typed.',
38 anyTypedArg: "Argument '{{name}}' should be typed with a non-any type.",
39 anyTypedArgUnnamed: '{{type}} argument should be typed with a non-any type.',
40 },
41 schema: [
42 {
43 type: 'object',
44 properties: {
45 allowArgumentsExplicitlyTypedAsAny: {
46 type: 'boolean',
47 },
48 allowDirectConstAssertionInArrowFunctions: {
49 type: 'boolean',
50 },
51 allowedNames: {
52 type: 'array',
53 items: {
54 type: 'string',
55 },
56 },
57 allowHigherOrderFunctions: {
58 type: 'boolean',
59 },
60 allowTypedFunctionExpressions: {
61 type: 'boolean',
62 },
63 // DEPRECATED - To be removed in next major
64 shouldTrackReferences: {
65 type: 'boolean',
66 },
67 },
68 additionalProperties: false,
69 },
70 ],
71 },
72 defaultOptions: [
73 {
74 allowArgumentsExplicitlyTypedAsAny: false,
75 allowDirectConstAssertionInArrowFunctions: true,
76 allowedNames: [],
77 allowHigherOrderFunctions: true,
78 allowTypedFunctionExpressions: true,
79 },
80 ],
81 create(context, [options]) {
82 const sourceCode = context.getSourceCode();
83 // tracks all of the functions we've already checked
84 const checkedFunctions = new Set();
85 // tracks functions that were found whilst traversing
86 const foundFunctions = [];
87 // all nodes visited, avoids infinite recursion for cyclic references
88 // (such as class member referring to itself)
89 const alreadyVisited = new Set();
90 /*
91 # How the rule works:
92
93 As the rule traverses the AST, it immediately checks every single function that it finds is exported.
94 "exported" means that it is either directly exported, or that its name is exported.
95
96 It also collects a list of every single function it finds on the way, but does not check them.
97 After it's finished traversing the AST, it then iterates through the list of found functions, and checks to see if
98 any of them are part of a higher-order function
99 */
100 return {
101 ExportDefaultDeclaration(node) {
102 checkNode(node.declaration);
103 },
104 'ExportNamedDeclaration:not([source])'(node) {
105 if (node.declaration) {
106 checkNode(node.declaration);
107 }
108 else {
109 for (const specifier of node.specifiers) {
110 followReference(specifier.local);
111 }
112 }
113 },
114 TSExportAssignment(node) {
115 checkNode(node.expression);
116 },
117 'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression'(node) {
118 foundFunctions.push(node);
119 },
120 'Program:exit'() {
121 for (const func of foundFunctions) {
122 if (isExportedHigherOrderFunction(func)) {
123 checkNode(func);
124 }
125 }
126 },
127 };
128 function checkParameters(node) {
129 function checkParameter(param) {
130 function report(namedMessageId, unnamedMessageId) {
131 if (param.type === utils_1.AST_NODE_TYPES.Identifier) {
132 context.report({
133 node: param,
134 messageId: namedMessageId,
135 data: { name: param.name },
136 });
137 }
138 else if (param.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
139 context.report({
140 node: param,
141 messageId: unnamedMessageId,
142 data: { type: 'Array pattern' },
143 });
144 }
145 else if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
146 context.report({
147 node: param,
148 messageId: unnamedMessageId,
149 data: { type: 'Object pattern' },
150 });
151 }
152 else if (param.type === utils_1.AST_NODE_TYPES.RestElement) {
153 if (param.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
154 context.report({
155 node: param,
156 messageId: namedMessageId,
157 data: { name: param.argument.name },
158 });
159 }
160 else {
161 context.report({
162 node: param,
163 messageId: unnamedMessageId,
164 data: { type: 'Rest' },
165 });
166 }
167 }
168 }
169 switch (param.type) {
170 case utils_1.AST_NODE_TYPES.ArrayPattern:
171 case utils_1.AST_NODE_TYPES.Identifier:
172 case utils_1.AST_NODE_TYPES.ObjectPattern:
173 case utils_1.AST_NODE_TYPES.RestElement:
174 if (!param.typeAnnotation) {
175 report('missingArgType', 'missingArgTypeUnnamed');
176 }
177 else if (options.allowArgumentsExplicitlyTypedAsAny !== true &&
178 param.typeAnnotation.typeAnnotation.type ===
179 utils_1.AST_NODE_TYPES.TSAnyKeyword) {
180 report('anyTypedArg', 'anyTypedArgUnnamed');
181 }
182 return;
183 case utils_1.AST_NODE_TYPES.TSParameterProperty:
184 return checkParameter(param.parameter);
185 case utils_1.AST_NODE_TYPES.AssignmentPattern: // ignored as it has a type via its assignment
186 return;
187 }
188 }
189 for (const arg of node.params) {
190 checkParameter(arg);
191 }
192 }
193 /**
194 * Checks if a function name is allowed and should not be checked.
195 */
196 function isAllowedName(node) {
197 var _a;
198 if (!node || !options.allowedNames || !options.allowedNames.length) {
199 return false;
200 }
201 if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
202 node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
203 return (((_a = node.id) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.Identifier &&
204 options.allowedNames.includes(node.id.name));
205 }
206 else if (node.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
207 node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition ||
208 (node.type === utils_1.AST_NODE_TYPES.Property && node.method) ||
209 node.type === utils_1.AST_NODE_TYPES.PropertyDefinition) {
210 if (node.key.type === utils_1.AST_NODE_TYPES.Literal &&
211 typeof node.key.value === 'string') {
212 return options.allowedNames.includes(node.key.value);
213 }
214 if (node.key.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
215 node.key.expressions.length === 0) {
216 return options.allowedNames.includes(node.key.quasis[0].value.raw);
217 }
218 if (!node.computed && node.key.type === utils_1.AST_NODE_TYPES.Identifier) {
219 return options.allowedNames.includes(node.key.name);
220 }
221 }
222 return false;
223 }
224 function isExportedHigherOrderFunction(node) {
225 var _a;
226 let current = node.parent;
227 while (current) {
228 if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
229 // the parent of a return will always be a block statement, so we can skip over it
230 current = (_a = current.parent) === null || _a === void 0 ? void 0 : _a.parent;
231 continue;
232 }
233 if (!util.isFunction(current) ||
234 !(0, explicitReturnTypeUtils_1.doesImmediatelyReturnFunctionExpression)(current)) {
235 return false;
236 }
237 if (checkedFunctions.has(current)) {
238 return true;
239 }
240 current = current.parent;
241 }
242 return false;
243 }
244 function followReference(node) {
245 const scope = context.getScope();
246 const variable = scope.set.get(node.name);
247 /* istanbul ignore if */ if (!variable) {
248 return;
249 }
250 // check all of the definitions
251 for (const definition of variable.defs) {
252 // cases we don't care about in this rule
253 if ([
254 scope_manager_1.DefinitionType.ImplicitGlobalVariable,
255 scope_manager_1.DefinitionType.ImportBinding,
256 scope_manager_1.DefinitionType.CatchClause,
257 scope_manager_1.DefinitionType.Parameter,
258 ].includes(definition.type)) {
259 continue;
260 }
261 checkNode(definition.node);
262 }
263 // follow references to find writes to the variable
264 for (const reference of variable.references) {
265 if (
266 // we don't want to check the initialization ref, as this is handled by the declaration check
267 !reference.init &&
268 reference.writeExpr) {
269 checkNode(reference.writeExpr);
270 }
271 }
272 }
273 function checkNode(node) {
274 if (node == null || alreadyVisited.has(node)) {
275 return;
276 }
277 alreadyVisited.add(node);
278 switch (node.type) {
279 case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
280 case utils_1.AST_NODE_TYPES.FunctionExpression:
281 return checkFunctionExpression(node);
282 case utils_1.AST_NODE_TYPES.ArrayExpression:
283 for (const element of node.elements) {
284 checkNode(element);
285 }
286 return;
287 case utils_1.AST_NODE_TYPES.PropertyDefinition:
288 if (node.accessibility === 'private' ||
289 node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
290 return;
291 }
292 return checkNode(node.value);
293 case utils_1.AST_NODE_TYPES.ClassDeclaration:
294 case utils_1.AST_NODE_TYPES.ClassExpression:
295 for (const element of node.body.body) {
296 checkNode(element);
297 }
298 return;
299 case utils_1.AST_NODE_TYPES.FunctionDeclaration:
300 return checkFunction(node);
301 case utils_1.AST_NODE_TYPES.MethodDefinition:
302 case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
303 if (node.accessibility === 'private' ||
304 node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
305 return;
306 }
307 return checkNode(node.value);
308 case utils_1.AST_NODE_TYPES.Identifier:
309 return followReference(node);
310 case utils_1.AST_NODE_TYPES.ObjectExpression:
311 for (const property of node.properties) {
312 checkNode(property);
313 }
314 return;
315 case utils_1.AST_NODE_TYPES.Property:
316 return checkNode(node.value);
317 case utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression:
318 return checkEmptyBodyFunctionExpression(node);
319 case utils_1.AST_NODE_TYPES.VariableDeclaration:
320 for (const declaration of node.declarations) {
321 checkNode(declaration);
322 }
323 return;
324 case utils_1.AST_NODE_TYPES.VariableDeclarator:
325 return checkNode(node.init);
326 }
327 }
328 function checkEmptyBodyFunctionExpression(node) {
329 var _a, _b, _c;
330 const isConstructor = ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.MethodDefinition &&
331 node.parent.kind === 'constructor';
332 const isSetAccessor = (((_b = node.parent) === null || _b === void 0 ? void 0 : _b.type) === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition ||
333 ((_c = node.parent) === null || _c === void 0 ? void 0 : _c.type) === utils_1.AST_NODE_TYPES.MethodDefinition) &&
334 node.parent.kind === 'set';
335 if (!isConstructor && !isSetAccessor && !node.returnType) {
336 context.report({
337 node,
338 messageId: 'missingReturnType',
339 });
340 }
341 checkParameters(node);
342 }
343 function checkFunctionExpression(node) {
344 if (checkedFunctions.has(node)) {
345 return;
346 }
347 checkedFunctions.add(node);
348 if (isAllowedName(node.parent) ||
349 (0, explicitReturnTypeUtils_1.isTypedFunctionExpression)(node, options) ||
350 (0, explicitReturnTypeUtils_1.ancestorHasReturnType)(node)) {
351 return;
352 }
353 (0, explicitReturnTypeUtils_1.checkFunctionExpressionReturnType)(node, options, sourceCode, loc => {
354 context.report({
355 node,
356 loc,
357 messageId: 'missingReturnType',
358 });
359 });
360 checkParameters(node);
361 }
362 function checkFunction(node) {
363 if (checkedFunctions.has(node)) {
364 return;
365 }
366 checkedFunctions.add(node);
367 if (isAllowedName(node) || (0, explicitReturnTypeUtils_1.ancestorHasReturnType)(node)) {
368 return;
369 }
370 (0, explicitReturnTypeUtils_1.checkFunctionReturnType)(node, options, sourceCode, loc => {
371 context.report({
372 node,
373 loc,
374 messageId: 'missingReturnType',
375 });
376 });
377 checkParameters(node);
378 }
379 },
380});
381//# sourceMappingURL=explicit-module-boundary-types.js.map
\No newline at end of file