| b69ab31 | | | 1 | /** |
| b69ab31 | | | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| b69ab31 | | | 3 | * |
| b69ab31 | | | 4 | * This source code is licensed under the MIT license found in the |
| b69ab31 | | | 5 | * LICENSE file in the root directory of this source tree. |
| b69ab31 | | | 6 | */ |
| b69ab31 | | | 7 | |
| b69ab31 | | | 8 | module.exports = { |
| b69ab31 | | | 9 | meta: { |
| b69ab31 | | | 10 | type: 'problem', |
| b69ab31 | | | 11 | docs: { |
| b69ab31 | | | 12 | description: |
| b69ab31 | | | 13 | 'Require explicit type annotations for callback parameters in Internal API promise chains (.then and .catch)', |
| b69ab31 | | | 14 | }, |
| b69ab31 | | | 15 | fixable: null, // Not automatically fixable |
| b69ab31 | | | 16 | messages: { |
| b69ab31 | | | 17 | missingTypeAnnotation: |
| b69ab31 | | | 18 | 'Parameters in callbacks for Internal API promise chains must have explicit type annotations to avoid "any" type errors when mirrored to open source.', |
| b69ab31 | | | 19 | }, |
| b69ab31 | | | 20 | schema: [], // no options |
| b69ab31 | | | 21 | }, |
| b69ab31 | | | 22 | create(context) { |
| b69ab31 | | | 23 | // Helper function to check if a node is part of an Internal API call chain |
| b69ab31 | | | 24 | function isInternalApiCall(node) { |
| b69ab31 | | | 25 | // Start from the object of the .then() call and traverse up |
| b69ab31 | | | 26 | let current = node; |
| b69ab31 | | | 27 | |
| b69ab31 | | | 28 | while (current) { |
| b69ab31 | | | 29 | // Check for direct Internal.something pattern |
| b69ab31 | | | 30 | if ( |
| b69ab31 | | | 31 | current.type === 'MemberExpression' && |
| b69ab31 | | | 32 | current.object && |
| b69ab31 | | | 33 | current.object.type === 'Identifier' && |
| b69ab31 | | | 34 | current.object.name === 'Internal' |
| b69ab31 | | | 35 | ) { |
| b69ab31 | | | 36 | return true; |
| b69ab31 | | | 37 | } |
| b69ab31 | | | 38 | |
| b69ab31 | | | 39 | // Check for property access on Internal |
| b69ab31 | | | 40 | if (current.type === 'MemberExpression' && current.object) { |
| b69ab31 | | | 41 | current = current.object; |
| b69ab31 | | | 42 | continue; |
| b69ab31 | | | 43 | } |
| b69ab31 | | | 44 | |
| b69ab31 | | | 45 | // Check for call expressions |
| b69ab31 | | | 46 | if (current.type === 'CallExpression' && current.callee) { |
| b69ab31 | | | 47 | current = current.callee; |
| b69ab31 | | | 48 | continue; |
| b69ab31 | | | 49 | } |
| b69ab31 | | | 50 | |
| b69ab31 | | | 51 | // Check for optional chaining |
| b69ab31 | | | 52 | if (current.type === 'ChainExpression' && current.expression) { |
| b69ab31 | | | 53 | current = current.expression; |
| b69ab31 | | | 54 | continue; |
| b69ab31 | | | 55 | } |
| b69ab31 | | | 56 | |
| b69ab31 | | | 57 | // If we can't traverse further up, break the loop |
| b69ab31 | | | 58 | break; |
| b69ab31 | | | 59 | } |
| b69ab31 | | | 60 | |
| b69ab31 | | | 61 | return false; |
| b69ab31 | | | 62 | } |
| b69ab31 | | | 63 | |
| b69ab31 | | | 64 | return { |
| b69ab31 | | | 65 | // Look for .then() and .catch() calls |
| b69ab31 | | | 66 | 'CallExpression[callee.property.name="then"], CallExpression[callee.property.name="catch"]'( |
| b69ab31 | | | 67 | node, |
| b69ab31 | | | 68 | ) { |
| b69ab31 | | | 69 | const methodName = node.callee.property.name; |
| b69ab31 | | | 70 | |
| b69ab31 | | | 71 | // Check if the object of the .then() or .catch() call is part of an Internal API call chain |
| b69ab31 | | | 72 | if (!isInternalApiCall(node.callee.object)) { |
| b69ab31 | | | 73 | return; |
| b69ab31 | | | 74 | } |
| b69ab31 | | | 75 | |
| b69ab31 | | | 76 | // Check if there are arguments to the call |
| b69ab31 | | | 77 | if (!node.arguments || node.arguments.length === 0) { |
| b69ab31 | | | 78 | return; |
| b69ab31 | | | 79 | } |
| b69ab31 | | | 80 | |
| b69ab31 | | | 81 | // Get the callback function (first argument to .then() or .catch()) |
| b69ab31 | | | 82 | const callback = node.arguments[0]; |
| b69ab31 | | | 83 | |
| b69ab31 | | | 84 | // Check if it's an arrow function or function expression |
| b69ab31 | | | 85 | if (callback.type !== 'ArrowFunctionExpression' && callback.type !== 'FunctionExpression') { |
| b69ab31 | | | 86 | return; |
| b69ab31 | | | 87 | } |
| b69ab31 | | | 88 | |
| b69ab31 | | | 89 | // Check each parameter for type annotations |
| b69ab31 | | | 90 | const params = callback.params || []; |
| b69ab31 | | | 91 | for (const param of params) { |
| b69ab31 | | | 92 | // If the parameter doesn't have a type annotation, report an error |
| b69ab31 | | | 93 | if (!param.typeAnnotation) { |
| b69ab31 | | | 94 | context.report({ |
| b69ab31 | | | 95 | node: param, |
| b69ab31 | | | 96 | messageId: 'missingTypeAnnotation', |
| b69ab31 | | | 97 | }); |
| b69ab31 | | | 98 | } |
| b69ab31 | | | 99 | } |
| b69ab31 | | | 100 | }, |
| b69ab31 | | | 101 | }; |
| b69ab31 | | | 102 | }, |
| b69ab31 | | | 103 | }; |