addons/eslint-rules/internal-promise-callback-types.jsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318module.exports = {
b69ab319 meta: {
b69ab3110 type: 'problem',
b69ab3111 docs: {
b69ab3112 description:
b69ab3113 'Require explicit type annotations for callback parameters in Internal API promise chains (.then and .catch)',
b69ab3114 },
b69ab3115 fixable: null, // Not automatically fixable
b69ab3116 messages: {
b69ab3117 missingTypeAnnotation:
b69ab3118 'Parameters in callbacks for Internal API promise chains must have explicit type annotations to avoid "any" type errors when mirrored to open source.',
b69ab3119 },
b69ab3120 schema: [], // no options
b69ab3121 },
b69ab3122 create(context) {
b69ab3123 // Helper function to check if a node is part of an Internal API call chain
b69ab3124 function isInternalApiCall(node) {
b69ab3125 // Start from the object of the .then() call and traverse up
b69ab3126 let current = node;
b69ab3127
b69ab3128 while (current) {
b69ab3129 // Check for direct Internal.something pattern
b69ab3130 if (
b69ab3131 current.type === 'MemberExpression' &&
b69ab3132 current.object &&
b69ab3133 current.object.type === 'Identifier' &&
b69ab3134 current.object.name === 'Internal'
b69ab3135 ) {
b69ab3136 return true;
b69ab3137 }
b69ab3138
b69ab3139 // Check for property access on Internal
b69ab3140 if (current.type === 'MemberExpression' && current.object) {
b69ab3141 current = current.object;
b69ab3142 continue;
b69ab3143 }
b69ab3144
b69ab3145 // Check for call expressions
b69ab3146 if (current.type === 'CallExpression' && current.callee) {
b69ab3147 current = current.callee;
b69ab3148 continue;
b69ab3149 }
b69ab3150
b69ab3151 // Check for optional chaining
b69ab3152 if (current.type === 'ChainExpression' && current.expression) {
b69ab3153 current = current.expression;
b69ab3154 continue;
b69ab3155 }
b69ab3156
b69ab3157 // If we can't traverse further up, break the loop
b69ab3158 break;
b69ab3159 }
b69ab3160
b69ab3161 return false;
b69ab3162 }
b69ab3163
b69ab3164 return {
b69ab3165 // Look for .then() and .catch() calls
b69ab3166 'CallExpression[callee.property.name="then"], CallExpression[callee.property.name="catch"]'(
b69ab3167 node,
b69ab3168 ) {
b69ab3169 const methodName = node.callee.property.name;
b69ab3170
b69ab3171 // Check if the object of the .then() or .catch() call is part of an Internal API call chain
b69ab3172 if (!isInternalApiCall(node.callee.object)) {
b69ab3173 return;
b69ab3174 }
b69ab3175
b69ab3176 // Check if there are arguments to the call
b69ab3177 if (!node.arguments || node.arguments.length === 0) {
b69ab3178 return;
b69ab3179 }
b69ab3180
b69ab3181 // Get the callback function (first argument to .then() or .catch())
b69ab3182 const callback = node.arguments[0];
b69ab3183
b69ab3184 // Check if it's an arrow function or function expression
b69ab3185 if (callback.type !== 'ArrowFunctionExpression' && callback.type !== 'FunctionExpression') {
b69ab3186 return;
b69ab3187 }
b69ab3188
b69ab3189 // Check each parameter for type annotations
b69ab3190 const params = callback.params || [];
b69ab3191 for (const param of params) {
b69ab3192 // If the parameter doesn't have a type annotation, report an error
b69ab3193 if (!param.typeAnnotation) {
b69ab3194 context.report({
b69ab3195 node: param,
b69ab3196 messageId: 'missingTypeAnnotation',
b69ab3197 });
b69ab3198 }
b69ab3199 }
b69ab31100 },
b69ab31101 };
b69ab31102 },
b69ab31103};