1/* @internal */ 2namespace ts.codefix { 3 const fixIdPlain = "fixJSDocTypes_plain"; 4 const fixIdNullable = "fixJSDocTypes_nullable"; 5 const errorCodes = [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code]; 6 registerCodeFix({ 7 errorCodes, 8 getCodeActions(context) { 9 const { sourceFile } = context; 10 const checker = context.program.getTypeChecker(); 11 const info = getInfo(sourceFile, context.span.start, checker); 12 if (!info) return undefined; 13 const { typeNode, type } = info; 14 const original = typeNode.getText(sourceFile); 15 const actions = [fix(type, fixIdPlain, Diagnostics.Change_all_jsdoc_style_types_to_TypeScript)]; 16 if (typeNode.kind === SyntaxKind.JSDocNullableType) { 17 // for nullable types, suggest the flow-compatible `T | null | undefined` 18 // in addition to the jsdoc/closure-compatible `T | null` 19 actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), fixIdNullable, Diagnostics.Change_all_jsdoc_style_types_to_TypeScript_and_add_undefined_to_nullable_types)); 20 } 21 return actions; 22 23 function fix(type: Type, fixId: string, fixAllDescription: DiagnosticMessage): CodeFixAction { 24 const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, typeNode, type, checker)); 25 return createCodeFixAction("jdocTypes", changes, [Diagnostics.Change_0_to_1, original, checker.typeToString(type)], fixId, fixAllDescription); 26 } 27 }, 28 fixIds: [fixIdPlain, fixIdNullable], 29 getAllCodeActions(context) { 30 const { fixId, program, sourceFile } = context; 31 const checker = program.getTypeChecker(); 32 return codeFixAll(context, errorCodes, (changes, err) => { 33 const info = getInfo(err.file, err.start, checker); 34 if (!info) return; 35 const { typeNode, type } = info; 36 const fixedType = typeNode.kind === SyntaxKind.JSDocNullableType && fixId === fixIdNullable ? checker.getNullableType(type, TypeFlags.Undefined) : type; 37 doChange(changes, sourceFile, typeNode, fixedType, checker); 38 }); 39 } 40 }); 41 42 function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldTypeNode: TypeNode, newType: Type, checker: TypeChecker): void { 43 changes.replaceNode(sourceFile, oldTypeNode, checker.typeToTypeNode(newType, /*enclosingDeclaration*/ oldTypeNode, /*flags*/ undefined)!); // TODO: GH#18217 44 } 45 46 function getInfo(sourceFile: SourceFile, pos: number, checker: TypeChecker): { readonly typeNode: TypeNode, readonly type: Type } | undefined { 47 const decl = findAncestor(getTokenAtPosition(sourceFile, pos), isTypeContainer); 48 const typeNode = decl && decl.type; 49 return typeNode && { typeNode, type: checker.getTypeFromTypeNode(typeNode) }; 50 } 51 52 // TODO: GH#19856 Node & { type: TypeNode } 53 type TypeContainer = 54 | AsExpression | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration 55 | GetAccessorDeclaration | IndexSignatureDeclaration | MappedTypeNode | MethodDeclaration 56 | MethodSignature | ParameterDeclaration | PropertyDeclaration | PropertySignature | SetAccessorDeclaration 57 | TypeAliasDeclaration | TypeAssertion | VariableDeclaration; 58 function isTypeContainer(node: Node): node is TypeContainer { 59 // NOTE: Some locations are not handled yet: 60 // MappedTypeNode.typeParameters and SignatureDeclaration.typeParameters, as well as CallExpression.typeArguments 61 switch (node.kind) { 62 case SyntaxKind.AsExpression: 63 case SyntaxKind.CallSignature: 64 case SyntaxKind.ConstructSignature: 65 case SyntaxKind.FunctionDeclaration: 66 case SyntaxKind.GetAccessor: 67 case SyntaxKind.IndexSignature: 68 case SyntaxKind.MappedType: 69 case SyntaxKind.MethodDeclaration: 70 case SyntaxKind.MethodSignature: 71 case SyntaxKind.Parameter: 72 case SyntaxKind.PropertyDeclaration: 73 case SyntaxKind.PropertySignature: 74 case SyntaxKind.SetAccessor: 75 case SyntaxKind.TypeAliasDeclaration: 76 case SyntaxKind.TypeAssertionExpression: 77 case SyntaxKind.VariableDeclaration: 78 return true; 79 default: 80 return false; 81 } 82 } 83} 84