1/* @internal */ 2namespace ts.codefix { 3 const deleteUnmatchedParameter = "deleteUnmatchedParameter"; 4 const renameUnmatchedParameter = "renameUnmatchedParameter"; 5 6 const errorCodes = [ 7 Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name.code, 8 ]; 9 10 registerCodeFix({ 11 fixIds: [deleteUnmatchedParameter, renameUnmatchedParameter], 12 errorCodes, 13 getCodeActions: function getCodeActionsToFixUnmatchedParameter(context) { 14 const { sourceFile, span } = context; 15 const actions: CodeFixAction[] = []; 16 const info = getInfo(sourceFile, span.start); 17 if (info) { 18 append(actions, getDeleteAction(context, info)); 19 append(actions, getRenameAction(context, info)); 20 return actions; 21 } 22 return undefined; 23 }, 24 getAllCodeActions: function getAllCodeActionsToFixUnmatchedParameter(context) { 25 const tagsToSignature = new Map<SignatureDeclaration, JSDocTag[]>(); 26 return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { 27 eachDiagnostic(context, errorCodes, ({ file, start }) => { 28 const info = getInfo(file, start); 29 if (info) { 30 tagsToSignature.set(info.signature, append(tagsToSignature.get(info.signature), info.jsDocParameterTag)); 31 } 32 }); 33 34 tagsToSignature.forEach((tags, signature) => { 35 if (context.fixId === deleteUnmatchedParameter) { 36 const tagsSet = new Set(tags); 37 changes.filterJSDocTags(signature.getSourceFile(), signature, t => !tagsSet.has(t)); 38 } 39 }); 40 })); 41 } 42 }); 43 44 function getDeleteAction(context: CodeFixContext, { name, signature, jsDocParameterTag }: Info) { 45 const changes = textChanges.ChangeTracker.with(context, changeTracker => 46 changeTracker.filterJSDocTags(context.sourceFile, signature, t => t !== jsDocParameterTag)); 47 return createCodeFixAction( 48 deleteUnmatchedParameter, 49 changes, 50 [Diagnostics.Delete_unused_param_tag_0, name.getText(context.sourceFile)], 51 deleteUnmatchedParameter, 52 Diagnostics.Delete_all_unused_param_tags 53 ); 54 } 55 56 function getRenameAction(context: CodeFixContext, { name, signature, jsDocParameterTag }: Info) { 57 if (!length(signature.parameters)) return undefined; 58 59 const sourceFile = context.sourceFile; 60 const tags = getJSDocTags(signature); 61 const names = new Set<__String>(); 62 for (const tag of tags) { 63 if (isJSDocParameterTag(tag) && isIdentifier(tag.name)) { 64 names.add(tag.name.escapedText); 65 } 66 } 67 // @todo - match to all available names instead to the first parameter name 68 // @see /codeFixRenameUnmatchedParameter3.ts 69 const parameterName = firstDefined(signature.parameters, p => 70 isIdentifier(p.name) && !names.has(p.name.escapedText) ? p.name.getText(sourceFile) : undefined); 71 if (parameterName === undefined) return undefined; 72 73 const newJSDocParameterTag = factory.updateJSDocParameterTag( 74 jsDocParameterTag, 75 jsDocParameterTag.tagName, 76 factory.createIdentifier(parameterName), 77 jsDocParameterTag.isBracketed, 78 jsDocParameterTag.typeExpression, 79 jsDocParameterTag.isNameFirst, 80 jsDocParameterTag.comment 81 ); 82 const changes = textChanges.ChangeTracker.with(context, changeTracker => 83 changeTracker.replaceJSDocComment(sourceFile, signature, map(tags, t => t === jsDocParameterTag ? newJSDocParameterTag : t))); 84 return createCodeFixActionWithoutFixAll(renameUnmatchedParameter, changes, [Diagnostics.Rename_param_tag_name_0_to_1, name.getText(sourceFile), parameterName]); 85 } 86 87 interface Info { 88 readonly signature: SignatureDeclaration; 89 readonly jsDocParameterTag: JSDocParameterTag; 90 readonly name: Identifier; 91 } 92 93 function getInfo(sourceFile: SourceFile, pos: number): Info | undefined { 94 const token = getTokenAtPosition(sourceFile, pos); 95 if (token.parent && isJSDocParameterTag(token.parent) && isIdentifier(token.parent.name)) { 96 const jsDocParameterTag = token.parent; 97 const signature = getHostSignatureFromJSDoc(jsDocParameterTag); 98 if (signature) { 99 return { signature, name: token.parent.name, jsDocParameterTag }; 100 } 101 } 102 return undefined; 103 } 104} 105