-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(eslint-plugin): [return-await] add option to report in error-handling scenarios only, and deprecate "never" #9364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1384bf5
bc8310f
7c5340f
624eeee
68d8c5b
845812e
93c66be
59192fc
c2bac5c
5150439
d050fe4
c12dfeb
5ea1b81
584aad9
b27b673
14f801b
9309769
f5a4060
8cd6116
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
kirkwaiblinger marked this conversation as resolved.
Show resolved
Hide resolved
|
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
<
EDBE
h3 data-view-component="true" class="blankslate-heading"> Uh oh!
There was an error while loading. Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,12 @@ interface ScopeInfo { | |
owningFunc: FunctionNode; | ||
} | ||
|
||
type Option = | ||
| 'in-try-catch' | ||
| 'always' | ||
| 'never' | ||
| 'error-handling-correctness-only'; | ||
|
||
export default createRule({ | ||
name: 'return-await', | ||
meta: { | ||
|
@@ -50,7 +56,12 @@ export default createRule({ | |
schema: [ | ||
{ | ||
type: 'string', | ||
enum: ['in-try-catch', 'always', 'never'], | ||
enum: [ | ||
'in-try-catch', | ||
'always', | ||
'never', | ||
'error-handling-correctness-only', | ||
] satisfies Option[], | ||
}, | ||
], | ||
}, | ||
|
@@ -271,92 +282,70 @@ export default createRule({ | |
const type = checker.getTypeAtLocation(child); | ||
const isThenable = tsutils.isThenableType(checker, expression, type); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. github's diffing gets real confused about what actually changed here if you don't use the "ignore whitespace" here. Be sure to use that. |
||
|
||
if (!isAwait && !isThenable) { | ||
return; | ||
} | ||
|
||
if (isAwait && !isThenable) { | ||
// any/unknown could be thenable; do not auto-fix | ||
const useAutoFix = !(isTypeAnyType(type) || isTypeUnknownType(type)); | ||
|
||
context.report({ | ||
messageId: 'nonPromiseAwait', | ||
node, | ||
...fixOrSuggest(useAutoFix, { | ||
messageId: 'nonPromiseAwait', | ||
fix: fixer => removeAwait(fixer, node), | ||
}), | ||
}); | ||
return; | ||
} | ||
// handle awaited _non_thenables | ||
|
||
const affectsErrorHandling = | ||
affectsExplicitErrorHandling(expression) || | ||
affectsExplicitResourceManagement(node); | ||
const useAutoFix = !affectsErrorHandling; | ||
if (!isThenable) { | ||
if (isAwait) { | ||
// any/unknown could be thenable; do not auto-fix | ||
const useAutoFix = !(isTypeAnyType(type) || isTypeUnknownType(type)); | ||
|
||
if (option === 'always') { | ||
if (!isAwait && isThenable) { | ||
context.report({ | ||
messageId: 'requiredPromiseAwait', | ||
messageId: 'nonPromiseAwait', | ||
node, | ||
...fixOrSuggest(useAutoFix, { | ||
messageId: 'requiredPromiseAwaitSuggestion', | ||
fix: fixer => | ||
insertAwait( | ||
fixer, | ||
node, | ||
isHigherPrecedenceThanAwait(expression), | ||
), | ||
messageId: 'nonPromiseAwait', | ||
fix: fixer => removeAwait(fixer, node), | ||
}), | ||
}); | ||
} | ||
|
||
return; | ||
} | ||
|
||
if (option === 'never') { | ||
if (isAwait) { | ||
context.report({ | ||
messageId: 'disallowedPromiseAwait', | ||
node, | ||
...fixOrSuggest(useAutoFix, { | ||
messageId: 'disallowedPromiseAwaitSuggestion', | ||
fix: fixer => removeAwait(fixer, node), | ||
}), | ||
}); | ||
} | ||
// At this point it's definitely a thenable. | ||
|
||
return; | ||
} | ||
const affectsErrorHandling = | ||
affectsExplicitErrorHandling(expression) || | ||
affectsExplicitResourceManagement(node); | ||
const useAutoFix = !affectsErrorHandling; | ||
|
||
if (option === 'in-try-catch') { | ||
if (isAwait && !affectsErrorHandling) { | ||
context.report({ | ||
messageId: 'disallowedPromiseAwait', | ||
node, | ||
...fixOrSuggest(useAutoFix, { | ||
messageId: 'disallowedPromiseAwaitSuggestion', | ||
fix: fixer => removeAwait(fixer, node), | ||
}), | ||
}); | ||
} else if (!isAwait && affectsErrorHandling) { | ||
context.report({ | ||
messageId: 'requiredPromiseAwait', | ||
node, | ||
...fixOrSuggest(useAutoFix, { | ||
messageId: 'requiredPromiseAwaitSuggestion', | ||
fix: fixer => | ||
insertAwait( | ||
fixer, | ||
node, | ||
isHigherPrecedenceThanAwait(expression), | ||
), | ||
}), | ||
}); | ||
} | ||
const ruleConfiguration = getConfiguration(option as Option); | ||
|
||
return; | ||
const shouldAwaitInCurrentContext = affectsErrorHandling | ||
? ruleConfiguration.errorHandlingContext | ||
: ruleConfiguration.ordinaryContext; | ||
|
||
switch (shouldAwaitInCurrentContext) { | ||
case "don't-care": | ||
break; | ||
case 'await': | ||
if (!isAwait) { | ||
context.report({ | ||
messageId: 'requiredPromiseAwait', | ||
node, | ||
...fixOrSuggest(useAutoFix, { | ||
messageId: 'requiredPromiseAwaitSuggestion', | ||
fix: fixer => | ||
insertAwait( | ||
fixer, | ||
node, | ||
isHigherPrecedenceThanAwait(expression), | ||
), | ||
}), | ||
}); | ||
} | ||
break; | ||
case 'no-await': | ||
if (isAwait) { | ||
context.report({ | ||
messageId: 'disallowedPromiseAwait', | ||
node, | ||
...fixOrSuggest(useAutoFix, { | ||
messageId: 'disallowedPromiseAwaitSuggestion', | ||
fix: fixer => removeAwait(fixer, node), | ||
}), | ||
}); | ||
} | ||
break; | ||
} | ||
} | ||
|
||
|
@@ -406,6 +395,38 @@ export default createRule({ | |
}, | ||
}); | ||
|
||
type WhetherToAwait = 'await' | 'no-await' | "don't-care"; | ||
|
||
interface RuleConfiguration { | ||
ordinaryContext: WhetherToAwait; | ||
errorHandlingContext: WhetherToAwait; | ||
} | ||
|
||
function getConfiguration(option: Option): RuleConfiguration { | ||
switch (option) { | ||
case 'always': | ||
return { | ||
ordinaryContext: 'await', | ||
errorHandlingContext: 'await', | ||
}; | ||
case 'never': | ||
return { | ||
ordinaryContext: 'no-await', | ||
errorHandlingContext: 'no-await', | ||
}; | ||
case 'error-handling-correctness-only': | ||
return { | ||
ordinaryContext: "don't-care", | ||
errorHandlingContext: 'await', | ||
}; | ||
case 'in-try-catch': | ||
return { | ||
ordinaryContext: 'no-await', | ||
errorHandlingContext: 'await', | ||
}; | ||
} | ||
} | ||
|
||
function fixOrSuggest<MessageId extends string>( | ||
useFix: boolean, | ||
suggestion: TSESLint.SuggestionReportDescriptor<MessageId>, | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.