namespace ts { const visitedNestedConvertibleFunctions = new Map < string , true > ( ) ; export function computeSuggestionDiagnostics ( sourceFile , program , cancellationToken ) { program . getSemanticDiagnostics ( sourceFile , cancellationToken ) ; const diags = [ ] ; const checker = program . getTypeChecker ( ) ; const isCommonJSFile = sourceFile . impliedNodeFormat === ModuleKind . CommonJS || fileExtensionIsOneOf ( sourceFile . fileName , [ Extension . Cts , Extension . Cjs ] ) ; if ( ! isCommonJSFile && sourceFile . commonJsModuleIndicator && ( programContainsEsModules ( program ) || compilerOptionsIndicateEsModules ( program . getCompilerOptions ( ) ) ) && containsTopLevelCommonjs ( sourceFile ) ) { diags . push ( createDiagnosticForNode ( getErrorNodeFromCommonJsIndicator ( sourceFile . commonJsModuleIndicator ) , Diagnostics . File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module ) ) ; } const isJsFile = isSourceFileJS ( sourceFile ) ; visitedNestedConvertibleFunctions . clear ( ) ; check ( sourceFile ) ; if ( getAllowSyntheticDefaultImports ( program . getCompilerOptions ( ) ) ) { for ( const moduleSpecifier of sourceFile . imports ) { const importNode = importFromModuleSpecifier ( moduleSpecifier ) ; const name = importNameForConvertToDefaultImport ( importNode ) ; if ( ! name ) continue ; const module = getResolvedModule ( sourceFile , moduleSpecifier . text , getModeForUsageLocation ( sourceFile , moduleSpecifier ) ) ; const resolvedFile = module && program . getSourceFile ( module . resolvedFileName ) ; if ( resolvedFile && resolvedFile . externalModuleIndicator && isExportAssignment ( resolvedFile . externalModuleIndicator ) && resolvedFile . externalModuleIndicator . isExportEquals ) { diags . push ( createDiagnosticForNode ( name , Diagnostics . Import_may_be_converted_to_a_default_import ) ) ; } } } addRange ( diags , sourceFile . bindSuggestionDiagnostics ) ; addRange ( diags , program . getSuggestionDiagnostics ( sourceFile , cancellationToken ) ) ; return diags . sort ( ( d1 , d2 ) => d1 . start - d2 . start ) ; function check ( node ) { if ( isJsFile ) { if ( canBeConvertedToClass ( node , checker ) ) { diags . push ( createDiagnosticForNode ( isVariableDeclaration ( node . parent ) ? node . parent . name : node , Diagnostics . This_constructor_function_may_be_converted_to_a_class_declaration ) ) ; } } else { if ( isVariableStatement ( node ) && node . parent === sourceFile && node . declarationList . flags & NodeFlags . Const && node . declarationList . declarations . length === 1 ) { const init = node . declarationList . declarations [ 0 ] . initializer ; if ( init && isRequireCall ( init , true ) ) { diags . push ( createDiagnosticForNode ( init , Diagnostics . require_call_may_be_converted_to_an_import ) ) ; } } if ( codefix . parameterShouldGetTypeFromJSDoc ( node ) ) { diags . push ( createDiagnosticForNode ( node . name || node , Diagnostics . JSDoc_types_may_be_moved_to_TypeScript_types ) ) ; } } if ( canBeConvertedToAsync ( node ) ) { addConvertToAsyncFunctionDiagnostics ( node , checker , diags ) ; } node . forEachChild ( check ) ; } } function containsTopLevelCommonjs ( sourceFile ) { return sourceFile . statements . some ( statement => { switch ( statement . kind ) { case SyntaxKind . VariableStatement : return ( statement as VariableStatement ) . declarationList . declarations . some ( decl => ! ! decl . initializer && isRequireCall ( propertyAccessLeftHandSide ( decl . initializer ) , true ) ) ; case SyntaxKind . ExpressionStatement : { const { expression } = statement as ExpressionStatement ; if ( ! isBinaryExpression ( expression ) ) return isRequireCall ( expression , true ) ; const kind = getAssignmentDeclarationKind ( expression ) ; return kind === AssignmentDeclarationKind . ExportsProperty || kind === AssignmentDeclarationKind . ModuleExports ; } default : return false ; } } ) ; } function propertyAccessLeftHandSide ( node ) { return isPropertyAccessExpression ( node ) ? propertyAccessLeftHandSide ( node . expression ) : node ; } function importNameForConvertToDefaultImport ( node ) { switch ( node . kind ) { case SyntaxKind . ImportDeclaration : const { importClause , moduleSpecifier } = node ; return importClause && ! importClause . name && importClause . namedBindings && importClause . namedBindings . kind === SyntaxKind . NamespaceImport && isStringLiteral ( moduleSpecifier ) ? importClause . namedBindings . name : undefined ; case SyntaxKind . ImportEqualsDeclaration : return node . name ; default : return undefined ; } } function addConvertToAsyncFunctionDiagnostics ( node , checker , diags ) { if ( isConvertibleFunction ( node , checker ) && ! visitedNestedConvertibleFunctions . has ( getKeyFromNode ( node ) ) ) { diags . push ( createDiagnosticForNode ( ! node . name && isVariableDeclaration ( node . parent ) && isIdentifier ( node . parent . name ) ? node . parent . name : node , Diagnostics . This_may_be_converted_to_an_async_function ) ) ; } } function isConvertibleFunction ( node , checker ) { return ! isAsyncFunction ( node ) && node . body && isBlock ( node . body ) && hasReturnStatementWithPromiseHandler ( node . body , checker ) && returnsPromise ( node , checker ) ; } export function returnsPromise ( node , checker ) { const signature = checker . getSignatureFromDeclaration ( node ) ; const returnType = signature ? checker . getReturnTypeOfSignature ( signature ) : undefined ; return ! ! returnType && ! ! checker . getPromisedTypeOfPromise ( returnType ) ; } function getErrorNodeFromCommonJsIndicator ( commonJsModuleIndicator ) { return isBinaryExpression ( commonJsModuleIndicator ) ? commonJsModuleIndicator . left : commonJsModuleIndicator ; } function hasReturnStatementWithPromiseHandler ( body , checker ) { return ! ! forEachReturnStatement ( body , statement => isReturnStatementWithFixablePromiseHandler ( statement , checker ) ) ; } export function isReturnStatementWithFixablePromiseHandler ( node , checker ) { return isReturnStatement ( node ) && ! ! node . expression && isFixablePromiseHandler ( node . expression , checker ) ; } export function isFixablePromiseHandler ( node , checker ) { if ( ! isPromiseHandler ( node ) || ! hasSupportedNumberOfArguments ( node ) || ! node . arguments . every ( arg => isFixablePromiseArgument ( arg , checker ) ) ) { return false ; } let currentNode = node . expression . expression ; while ( isPromiseHandler ( currentNode ) || isPropertyAccessExpression ( currentNode ) ) { if ( isCallExpression ( currentNode ) ) { if ( ! hasSupportedNumberOfArguments ( currentNode ) || ! currentNode . arguments . every ( arg => isFixablePromiseArgument ( arg , checker ) ) ) { return false ; } currentNode = currentNode . expression . expression ; } else { currentNode = currentNode . expression ; } } return true ; } function isPromiseHandler ( node ) { return isCallExpression ( node ) && ( hasPropertyAccessExpressionWithName ( node , "then" ) || hasPropertyAccessExpressionWithName ( node , "catch" ) || hasPropertyAccessExpressionWithName ( node , "finally" ) ) ; } function hasSupportedNumberOfArguments ( node ) { const name = node . expression . name . text ; const maxArguments = name === "then" ? 2 : name === "catch" ? 1 : name === "finally" ? 1 : 0 ; if ( node . arguments . length > maxArguments ) return false ; if ( node . arguments . length < maxArguments ) return true ; return maxArguments === 1 || some ( node . arguments , arg => { return arg . kind === SyntaxKind . NullKeyword || isIdentifier ( arg ) && arg . text === "undefined" ; } ) ; } function isFixablePromiseArgument ( arg , checker ) { switch ( arg . kind ) { case SyntaxKind . FunctionDeclaration : case SyntaxKind . FunctionExpression : const functionFlags = getFunctionFlags ( arg as FunctionDeclaration | FunctionExpression ) ; if ( functionFlags & FunctionFlags . Generator ) { return false ; } case SyntaxKind . ArrowFunction : visitedNestedConvertibleFunctions . set ( getKeyFromNode ( arg as FunctionLikeDeclaration ) , true ) ; case SyntaxKind . NullKeyword : return true ; case SyntaxKind . Identifier : case SyntaxKind . PropertyAccessExpression : { const symbol = checker . getSymbolAtLocation ( arg ) ; if ( ! symbol ) { return false ; } return checker . isUndefinedSymbol ( symbol ) || some ( skipAlias ( symbol , checker ) . declarations , d => isFunctionLike ( d ) || hasInitializer ( d ) && ! ! d . initializer && isFunctionLike ( d . initializer ) ) ; } default : return false ; } } function getKeyFromNode ( exp ) { return `${ exp . pos . toString ( ) }:${ exp . end . toString ( ) }` ; } function canBeConvertedToClass ( node , checker ) { if ( node . kind === SyntaxKind . FunctionExpression ) { if ( isVariableDeclaration ( node . parent ) && node . symbol . members ?. size ) { return true ; } const symbol = checker . getSymbolOfExpando ( node , false ) ; return ! ! ( symbol && ( symbol . exports ?. size || symbol . members ?. size ) ) ; } if ( node . kind === SyntaxKind . FunctionDeclaration ) { return ! ! node . symbol . members ?. size ; } return false ; } export function canBeConvertedToAsync ( node ) { switch ( node . kind ) { case SyntaxKind . FunctionDeclaration : case SyntaxKind . MethodDeclaration : case SyntaxKind . FunctionExpression : case SyntaxKind . ArrowFunction : return true ; default : return false ; } } }