📄 promise-function-async¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 3 |
| 📦 Imports | 10 |
| 📑 Type Aliases | 2 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/promise-function-async.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'promise-function-async' |
meta.type |
'suggestion' |
meta.docs.description |
'Require any function or method that returns a Promise to be marked async' |
meta.docs.requiresTypeChecking |
true |
meta.fixable |
'code' |
meta.messages.missingAsync |
'Functions that return promises must be async.' |
meta.messages.missingAsyncHybridReturn |
'Functions that return promises must be async. Consider adding an explicit return type annotation if the function is ... |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { allowAny: { type: 'boolean', description: 'Whether to ... |
defaultOptions |
[ { allowAny: true, allowedPromiseNames: [], checkArrowFunctions: true, checkFunctionDeclarations: true, checkFunctio... |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
AST_TOKEN_TYPES |
@typescript-eslint/utils |
containsAllTypesByName |
../util |
createRule |
../util |
getFunctionHeadLoc |
../util |
getParserServices |
../util |
isTypeFlagSet |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
Functions¶
create(context: any, [ { allowAny, allowedPromiseNam…: any): { 'FunctionExpression[async = false]'(node: TSESTree.Functi…¶
Parameters:
contextany[ { allowAny, allowedPromiseNames, checkArrowFunctions, checkFunctionDeclarations, checkFunctionExpressions, checkMethodDeclarations, }, ]any
Returns: { 'FunctionExpression[async = false]'(node: TSESTree.FunctionExpression): void; 'FunctionDeclaration[async = false]'(node: TSESTree.FunctionDeclaration): void; 'ArrowFunctionExpression[async = false]'(node: TSESTree.ArrowFunctionExpression): void; }
Calls:
getParserServices (from ../util)services.program.getTypeCheckerservices.getTypeAtLocation(node).getCallSignaturessignatures.mapchecker.getReturnTypeOfSignaturereturnTypes.someisTypeFlagSet (from ../util)context.reportgetFunctionHeadLoc (from ../util)returnTypes.everycontainsAllTypesByName (from ../util)type.isUniontype.types.everynullThrows (from ../util)context.sourceCode.getFirstTokenNullThrowsReasons.MissingTokencontext.sourceCode.getTokenAftercontext.sourceCode.isSpaceBetweencontext.sourceCode.getTokenBeforefixer.insertTextBeforevalidateNode
Internal Comments:
// https://github.com/typescript-eslint/typescript-eslint/issues/5439
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
// Abstract method can't be async
// Getters and setters can't be async
// Report without auto fixer because the return type is unknown
// require all potential return types to be promise/any/unknown (x3)
// If no return type is explicitly set, we check if any parts of the return type match a Promise (instead of requiring all to match). (x3)
// this function is a class method or object function property shorthand (x2)
// the token to put `async` before (x2)
// if there are decorators then skip past them
// if current token is a keyword like `static` or `public`, or the `override` modifier, then skip it
// check if there is a space between key and previous token (x2)
Code
create(
context,
[
{
allowAny,
allowedPromiseNames,
checkArrowFunctions,
checkFunctionDeclarations,
checkFunctionExpressions,
checkMethodDeclarations,
},
],
) {
const allAllowedPromiseNames = new Set([
'Promise',
// https://github.com/typescript-eslint/typescript-eslint/issues/5439
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...allowedPromiseNames!,
]);
const services = getParserServices(context);
const checker = services.program.getTypeChecker();
function validateNode(
node:
| TSESTree.ArrowFunctionExpression
| TSESTree.FunctionDeclaration
| TSESTree.FunctionExpression,
): void {
if (node.parent.type === AST_NODE_TYPES.TSAbstractMethodDefinition) {
// Abstract method can't be async
return;
}
if (
(node.parent.type === AST_NODE_TYPES.Property ||
node.parent.type === AST_NODE_TYPES.MethodDefinition) &&
(node.parent.kind === 'get' || node.parent.kind === 'set')
) {
// Getters and setters can't be async
return;
}
const signatures = services.getTypeAtLocation(node).getCallSignatures();
if (!signatures.length) {
return;
}
const returnTypes = signatures.map(signature =>
checker.getReturnTypeOfSignature(signature),
);
if (
!allowAny &&
returnTypes.some(type =>
isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown),
)
) {
// Report without auto fixer because the return type is unknown
return context.report({
loc: getFunctionHeadLoc(node, context.sourceCode),
node,
messageId: 'missingAsync',
});
}
if (
// require all potential return types to be promise/any/unknown
returnTypes.every(type =>
containsAllTypesByName(
type,
true,
allAllowedPromiseNames,
// If no return type is explicitly set, we check if any parts of the return type match a Promise (instead of requiring all to match).
node.returnType == null,
),
)
) {
const isHybridReturnType = returnTypes.some(
type =>
type.isUnion() &&
!type.types.every(part =>
containsAllTypesByName(part, true, allAllowedPromiseNames),
),
);
context.report({
loc: getFunctionHeadLoc(node, context.sourceCode),
node,
messageId: isHybridReturnType
? 'missingAsyncHybridReturn'
: 'missingAsync',
fix: fixer => {
if (
node.parent.type === AST_NODE_TYPES.MethodDefinition ||
(node.parent.type === AST_NODE_TYPES.Property &&
node.parent.method)
) {
// this function is a class method or object function property shorthand
const method = node.parent;
// the token to put `async` before
let keyToken = nullThrows(
context.sourceCode.getFirstToken(method),
NullThrowsReasons.MissingToken('key token', 'method'),
);
// if there are decorators then skip past them
if (
method.type === AST_NODE_TYPES.MethodDefinition &&
method.decorators.length
) {
const lastDecorator =
method.decorators[method.decorators.length - 1];
keyToken = nullThrows(
context.sourceCode.getTokenAfter(lastDecorator),
NullThrowsReasons.MissingToken('key token', 'last decorator'),
);
}
// if current token is a keyword like `static` or `public`, or the `override` modifier, then skip it
while (
(keyToken.type === AST_TOKEN_TYPES.Keyword ||
(keyToken.type === AST_TOKEN_TYPES.Identifier &&
keyToken.value === 'override')) &&
keyToken.range[0] < method.key.range[0]
) {
keyToken = nullThrows(
context.sourceCode.getTokenAfter(keyToken),
NullThrowsReasons.MissingToken('token', 'modifier keyword'),
);
}
// check if there is a space between key and previous token
const insertSpace = !context.sourceCode.isSpaceBetween(
nullThrows(
context.sourceCode.getTokenBefore(keyToken),
NullThrowsReasons.MissingToken('token', 'keyword'),
),
keyToken,
);
let code = 'async ';
if (insertSpace) {
code = ` ${code}`;
}
return fixer.insertTextBefore(keyToken, code);
}
return fixer.insertTextBefore(node, 'async ');
},
});
}
}
return {
...(checkArrowFunctions && {
'ArrowFunctionExpression[async = false]'(
node: TSESTree.ArrowFunctionExpression,
): void {
validateNode(node);
},
}),
...(checkFunctionDeclarations && {
'FunctionDeclaration[async = false]'(
node: TSESTree.FunctionDeclaration,
): void {
validateNode(node);
},
}),
'FunctionExpression[async = false]'(
node: TSESTree.FunctionExpression,
): void {
if (
node.parent.type === AST_NODE_TYPES.MethodDefinition &&
node.parent.kind === 'method'
) {
if (checkMethodDeclarations) {
validateNode(node);
}
return;
}
if (checkFunctionExpressions) {
validateNode(node);
}
},
};
}
Internal helpers¶
Declared inside another function in this file.
validateNode(node: | TSESTree.ArrowFunctionExpression | TS…): void¶
Parameters:
node| TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression
Returns: void
Calls:
services.getTypeAtLocation(node).getCallSignaturessignatures.mapchecker.getReturnTypeOfSignaturereturnTypes.someisTypeFlagSet (from ../util)context.reportgetFunctionHeadLoc (from ../util)returnTypes.everycontainsAllTypesByName (from ../util)type.isUniontype.types.everynullThrows (from ../util)context.sourceCode.getFirstTokenNullThrowsReasons.MissingTokencontext.sourceCode.getTokenAftercontext.sourceCode.isSpaceBetweencontext.sourceCode.getTokenBeforefixer.insertTextBefore
Internal Comments:
// Abstract method can't be async
// Getters and setters can't be async
// Report without auto fixer because the return type is unknown
// require all potential return types to be promise/any/unknown (x3)
// If no return type is explicitly set, we check if any parts of the return type match a Promise (instead of requiring all to match). (x3)
// this function is a class method or object function property shorthand (x2)
// the token to put `async` before (x2)
// if there are decorators then skip past them
// if current token is a keyword like `static` or `public`, or the `override` modifier, then skip it
// check if there is a space between key and previous token (x2)
Code
function validateNode(
node:
| TSESTree.ArrowFunctionExpression
| TSESTree.FunctionDeclaration
| TSESTree.FunctionExpression,
): void {
if (node.parent.type === AST_NODE_TYPES.TSAbstractMethodDefinition) {
// Abstract method can't be async
return;
}
if (
(node.parent.type === AST_NODE_TYPES.Property ||
node.parent.type === AST_NODE_TYPES.MethodDefinition) &&
(node.parent.kind === 'get' || node.parent.kind === 'set')
) {
// Getters and setters can't be async
return;
}
const signatures = services.getTypeAtLocation(node).getCallSignatures();
if (!signatures.length) {
return;
}
const returnTypes = signatures.map(signature =>
checker.getReturnTypeOfSignature(signature),
);
if (
!allowAny &&
returnTypes.some(type =>
isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown),
)
) {
// Report without auto fixer because the return type is unknown
return context.report({
loc: getFunctionHeadLoc(node, context.sourceCode),
node,
messageId: 'missingAsync',
});
}
if (
// require all potential return types to be promise/any/unknown
returnTypes.every(type =>
containsAllTypesByName(
type,
true,
allAllowedPromiseNames,
// If no return type is explicitly set, we check if any parts of the return type match a Promise (instead of requiring all to match).
node.returnType == null,
),
)
) {
const isHybridReturnType = returnTypes.some(
type =>
type.isUnion() &&
!type.types.every(part =>
containsAllTypesByName(part, true, allAllowedPromiseNames),
),
);
context.report({
loc: getFunctionHeadLoc(node, context.sourceCode),
node,
messageId: isHybridReturnType
? 'missingAsyncHybridReturn'
: 'missingAsync',
fix: fixer => {
if (
node.parent.type === AST_NODE_TYPES.MethodDefinition ||
(node.parent.type === AST_NODE_TYPES.Property &&
node.parent.method)
) {
// this function is a class method or object function property shorthand
const method = node.parent;
// the token to put `async` before
let keyToken = nullThrows(
context.sourceCode.getFirstToken(method),
NullThrowsReasons.MissingToken('key token', 'method'),
);
// if there are decorators then skip past them
if (
method.type === AST_NODE_TYPES.MethodDefinition &&
method.decorators.length
) {
const lastDecorator =
method.decorators[method.decorators.length - 1];
keyToken = nullThrows(
context.sourceCode.getTokenAfter(lastDecorator),
NullThrowsReasons.MissingToken('key token', 'last decorator'),
);
}
// if current token is a keyword like `static` or `public`, or the `override` modifier, then skip it
while (
(keyToken.type === AST_TOKEN_TYPES.Keyword ||
(keyToken.type === AST_TOKEN_TYPES.Identifier &&
keyToken.value === 'override')) &&
keyToken.range[0] < method.key.range[0]
) {
keyToken = nullThrows(
context.sourceCode.getTokenAfter(keyToken),
NullThrowsReasons.MissingToken('token', 'modifier keyword'),
);
}
// check if there is a space between key and previous token
const insertSpace = !context.sourceCode.isSpaceBetween(
nullThrows(
context.sourceCode.getTokenBefore(keyToken),
NullThrowsReasons.MissingToken('token', 'keyword'),
),
keyToken,
);
let code = 'async ';
if (insertSpace) {
code = ` ${code}`;
}
return fixer.insertTextBefore(keyToken, code);
}
return fixer.insertTextBefore(node, 'async ');
},
});
}
}
fix(fixer: any): any¶
Parameters:
fixerany
Returns: any
Calls:
nullThrows (from ../util)context.sourceCode.getFirstTokenNullThrowsReasons.MissingTokencontext.sourceCode.getTokenAftercontext.sourceCode.isSpaceBetweencontext.sourceCode.getTokenBeforefixer.insertTextBefore
Internal Comments:
// this function is a class method or object function property shorthand (x2)
// the token to put `async` before (x2)
// if there are decorators then skip past them
// if current token is a keyword like `static` or `public`, or the `override` modifier, then skip it
// check if there is a space between key and previous token (x2)
Code
fixer => {
if (
node.parent.type === AST_NODE_TYPES.MethodDefinition ||
(node.parent.type === AST_NODE_TYPES.Property &&
node.parent.method)
) {
// this function is a class method or object function property shorthand
const method = node.parent;
// the token to put `async` before
let keyToken = nullThrows(
context.sourceCode.getFirstToken(method),
NullThrowsReasons.MissingToken('key token', 'method'),
);
// if there are decorators then skip past them
if (
method.type === AST_NODE_TYPES.MethodDefinition &&
method.decorators.length
) {
const lastDecorator =
method.decorators[method.decorators.length - 1];
keyToken = nullThrows(
context.sourceCode.getTokenAfter(lastDecorator),
NullThrowsReasons.MissingToken('key token', 'last decorator'),
);
}
// if current token is a keyword like `static` or `public`, or the `override` modifier, then skip it
while (
(keyToken.type === AST_TOKEN_TYPES.Keyword ||
(keyToken.type === AST_TOKEN_TYPES.Identifier &&
keyToken.value === 'override')) &&
keyToken.range[0] < method.key.range[0]
) {
keyToken = nullThrows(
context.sourceCode.getTokenAfter(keyToken),
NullThrowsReasons.MissingToken('token', 'modifier keyword'),
);
}
// check if there is a space between key and previous token
const insertSpace = !context.sourceCode.isSpaceBetween(
nullThrows(
context.sourceCode.getTokenBefore(keyToken),
NullThrowsReasons.MissingToken('token', 'keyword'),
),
keyToken,
);
let code = 'async ';
if (insertSpace) {
code = ` ${code}`;
}
return fixer.insertTextBefore(keyToken, code);
}
return fixer.insertTextBefore(node, 'async ');
}
Type Aliases¶
Options¶
type Options = [
{
allowAny?: boolean;
allowedPromiseNames?: string[];
checkArrowFunctions?: boolean;
checkFunctionDeclarations?: boolean;
checkFunctionExpressions?: boolean;
checkMethodDeclarations?: boolean;
},
];
MessageIds¶
Generated by Syntax Scribe