diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 11ed55a2c79d2..df92e23634c5a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -34413,6 +34413,7 @@ namespace ts { } checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkUnmatchedJSDocParameters(node); forEach(node.parameters, checkParameter); @@ -36178,40 +36179,7 @@ namespace ts { function checkJSDocParameterTag(node: JSDocParameterTag) { checkSourceElement(node.typeExpression); - if (!getParameterSymbolFromJSDoc(node)) { - const decl = getHostSignatureFromJSDoc(node); - // don't issue an error for invalid hosts -- just functions -- - // and give a better error message when the host function mentions `arguments` - // but the tag doesn't have an array type - if (decl) { - const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node); - if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) { - return; - } - if (!containsArgumentsReference(decl)) { - if (isQualifiedName(node.name)) { - error(node.name, - Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, - entityNameToString(node.name), - entityNameToString(node.name.left)); - } - else { - error(node.name, - Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, - idText(node.name)); - } - } - else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && - node.typeExpression && node.typeExpression.type && - !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { - error(node.name, - Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, - idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); - } - } - } } - function checkJSDocPropertyTag(node: JSDocPropertyTag) { checkSourceElement(node.typeExpression); } @@ -38506,6 +38474,45 @@ namespace ts { } } + function checkUnmatchedJSDocParameters(node: SignatureDeclaration) { + const jsdocParameters = filter(getJSDocTags(node), isJSDocParameterTag); + if (length(jsdocParameters) === 0) return; + + const isJs = isInJSFile(node); + const parameters = new Set<__String>(); + const excludedParameters = new Set(); + forEach(node.parameters, ({ name }, index) => { + if (isIdentifier(name)) { + parameters.add(name.escapedText); + } + if (isBindingPattern(name)) { + excludedParameters.add(index); + } + }); + + const containsArguments = containsArgumentsReference(node); + if (containsArguments) { + const lastJSDocParam = lastOrUndefined(jsdocParameters); + if (lastJSDocParam && isIdentifier(lastJSDocParam.name) && lastJSDocParam.typeExpression && + lastJSDocParam.typeExpression.type && !parameters.has(lastJSDocParam.name.escapedText) && !isArrayType(getTypeFromTypeNode(lastJSDocParam.typeExpression.type))) { + errorOrSuggestion(isJs, lastJSDocParam.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, idText(lastJSDocParam.name)); + } + } + else { + forEach(jsdocParameters, ({ name }, index) => { + if (excludedParameters.has(index) || isIdentifier(name) && parameters.has(name.escapedText)) { + return; + } + if (isQualifiedName(name)) { + errorOrSuggestion(isJs, name, Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, entityNameToString(name), entityNameToString(name.left)); + } + else { + errorOrSuggestion(isJs, name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, idText(name)); + } + }); + } + } + /** * Check each type parameter and check that type parameters have no duplicate type parameter declarations */ diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 836a6da2b6f9b..9191568685f4a 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7135,6 +7135,18 @@ "category": "Message", "code": 95169 }, + "Delete unused '@param' tag '{0}'": { + "category": "Message", + "code": 95170 + }, + "Delete all unused '@param' tags": { + "category": "Message", + "code": 95171 + }, + "Rename '@param' tag name '{0}' to '{1}'": { + "category": "Message", + "code": 95172 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/services/codefixes/fixUnmatchedParameter.ts b/src/services/codefixes/fixUnmatchedParameter.ts new file mode 100644 index 0000000000000..97c6d080069ac --- /dev/null +++ b/src/services/codefixes/fixUnmatchedParameter.ts @@ -0,0 +1,102 @@ +/* @internal */ +namespace ts.codefix { + const deleteUnmatchedParameter = "deleteUnmatchedParameter"; + const renameUnmatchedParameter = "renameUnmatchedParameter"; + + const errorCodes = [ + Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name.code, + ]; + + registerCodeFix({ + fixIds: [deleteUnmatchedParameter, renameUnmatchedParameter], + errorCodes, + getCodeActions: function getCodeActionsToFixUnmatchedParameter(context) { + const { sourceFile, span } = context; + const actions: CodeFixAction[] = []; + const info = getInfo(sourceFile, span.start); + if (info) { + append(actions, getDeleteAction(context, info)); + append(actions, getRenameAction(context, info)); + return actions; + } + return undefined; + }, + getAllCodeActions: function getAllCodeActionsToFixUnmatchedParameter(context) { + const tagsToSignature = new Map(); + return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { + eachDiagnostic(context, errorCodes, ({ file, start }) => { + const info = getInfo(file, start); + if (info) { + tagsToSignature.set(info.signature, append(tagsToSignature.get(info.signature), info.jsDocParameterTag)); + } + }); + + tagsToSignature.forEach((tags, signature) => { + if (context.fixId === deleteUnmatchedParameter) { + const tagsSet = new Set(tags); + changes.filterJSDocTags(signature.getSourceFile(), signature, t => !tagsSet.has(t)); + } + }); + })); + } + }); + + function getDeleteAction(context: CodeFixContext, { name, signature, jsDocParameterTag }: Info) { + const changes = textChanges.ChangeTracker.with(context, changeTracker => + changeTracker.filterJSDocTags(context.sourceFile, signature, t => t !== jsDocParameterTag)); + return createCodeFixAction( + deleteUnmatchedParameter, + changes, + [Diagnostics.Delete_unused_param_tag_0, name.getText(context.sourceFile)], + deleteUnmatchedParameter, + Diagnostics.Delete_all_unused_param_tags + ); + } + + function getRenameAction(context: CodeFixContext, { name, signature, jsDocParameterTag }: Info) { + if (!length(signature.parameters)) return undefined; + + const sourceFile = context.sourceFile; + const tags = getJSDocTags(signature); + const names = new Set<__String>(); + for (const tag of tags) { + if (isJSDocParameterTag(tag) && isIdentifier(tag.name)) { + names.add(tag.name.escapedText); + } + } + const parameterName = firstDefined(signature.parameters, p => + isIdentifier(p.name) && !names.has(p.name.escapedText) ? p.name.getText(sourceFile) : undefined); + if (parameterName === undefined) return undefined; + + const newJSDocParameterTag = factory.updateJSDocParameterTag( + jsDocParameterTag, + jsDocParameterTag.tagName, + factory.createIdentifier(parameterName), + jsDocParameterTag.isBracketed, + jsDocParameterTag.typeExpression, + jsDocParameterTag.isNameFirst, + jsDocParameterTag.comment + ); + const changes = textChanges.ChangeTracker.with(context, changeTracker => + changeTracker.replaceJSDocComment(sourceFile, signature, map(tags, t => t === jsDocParameterTag ? newJSDocParameterTag : t))); + return createCodeFixActionWithoutFixAll(renameUnmatchedParameter, changes, [Diagnostics.Rename_param_tag_name_0_to_1, name.getText(sourceFile), parameterName]); + } + + interface Info { + readonly signature: SignatureDeclaration; + readonly jsDocParameterTag: JSDocParameterTag; + readonly name: Identifier; + } + + function getInfo(sourceFile: SourceFile, pos: number): Info | undefined { + const token = getTokenAtPosition(sourceFile, pos); + if (token.parent && isJSDocParameterTag(token.parent) && isIdentifier(token.parent.name)) { + const jsDocParameterTag = token.parent; + const signature = getHostSignatureFromJSDoc(jsDocParameterTag); + if (signature) { + return { signature, name: token.parent.name, jsDocParameterTag }; + } + } + return undefined; + } +} diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 83d5acb9d7291..f697571607541 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -495,29 +495,30 @@ namespace ts.textChanges { this.insertNodeAt(sourceFile, fnStart, tag, { preserveLeadingWhitespace: false, suffix: this.newLineCharacter + indent }); } + private createJSDocText(sourceFile: SourceFile, node: HasJSDoc) { + const comments = flatMap(node.jsDoc, jsDoc => + isString(jsDoc.comment) ? factory.createJSDocText(jsDoc.comment) : jsDoc.comment) as JSDocComment[]; + const jsDoc = singleOrUndefined(node.jsDoc); + return jsDoc && positionsAreOnSameLine(jsDoc.pos, jsDoc.end, sourceFile) && length(comments) === 0 ? undefined : + factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))); + } + + public replaceJSDocComment(sourceFile: SourceFile, node: HasJSDoc, tags: readonly JSDocTag[]) { + this.insertJsdocCommentBefore(sourceFile, updateJSDocHost(node), factory.createJSDocComment(this.createJSDocText(sourceFile, node), factory.createNodeArray(tags))); + } + public addJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void { - const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[]; const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); const unmergedNewTags = newTags.filter(newTag => !oldTags.some((tag, i) => { const merged = tryMergeJsdocTags(tag, newTag); if (merged) oldTags[i] = merged; return !!merged; })); - const tags = [...oldTags, ...unmergedNewTags]; - const jsDoc = singleOrUndefined(parent.jsDoc); - const comment = jsDoc && positionsAreOnSameLine(jsDoc.pos, jsDoc.end, sourceFile) && !length(comments) ? undefined : - factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))); - const tag = factory.createJSDocComment(comment, factory.createNodeArray(tags)); - const host = updateJSDocHost(parent); - this.insertJsdocCommentBefore(sourceFile, host, tag); + this.replaceJSDocComment(sourceFile, parent, [...oldTags, ...unmergedNewTags]); } public filterJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, predicate: (tag: JSDocTag) => boolean): void { - const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[]; - const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); - const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...(filter(oldTags, predicate) || emptyArray)])); - const host = updateJSDocHost(parent); - this.insertJsdocCommentBefore(sourceFile, host, tag); + this.replaceJSDocComment(sourceFile, parent, filter(flatMapToMutable(parent.jsDoc, j => j.tags), predicate)); } public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string): void { diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index d2119947f8fa8..3cd2188314613 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -87,6 +87,7 @@ "codefixes/fixExtendsInterfaceBecomesImplements.ts", "codefixes/fixForgottenThisPropertyAccess.ts", "codefixes/fixInvalidJsxCharacters.ts", + "codefixes/fixUnmatchedParameter.ts", "codefixes/fixUnusedIdentifier.ts", "codefixes/fixUnreachableCode.ts", "codefixes/fixUnusedLabel.ts", diff --git a/tests/baselines/reference/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt b/tests/baselines/reference/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt new file mode 100644 index 0000000000000..f3ea79ea97cfa --- /dev/null +++ b/tests/baselines/reference/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt @@ -0,0 +1,23 @@ +tests/cases/conformance/jsdoc/0.js(14,20): error TS8024: JSDoc '@param' tag has name 's', but there is no parameter with that name. + + +==== tests/cases/conformance/jsdoc/0.js (1 errors) ==== + // @ts-check + /** + * @param {number=} n + * @param {string} [s] + */ + var x = function foo(n, s) {} + var y; + /** + * @param {boolean!} b + */ + y = function bar(b) {} + + /** + * @param {string} s + ~ +!!! error TS8024: JSDoc '@param' tag has name 's', but there is no parameter with that name. + */ + var one = function (s) { }, two = function (untyped) { }; + \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameter1.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter1.ts new file mode 100644 index 0000000000000..caad37daa44a7 --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter1.ts @@ -0,0 +1,23 @@ +/// + +// @filename: a.ts +/////** +//// * @param {number} a +//// * @param {number} b +//// */ +////function foo() {} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'a'" }, + { description: "Delete unused '@param' tag 'b'" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Delete_unused_param_tag_0.message, "a"], + index: 0, + newFileContent: +`/** + * @param {number} b + */ +function foo() {}`, +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameter2.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter2.ts new file mode 100644 index 0000000000000..5a9adbce8116f --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter2.ts @@ -0,0 +1,26 @@ +/// + +// @filename: a.ts +/////** +//// * @param {number} a +//// * @param {string} b +//// */ +////function foo(a: number) { +//// a; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'b'" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Delete_unused_param_tag_0.message, "b"], + index: 0, + newFileContent: +`/** + * @param {number} a + */ +function foo(a: number) { + a; +}` +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameter3.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter3.ts new file mode 100644 index 0000000000000..1f053813182fc --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter3.ts @@ -0,0 +1,30 @@ +/// + +// @filename: a.ts +/////** +//// * @param {number} a +//// * @param {string} b +//// * @param {number} c +//// */ +////function foo(a: number, c: number) { +//// a; +//// c; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'b'" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Delete_unused_param_tag_0.message, "b"], + index: 0, + newFileContent: +`/** + * @param {number} a + * @param {number} c + */ +function foo(a: number, c: number) { + a; + c; +}` +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameter4.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter4.ts new file mode 100644 index 0000000000000..c84f721ab7508 --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter4.ts @@ -0,0 +1,19 @@ +/// + +// @filename: a.ts +/////** +//// * @param {number} a +//// */ +////function foo() {} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'a'" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Delete_unused_param_tag_0.message, "a"], + index: 0, + newFileContent: +`/** */ +function foo() {}` +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS1.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS1.ts new file mode 100644 index 0000000000000..75fa6515cd117 --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS1.ts @@ -0,0 +1,27 @@ +/// + +// @allowJs: true +// @checkJs: true +// @filename: /a.js +/////** +//// * @param {number} a +//// * @param {number} b +//// */ +////function foo() {} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'a'" }, + { description: "Disable checking for this file" }, + { description: "Delete unused '@param' tag 'b'" }, + { description: "Disable checking for this file" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Delete_unused_param_tag_0.message, "a"], + index: 0, + newFileContent: +`/** + * @param {number} b + */ +function foo() {}`, +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS2.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS2.ts new file mode 100644 index 0000000000000..f8324b3116a2b --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS2.ts @@ -0,0 +1,29 @@ +/// + +// @allowJs: true +// @checkJs: true +// @filename: /a.js +/////** +//// * @param {number} a +//// * @param {string} b +//// */ +////function foo(a) { +//// a; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'b'" }, + { description: "Disable checking for this file" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Delete_unused_param_tag_0.message, "b"], + index: 0, + newFileContent: +`/** + * @param {number} a + */ +function foo(a) { + a; +}` +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS3.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS3.ts new file mode 100644 index 0000000000000..e02c1f51c0421 --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS3.ts @@ -0,0 +1,33 @@ +/// + +// @allowJs: true +// @checkJs: true +// @filename: /a.js +/////** +//// * @param {number} a +//// * @param {string} b +//// * @param {number} c +//// */ +////function foo(a, c) { +//// a; +//// c; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'b'" }, + { description: "Disable checking for this file" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Delete_unused_param_tag_0.message, "b"], + index: 0, + newFileContent: +`/** + * @param {number} a + * @param {number} c + */ +function foo(a, c) { + a; + c; +}` +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS4.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS4.ts new file mode 100644 index 0000000000000..272f49d1096a2 --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS4.ts @@ -0,0 +1,22 @@ +/// + +// @allowJs: true +// @checkJs: true +// @filename: /a.js +/////** +//// * @param {number} a +//// */ +////function foo() {} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'a'" }, + { description: "Disable checking for this file" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Delete_unused_param_tag_0.message, "a"], + index: 0, + newFileContent: +`/** */ +function foo() {}` +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameter_all.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter_all.ts new file mode 100644 index 0000000000000..ee62961df6e81 --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter_all.ts @@ -0,0 +1,51 @@ +/// + +// @filename: /a.ts +/////** +//// * @param {number} a +//// * @param {number} b +//// */ +////function f1() {} +//// +/////** +//// * @param {number} a +//// * @param {string} b +//// */ +////function f2(a: number) { +//// a; +////} +//// +/////** +//// * @param {number} a +//// * @param {string} b +//// * @param {number} c +//// */ +////function f3(a: number, c: number) { +//// a; +//// c; +////} + +goTo.file("/a.ts"); +verify.codeFixAll({ + fixId: "deleteUnmatchedParameter", + fixAllDescription: ts.Diagnostics.Delete_all_unused_param_tags.message, + newFileContent: +`/** */ +function f1() {} + +/** + * @param {number} a + */ +function f2(a: number) { + a; +} + +/** + * @param {number} a + * @param {number} c + */ +function f3(a: number, c: number) { + a; + c; +}`, +}); diff --git a/tests/cases/fourslash/codeFixDeleteUnmatchedParameter_allJS.ts b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter_allJS.ts new file mode 100644 index 0000000000000..f139514e72d74 --- /dev/null +++ b/tests/cases/fourslash/codeFixDeleteUnmatchedParameter_allJS.ts @@ -0,0 +1,53 @@ +/// + +// @allowJs: true +// @checkJs: true +// @filename: /a.js +/////** +//// * @param {number} a +//// * @param {number} b +//// */ +////function f1() {} +//// +/////** +//// * @param {number} a +//// * @param {string} b +//// */ +////function f2(a) { +//// a; +////} +//// +/////** +//// * @param {number} a +//// * @param {string} b +//// * @param {number} c +//// */ +////function f3(a, c) { +//// a; +//// c; +////} + +goTo.file("/a.js"); +verify.codeFixAll({ + fixId: "deleteUnmatchedParameter", + fixAllDescription: ts.Diagnostics.Delete_all_unused_param_tags.message, + newFileContent: +`/** */ +function f1() {} + +/** + * @param {number} a + */ +function f2(a) { + a; +} + +/** + * @param {number} a + * @param {number} c + */ +function f3(a, c) { + a; + c; +}`, +}); diff --git a/tests/cases/fourslash/codeFixRenameUnmatchedParameter1.ts b/tests/cases/fourslash/codeFixRenameUnmatchedParameter1.ts new file mode 100644 index 0000000000000..743c9da9073e0 --- /dev/null +++ b/tests/cases/fourslash/codeFixRenameUnmatchedParameter1.ts @@ -0,0 +1,30 @@ +/// + +// @filename: a.ts +/////** +//// * @param {number} a +//// * @param {number} c +//// */ +////function foo(a: number, b: string) { +//// a; +//// b; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'c'" }, + { description: "Rename '@param' tag name 'c' to 'b'" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Rename_param_tag_name_0_to_1.message, "c", "b"], + index: 1, + newFileContent: +`/** + * @param {number} a + * @param {number} b + */ +function foo(a: number, b: string) { + a; + b; +}` +}); diff --git a/tests/cases/fourslash/codeFixRenameUnmatchedParameter2.ts b/tests/cases/fourslash/codeFixRenameUnmatchedParameter2.ts new file mode 100644 index 0000000000000..cc002d07ed6f4 --- /dev/null +++ b/tests/cases/fourslash/codeFixRenameUnmatchedParameter2.ts @@ -0,0 +1,34 @@ +/// + +// @filename: a.ts +/////** +//// * @param {number} d +//// * @param {number} a +//// * @param {number} b +//// */ +////function foo(a: number, b: string, c: string) { +//// a; +//// b; +//// c; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'd'" }, + { description: "Rename '@param' tag name 'd' to 'c'" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Rename_param_tag_name_0_to_1.message, "d", "c"], + index: 1, + newFileContent: +`/** + * @param {number} c + * @param {number} a + * @param {number} b + */ +function foo(a: number, b: string, c: string) { + a; + b; + c; +}` +}); diff --git a/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS1.ts b/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS1.ts new file mode 100644 index 0000000000000..ccc7c97115026 --- /dev/null +++ b/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS1.ts @@ -0,0 +1,34 @@ +/// + +// @allowJs: true +// @checkJs: true +// @filename: /a.js +/////** +//// * @param {number} a +//// * @param {number} c +//// */ +////function foo(a, b) { +//// a; +//// b; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'c'" }, + { description: "Rename '@param' tag name 'c' to 'b'" }, + { description: "Disable checking for this file" }, + { description: "Infer parameter types from usage" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Rename_param_tag_name_0_to_1.message, "c", "b"], + index: 1, + newFileContent: +`/** + * @param {number} a + * @param {number} b + */ +function foo(a, b) { + a; + b; +}` +}); diff --git a/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS2.ts b/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS2.ts new file mode 100644 index 0000000000000..89acd2ff5904f --- /dev/null +++ b/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS2.ts @@ -0,0 +1,38 @@ +/// + +// @allowJs: true +// @checkJs: true +// @filename: /a.js +/////** +//// * @param {number} d +//// * @param {number} a +//// * @param {number} b +//// */ +////function foo(a, b, c) { +//// a; +//// b; +//// c; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'd'" }, + { description: "Rename '@param' tag name 'd' to 'c'" }, + { description: "Disable checking for this file" }, + { description: "Infer parameter types from usage" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Rename_param_tag_name_0_to_1.message, "d", "c"], + index: 1, + newFileContent: +`/** + * @param {number} c + * @param {number} a + * @param {number} b + */ +function foo(a, b, c) { + a; + b; + c; +}` +});