β¬ οΈ Back to Table of Contents
π use-unknown-in-catch-callback-variable¶
π Analysis Summary¶
| Metric | Count |
|---|---|
| π§ Functions | 8 |
| π¦ Imports | 10 |
| π Variables & Constants | 1 |
| π Type Aliases | 1 |
π Table of Contents¶
π οΈ File Location:¶
π packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts
π€ Default Export¶
| Property | Value |
|---|---|
name |
'use-unknown-in-catch-callback-variable' |
meta.type |
'suggestion' |
meta.docs.description |
'Enforce typing arguments in Promise rejection callbacks as unknown' |
meta.docs.recommended |
'strict' |
meta.docs.requiresTypeChecking |
true |
meta.hasSuggestions |
true |
meta.messages.addUnknownRestTypeAnnotationSuggestion |
'Add an explicit : [unknown] type annotation to the rejection callback rest variable.' |
meta.messages.addUnknownTypeAnnotationSuggestion |
'Add an explicit : unknown type annotation to the rejection callback variable.' |
meta.messages.useUnknown |
useUnknownMessageBase |
meta.messages.useUnknownArrayDestructuringPattern |
${useUnknownMessageBase} The thrown error may not be iterable. |
meta.messages.useUnknownObjectDestructuringPattern |
${ useUnknownMessageBase } The thrown error may be nullable, or may not have the expected shape. |
meta.messages.wrongRestTypeAnnotationSuggestion |
'Change existing type annotation to : [unknown].' |
meta.messages.wrongTypeAnnotationSuggestion |
'Change existing type annotation to : unknown.' |
meta.schema |
[] |
defaultOptions |
[] |
Entry point: create β documented under Functions.
π¦ Imports¶
| Name | Source |
|---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
ReportDescriptor |
@typescript-eslint/utils/ts-eslint |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getParserServices |
../util |
getStaticMemberAccessValue |
../util |
isParenlessArrowFunction |
../util |
isRestParameterDeclaration |
../util |
nullThrows |
../util |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
useUnknownMessageBase |
"Prefer the safe: unknownfor a{...| const |'Prefer the safe : unknown for a {{method}} callback variable.'`} |
β |
Functions¶
create(context: any): { CallExpression({ arguments: args, callee }: { arguments: ⦶
Parameters:
contextany
Returns: { CallExpression({ arguments: args, callee }: { arguments: any; callee: any; }): void; }
Calls:
getParserServices (from ../util)program.getTypeCheckertsutils.unionConstituentstsutils.getCallSignaturesOfTypecallSignature.parameters.atchecker.getTypeOfSymbolisRestParameterDeclaration (from ../util)checker.isArrayTypechecker.getTypeArgumentschecker.isTupleTypetsutils.isIntrinsicUnknownTypecollectFlaggedNodesnullThrows (from ../util)node.expressions.atesTreeNodeToTSNodeMap.getchecker.getTypeAtLocationisFlaggableHandlerTypeargument.params.atisParenlessArrowFunction (from ../util)fixer.insertTextBeforefixer.insertTextAfterfixer.replaceTextgetStaticMemberAccessValue (from ../util)( [ { append: '', argIndexToCheck: 0, method: 'catch' }, { append: ' rejection', argIndexToCheck: 1, method: 'then' }, ] satisfies { append: string; argIndexToCheck: number; method: string; }[] ).findargs.sliceargsToCheck.sometsutils.isThenableTyperefineReportIfPossiblecontext.report
Internal Comments:
// Ignore any non-function components to the type. Those are not this rule's problem.
// it's not an issue if there's no catch variable at all.
// a rest arg that's not an array or tuple should definitely be flagged.
/**
* Analyzes the syntax of the catch argument and makes a best effort to pinpoint
* why it's reporting, and to come up with a suggested fix if possible.
*
* This function is explicitly operating under the assumption that the
* rule _is reporting_, so it is not guaranteed to be sound to call otherwise.
*/
// Function expressions can't have parameter properties; those only exist in constructors. (x2)
// Need to be enough args to check (x2)
// Argument to check, and all arguments before it, must be "ordinary" arguments (i.e. no spread arguments) (x2)
// promise.catch(f), promise.catch(() => {}), promise.catch(<expression>, <<other-args>>) (x2)
// the `some` check above has already excluded `SpreadElement`, so we are safe to assert the same (x2)
// We are now guaranteed to report, but we have a bit of work to do (x2)
// to determine exactly where, and whether we can fix it. (x2)
Code
create(context) {
const { esTreeNodeToTSNodeMap, program } = getParserServices(context);
const checker = program.getTypeChecker();
function isFlaggableHandlerType(type: ts.Type): boolean {
for (const unionPart of tsutils.unionConstituents(type)) {
const callSignatures = tsutils.getCallSignaturesOfType(unionPart);
if (callSignatures.length === 0) {
// Ignore any non-function components to the type. Those are not this rule's problem.
continue;
}
for (const callSignature of callSignatures) {
const firstParam = callSignature.parameters.at(0);
if (!firstParam) {
// it's not an issue if there's no catch variable at all.
continue;
}
let firstParamType = checker.getTypeOfSymbol(firstParam);
const decl = firstParam.valueDeclaration;
if (decl != null && isRestParameterDeclaration(decl)) {
if (checker.isArrayType(firstParamType)) {
firstParamType = checker.getTypeArguments(firstParamType)[0];
} else if (checker.isTupleType(firstParamType)) {
firstParamType = checker.getTypeArguments(firstParamType)[0];
} else {
// a rest arg that's not an array or tuple should definitely be flagged.
return true;
}
}
if (!tsutils.isIntrinsicUnknownType(firstParamType)) {
return true;
}
}
}
return false;
}
function collectFlaggedNodes(
node: Exclude<TSESTree.Node, TSESTree.SpreadElement>,
): (TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression)[] {
switch (node.type) {
case AST_NODE_TYPES.LogicalExpression:
return [
...collectFlaggedNodes(node.left),
...collectFlaggedNodes(node.right),
];
case AST_NODE_TYPES.SequenceExpression:
return collectFlaggedNodes(
nullThrows(
node.expressions.at(-1),
'sequence expression must have multiple expressions',
),
);
case AST_NODE_TYPES.ConditionalExpression:
return [
...collectFlaggedNodes(node.consequent),
...collectFlaggedNodes(node.alternate),
];
case AST_NODE_TYPES.ArrowFunctionExpression:
case AST_NODE_TYPES.FunctionExpression:
{
const argument = esTreeNodeToTSNodeMap.get(node);
const typeOfArgument = checker.getTypeAtLocation(argument);
if (isFlaggableHandlerType(typeOfArgument)) {
return [node];
}
}
break;
default:
break;
}
return [];
}
/**
* Analyzes the syntax of the catch argument and makes a best effort to pinpoint
* why it's reporting, and to come up with a suggested fix if possible.
*
* This function is explicitly operating under the assumption that the
* rule _is reporting_, so it is not guaranteed to be sound to call otherwise.
*/
function refineReportIfPossible(
argument: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
): Partial<ReportDescriptor<MessageIds>> | undefined {
const catchVariableOuterWithIncorrectTypes = nullThrows(
argument.params.at(0),
'There should have been at least one parameter for the rule to have flagged.',
);
// Function expressions can't have parameter properties; those only exist in constructors.
const catchVariableOuter =
catchVariableOuterWithIncorrectTypes as Exclude<
typeof catchVariableOuterWithIncorrectTypes,
TSESTree.TSParameterProperty
>;
const catchVariableInner =
catchVariableOuter.type === AST_NODE_TYPES.AssignmentPattern
? catchVariableOuter.left
: catchVariableOuter;
switch (catchVariableInner.type) {
case AST_NODE_TYPES.Identifier: {
const catchVariableTypeAnnotation = catchVariableInner.typeAnnotation;
if (catchVariableTypeAnnotation == null) {
return {
node: catchVariableOuter,
suggest: [
{
messageId: 'addUnknownTypeAnnotationSuggestion',
fix: (fixer: TSESLint.RuleFixer): TSESLint.RuleFix[] => {
if (
argument.type ===
AST_NODE_TYPES.ArrowFunctionExpression &&
isParenlessArrowFunction(argument, context.sourceCode)
) {
return [
fixer.insertTextBefore(catchVariableInner, '('),
fixer.insertTextAfter(catchVariableInner, ': unknown)'),
];
}
return [
fixer.insertTextAfter(catchVariableInner, ': unknown'),
];
},
},
],
};
}
return {
node: catchVariableOuter,
suggest: [
{
messageId: 'wrongTypeAnnotationSuggestion',
fix: (fixer: TSESLint.RuleFixer): TSESLint.RuleFix =>
fixer.replaceText(catchVariableTypeAnnotation, ': unknown'),
},
],
};
}
case AST_NODE_TYPES.ArrayPattern: {
return {
node: catchVariableOuter,
messageId: 'useUnknownArrayDestructuringPattern',
};
}
case AST_NODE_TYPES.ObjectPattern: {
return {
node: catchVariableOuter,
messageId: 'useUnknownObjectDestructuringPattern',
};
}
case AST_NODE_TYPES.RestElement: {
const catchVariableTypeAnnotation = catchVariableInner.typeAnnotation;
if (catchVariableTypeAnnotation == null) {
return {
node: catchVariableOuter,
suggest: [
{
messageId: 'addUnknownRestTypeAnnotationSuggestion',
fix: (fixer): TSESLint.RuleFix =>
fixer.insertTextAfter(catchVariableInner, ': [unknown]'),
},
],
};
}
return {
node: catchVariableOuter,
suggest: [
{
messageId: 'wrongRestTypeAnnotationSuggestion',
fix: (fixer): TSESLint.RuleFix =>
fixer.replaceText(catchVariableTypeAnnotation, ': [unknown]'),
},
],
};
}
}
}
return {
CallExpression({ arguments: args, callee }): void {
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
return;
}
const staticMemberAccessKey = getStaticMemberAccessValue(
callee,
context,
);
if (!staticMemberAccessKey) {
return;
}
const promiseMethodInfo = (
[
{ append: '', argIndexToCheck: 0, method: 'catch' },
{ append: ' rejection', argIndexToCheck: 1, method: 'then' },
] satisfies {
append: string;
argIndexToCheck: number;
method: string;
}[]
).find(({ method }) => staticMemberAccessKey === method);
if (!promiseMethodInfo) {
return;
}
// Need to be enough args to check
const { argIndexToCheck, ...data } = promiseMethodInfo;
if (args.length < argIndexToCheck + 1) {
return;
}
// Argument to check, and all arguments before it, must be "ordinary" arguments (i.e. no spread arguments)
// promise.catch(f), promise.catch(() => {}), promise.catch(<expression>, <<other-args>>)
const argsToCheck = args.slice(0, argIndexToCheck + 1);
if (
argsToCheck.some(({ type }) => type === AST_NODE_TYPES.SpreadElement)
) {
return;
}
if (
!tsutils.isThenableType(
checker,
esTreeNodeToTSNodeMap.get(callee),
checker.getTypeAtLocation(esTreeNodeToTSNodeMap.get(callee.object)),
)
) {
return;
}
// the `some` check above has already excluded `SpreadElement`, so we are safe to assert the same
const argToCheck = argsToCheck[argIndexToCheck] as Exclude<
TSESTree.Node,
TSESTree.SpreadElement
>;
for (const node of collectFlaggedNodes(argToCheck)) {
// We are now guaranteed to report, but we have a bit of work to do
// to determine exactly where, and whether we can fix it.
const overrides = refineReportIfPossible(node);
context.report({
node,
messageId: 'useUnknown',
data,
...overrides,
});
}
},
};
}
Internal helpers¶
Declared inside another function in this file.
isFlaggableHandlerType(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
tsutils.unionConstituentstsutils.getCallSignaturesOfTypecallSignature.parameters.atchecker.getTypeOfSymbolisRestParameterDeclaration (from ../util)checker.isArrayTypechecker.getTypeArgumentschecker.isTupleTypetsutils.isIntrinsicUnknownType
Internal Comments:
// Ignore any non-function components to the type. Those are not this rule's problem.
// it's not an issue if there's no catch variable at all.
// a rest arg that's not an array or tuple should definitely be flagged.
Code
function isFlaggableHandlerType(type: ts.Type): boolean {
for (const unionPart of tsutils.unionConstituents(type)) {
const callSignatures = tsutils.getCallSignaturesOfType(unionPart);
if (callSignatures.length === 0) {
// Ignore any non-function components to the type. Those are not this rule's problem.
continue;
}
for (const callSignature of callSignatures) {
const firstParam = callSignature.parameters.at(0);
if (!firstParam) {
// it's not an issue if there's no catch variable at all.
continue;
}
let firstParamType = checker.getTypeOfSymbol(firstParam);
const decl = firstParam.valueDeclaration;
if (decl != null && isRestParameterDeclaration(decl)) {
if (checker.isArrayType(firstParamType)) {
firstParamType = checker.getTypeArguments(firstParamType)[0];
} else if (checker.isTupleType(firstParamType)) {
firstParamType = checker.getTypeArguments(firstParamType)[0];
} else {
// a rest arg that's not an array or tuple should definitely be flagged.
return true;
}
}
if (!tsutils.isIntrinsicUnknownType(firstParamType)) {
return true;
}
}
}
return false;
}
collectFlaggedNodes(node: Exclude<TSESTree.Node, TSESTree.SpreadEβ¦): (TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpres⦶
Parameters:
nodeExclude<TSESTree.Node, TSESTree.SpreadElement>
Returns: (TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression)[]
Calls:
collectFlaggedNodesnullThrows (from ../util)node.expressions.atesTreeNodeToTSNodeMap.getchecker.getTypeAtLocationisFlaggableHandlerType
Code
function collectFlaggedNodes(
node: Exclude<TSESTree.Node, TSESTree.SpreadElement>,
): (TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression)[] {
switch (node.type) {
case AST_NODE_TYPES.LogicalExpression:
return [
...collectFlaggedNodes(node.left),
...collectFlaggedNodes(node.right),
];
case AST_NODE_TYPES.SequenceExpression:
return collectFlaggedNodes(
nullThrows(
node.expressions.at(-1),
'sequence expression must have multiple expressions',
),
);
case AST_NODE_TYPES.ConditionalExpression:
return [
...collectFlaggedNodes(node.consequent),
...collectFlaggedNodes(node.alternate),
];
case AST_NODE_TYPES.ArrowFunctionExpression:
case AST_NODE_TYPES.FunctionExpression:
{
const argument = esTreeNodeToTSNodeMap.get(node);
const typeOfArgument = checker.getTypeAtLocation(argument);
if (isFlaggableHandlerType(typeOfArgument)) {
return [node];
}
}
break;
default:
break;
}
return [];
}
refineReportIfPossible(argument: TSESTree.ArrowFunctionExpression | TSESβ¦): Partial<ReportDescriptor<MessageIds>> | undefined¶
Analyzes the syntax of the catch argument and makes a best effort to pinpoint why it's reporting, and to come up with a suggested fix if possible.
This function is explicitly operating under the assumption that the rule is reporting, so it is not guaranteed to be sound to call otherwise.
Raw JSDoc
/**
* Analyzes the syntax of the catch argument and makes a best effort to pinpoint
* why it's reporting, and to come up with a suggested fix if possible.
*
* This function is explicitly operating under the assumption that the
* rule _is reporting_, so it is not guaranteed to be sound to call otherwise.
*/
Calls:
nullThrows (from ../util)argument.params.atisParenlessArrowFunction (from ../util)fixer.insertTextBeforefixer.insertTextAfterfixer.replaceText
Internal Comments:
Code
function refineReportIfPossible(
argument: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
): Partial<ReportDescriptor<MessageIds>> | undefined {
const catchVariableOuterWithIncorrectTypes = nullThrows(
argument.params.at(0),
'There should have been at least one parameter for the rule to have flagged.',
);
// Function expressions can't have parameter properties; those only exist in constructors.
const catchVariableOuter =
catchVariableOuterWithIncorrectTypes as Exclude<
typeof catchVariableOuterWithIncorrectTypes,
TSESTree.TSParameterProperty
>;
const catchVariableInner =
catchVariableOuter.type === AST_NODE_TYPES.AssignmentPattern
? catchVariableOuter.left
: catchVariableOuter;
switch (catchVariableInner.type) {
case AST_NODE_TYPES.Identifier: {
const catchVariableTypeAnnotation = catchVariableInner.typeAnnotation;
if (catchVariableTypeAnnotation == null) {
return {
node: catchVariableOuter,
suggest: [
{
messageId: 'addUnknownTypeAnnotationSuggestion',
fix: (fixer: TSESLint.RuleFixer): TSESLint.RuleFix[] => {
if (
argument.type ===
AST_NODE_TYPES.ArrowFunctionExpression &&
isParenlessArrowFunction(argument, context.sourceCode)
) {
return [
fixer.insertTextBefore(catchVariableInner, '('),
fixer.insertTextAfter(catchVariableInner, ': unknown)'),
];
}
return [
fixer.insertTextAfter(catchVariableInner, ': unknown'),
];
},
},
],
};
}
return {
node: catchVariableOuter,
suggest: [
{
messageId: 'wrongTypeAnnotationSuggestion',
fix: (fixer: TSESLint.RuleFixer): TSESLint.RuleFix =>
fixer.replaceText(catchVariableTypeAnnotation, ': unknown'),
},
],
};
}
case AST_NODE_TYPES.ArrayPattern: {
return {
node: catchVariableOuter,
messageId: 'useUnknownArrayDestructuringPattern',
};
}
case AST_NODE_TYPES.ObjectPattern: {
return {
node: catchVariableOuter,
messageId: 'useUnknownObjectDestructuringPattern',
};
}
case AST_NODE_TYPES.RestElement: {
const catchVariableTypeAnnotation = catchVariableInner.typeAnnotation;
if (catchVariableTypeAnnotation == null) {
return {
node: catchVariableOuter,
suggest: [
{
messageId: 'addUnknownRestTypeAnnotationSuggestion',
fix: (fixer): TSESLint.RuleFix =>
fixer.insertTextAfter(catchVariableInner, ': [unknown]'),
},
],
};
}
return {
node: catchVariableOuter,
suggest: [
{
messageId: 'wrongRestTypeAnnotationSuggestion',
fix: (fixer): TSESLint.RuleFix =>
fixer.replaceText(catchVariableTypeAnnotation, ': [unknown]'),
},
],
};
}
}
}
suggest.fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix[]¶
Parameters:
fixerTSESLint.RuleFixer
Returns: TSESLint.RuleFix[]
Calls:
isParenlessArrowFunction (from ../util)fixer.insertTextBeforefixer.insertTextAfter
Code
(fixer: TSESLint.RuleFixer): TSESLint.RuleFix[] => {
if (
argument.type ===
AST_NODE_TYPES.ArrowFunctionExpression &&
isParenlessArrowFunction(argument, context.sourceCode)
) {
return [
fixer.insertTextBefore(catchVariableInner, '('),
fixer.insertTextAfter(catchVariableInner, ': unknown)'),
];
}
return [
fixer.insertTextAfter(catchVariableInner, ': unknown'),
];
}
suggest.fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix¶
Parameters:
fixerTSESLint.RuleFixer
Returns: TSESLint.RuleFix
Calls:
fixer.replaceText
Code
suggest.fix(fixer: any): TSESLint.RuleFix¶
Parameters:
fixerany
Returns: TSESLint.RuleFix
Calls:
fixer.insertTextAfter
suggest.fix(fixer: any): TSESLint.RuleFix¶
Parameters:
fixerany
Returns: TSESLint.RuleFix
Calls:
fixer.replaceText
Type Aliases¶
MessageIds¶
type MessageIds = | 'addUnknownRestTypeAnnotationSuggestion'
| 'addUnknownTypeAnnotationSuggestion'
| 'useUnknown'
| 'useUnknownArrayDestructuringPattern'
| 'useUnknownObjectDestructuringPattern'
| 'wrongRestTypeAnnotationSuggestion'
| 'wrongTypeAnnotationSuggestion';
Generated by Syntax Scribe