1+ /* @internal */
2+ namespace ts . codefix {
3+ const fixName = "addVoidToPromise" ;
4+ const fixId = "addVoidToPromise" ;
5+ const errorCodes = [
6+ Diagnostics . Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise . code
7+ ] ;
8+ registerCodeFix ( {
9+ errorCodes,
10+ fixIds : [ fixId ] ,
11+ getCodeActions ( context ) {
12+ const changes = textChanges . ChangeTracker . with ( context , t => makeChange ( t , context . sourceFile , context . span , context . program ) ) ;
13+ if ( changes . length > 0 ) {
14+ return [ createCodeFixAction ( fixName , changes , Diagnostics . Add_void_to_Promise_resolved_without_a_value , fixId , Diagnostics . Add_void_to_all_Promises_resolved_without_a_value ) ] ;
15+ }
16+ } ,
17+ getAllCodeActions ( context : CodeFixAllContext ) {
18+ return codeFixAll ( context , errorCodes , ( changes , diag ) => makeChange ( changes , diag . file , diag , context . program , new Set ( ) ) ) ;
19+ }
20+ } ) ;
21+
22+ function makeChange ( changes : textChanges . ChangeTracker , sourceFile : SourceFile , span : TextSpan , program : Program , seen ?: Set < ParameterDeclaration > ) {
23+ const node = getTokenAtPosition ( sourceFile , span . start ) ;
24+ if ( ! isIdentifier ( node ) || ! isCallExpression ( node . parent ) || node . parent . expression !== node || node . parent . arguments . length !== 0 ) return ;
25+
26+ const checker = program . getTypeChecker ( ) ;
27+ const symbol = checker . getSymbolAtLocation ( node ) ;
28+
29+ // decl should be `new Promise((<decl>) => {})`
30+ const decl = symbol ?. valueDeclaration ;
31+ if ( ! decl || ! isParameter ( decl ) || ! isNewExpression ( decl . parent . parent ) ) return ;
32+
33+ // no need to make this change if we have already seen this parameter.
34+ if ( seen ?. has ( decl ) ) return ;
35+ seen ?. add ( decl ) ;
36+
37+ const typeArguments = getEffectiveTypeArguments ( decl . parent . parent ) ;
38+ if ( some ( typeArguments ) ) {
39+ // append ` | void` to type argument
40+ const typeArgument = typeArguments [ 0 ] ;
41+ const needsParens = ! isUnionTypeNode ( typeArgument ) && ! isParenthesizedTypeNode ( typeArgument ) &&
42+ isParenthesizedTypeNode ( factory . createUnionTypeNode ( [ typeArgument , factory . createKeywordTypeNode ( SyntaxKind . VoidKeyword ) ] ) . types [ 0 ] ) ;
43+ if ( needsParens ) {
44+ changes . insertText ( sourceFile , typeArgument . pos , "(" ) ;
45+ }
46+ changes . insertText ( sourceFile , typeArgument . end , needsParens ? ") | void" : " | void" ) ;
47+ }
48+ else {
49+ // make sure the Promise is type is untyped (i.e., `unknown`)
50+ const signature = checker . getResolvedSignature ( node . parent ) ;
51+ const parameter = signature ?. parameters [ 0 ] ;
52+ const parameterType = parameter && checker . getTypeOfSymbolAtLocation ( parameter , decl . parent . parent ) ;
53+ if ( isInJSFile ( decl ) ) {
54+ if ( ! parameterType || parameterType . flags & TypeFlags . AnyOrUnknown ) {
55+ // give the expression a type
56+ changes . insertText ( sourceFile , decl . parent . parent . end , `)` ) ;
57+ changes . insertText ( sourceFile , skipTrivia ( sourceFile . text , decl . parent . parent . pos ) , `/** @type {Promise<void>} */(` ) ;
58+ }
59+ }
60+ else {
61+ if ( ! parameterType || parameterType . flags & TypeFlags . Unknown ) {
62+ // add `void` type argument
63+ changes . insertText ( sourceFile , decl . parent . parent . expression . end , "<void>" ) ;
64+ }
65+ }
66+ }
67+ }
68+
69+ function getEffectiveTypeArguments ( node : NewExpression ) {
70+ if ( isInJSFile ( node ) ) {
71+ if ( isParenthesizedExpression ( node . parent ) ) {
72+ const jsDocType = getJSDocTypeTag ( node . parent ) ?. typeExpression . type ;
73+ if ( jsDocType && isTypeReferenceNode ( jsDocType ) && isIdentifier ( jsDocType . typeName ) && idText ( jsDocType . typeName ) === "Promise" ) {
74+ return jsDocType . typeArguments ;
75+ }
76+ }
77+ }
78+ else {
79+ return node . typeArguments ;
80+ }
81+ }
82+ }
0 commit comments