From 108fb7cdc0c73e9dd46896ece54ad165e9879ac3 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 5 Feb 2022 20:51:54 +0200 Subject: [PATCH] feat(11378): check param names in JSDoc --- src/compiler/checker.ts | 73 ++++++------ src/compiler/diagnosticMessages.json | 12 ++ .../codefixes/fixUnmatchedParameter.ts | 104 ++++++++++++++++++ src/services/textChanges.ts | 27 ++--- src/services/tsconfig.json | 1 + ...iableDeclaredFunctionExpression.errors.txt | 23 ++++ .../codeFixDeleteUnmatchedParameter1.ts | 23 ++++ .../codeFixDeleteUnmatchedParameter2.ts | 26 +++++ .../codeFixDeleteUnmatchedParameter3.ts | 30 +++++ .../codeFixDeleteUnmatchedParameter4.ts | 19 ++++ .../codeFixDeleteUnmatchedParameterJS1.ts | 27 +++++ .../codeFixDeleteUnmatchedParameterJS2.ts | 29 +++++ .../codeFixDeleteUnmatchedParameterJS3.ts | 33 ++++++ .../codeFixDeleteUnmatchedParameterJS4.ts | 22 ++++ .../codeFixDeleteUnmatchedParameter_all.ts | 51 +++++++++ .../codeFixDeleteUnmatchedParameter_allJS.ts | 53 +++++++++ .../codeFixRenameUnmatchedParameter1.ts | 30 +++++ .../codeFixRenameUnmatchedParameter2.ts | 34 ++++++ .../codeFixRenameUnmatchedParameter3.ts | 64 +++++++++++ .../codeFixRenameUnmatchedParameterJS1.ts | 34 ++++++ .../codeFixRenameUnmatchedParameterJS2.ts | 38 +++++++ .../codeFixRenameUnmatchedParameterJS3.ts | 72 ++++++++++++ 22 files changed, 779 insertions(+), 46 deletions(-) create mode 100644 src/services/codefixes/fixUnmatchedParameter.ts create mode 100644 tests/baselines/reference/checkJsdocParamOnVariableDeclaredFunctionExpression.errors.txt create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameter1.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameter2.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameter3.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameter4.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS1.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS2.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS3.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameterJS4.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameter_all.ts create mode 100644 tests/cases/fourslash/codeFixDeleteUnmatchedParameter_allJS.ts create mode 100644 tests/cases/fourslash/codeFixRenameUnmatchedParameter1.ts create mode 100644 tests/cases/fourslash/codeFixRenameUnmatchedParameter2.ts create mode 100644 tests/cases/fourslash/codeFixRenameUnmatchedParameter3.ts create mode 100644 tests/cases/fourslash/codeFixRenameUnmatchedParameterJS1.ts create mode 100644 tests/cases/fourslash/codeFixRenameUnmatchedParameterJS2.ts create mode 100644 tests/cases/fourslash/codeFixRenameUnmatchedParameterJS3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 840aa5e827b61..b2e6b85c6280f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -34395,6 +34395,7 @@ namespace ts { } checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkUnmatchedJSDocParameters(node); forEach(node.parameters, checkParameter); @@ -36158,40 +36159,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); } @@ -38486,6 +38454,45 @@ namespace ts { } } + function checkUnmatchedJSDocParameters(node: SignatureDeclaration) { + const jsdocParameters = filter(getJSDocTags(node), isJSDocParameterTag); + if (!length(jsdocParameters)) 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 34685f0ef7b28..7ab376dec2be8 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7127,6 +7127,18 @@ "category": "Message", "code": 95170 }, + "Delete unused '@param' tag '{0}'": { + "category": "Message", + "code": 95171 + }, + "Delete all unused '@param' tags": { + "category": "Message", + "code": 95172 + }, + "Rename '@param' tag name '{0}' to '{1}'": { + "category": "Message", + "code": 95173 + }, "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..b0dc5db0a85ae --- /dev/null +++ b/src/services/codefixes/fixUnmatchedParameter.ts @@ -0,0 +1,104 @@ +/* @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); + } + } + // @todo - match to all available names instead to the first parameter name + // @see /codeFixRenameUnmatchedParameter3.ts + 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/codeFixRenameUnmatchedParameter3.ts b/tests/cases/fourslash/codeFixRenameUnmatchedParameter3.ts new file mode 100644 index 0000000000000..daf725ee5fab4 --- /dev/null +++ b/tests/cases/fourslash/codeFixRenameUnmatchedParameter3.ts @@ -0,0 +1,64 @@ +/// + +// @filename: a.ts +/////** +//// * @param {number} notDefined1 +//// * @param {number} notDefined2 +//// * @param {number} a +//// * @param {number} b +//// */ +////function foo(a: number, b: string, typo1: string, typo2: string) { +//// a; +//// b; +//// typo1; +//// typo2; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'notDefined1'" }, + { description: "Rename '@param' tag name 'notDefined1' to 'typo1'" }, + { description: "Delete unused '@param' tag 'notDefined2'" }, + { description: "Rename '@param' tag name 'notDefined2' to 'typo1'" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Rename_param_tag_name_0_to_1.message, "notDefined1", "typo1"], + index: 1, + newFileContent: +`/** + * @param {number} typo1 + * @param {number} notDefined2 + * @param {number} a + * @param {number} b + */ +function foo(a: number, b: string, typo1: string, typo2: string) { + a; + b; + typo1; + typo2; +}`, + applyChanges: true +}); + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'notDefined2'" }, + { description: "Rename '@param' tag name 'notDefined2' to 'typo2'" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Rename_param_tag_name_0_to_1.message, "notDefined2", "typo2"], + index: 1, + newFileContent: +`/** + * @param {number} typo1 + * @param {number} typo2 + * @param {number} a + * @param {number} b + */ +function foo(a: number, b: string, typo1: string, typo2: string) { + a; + b; + typo1; + typo2; +}`, +}); 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; +}` +}); diff --git a/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS3.ts b/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS3.ts new file mode 100644 index 0000000000000..13c11a224c9b9 --- /dev/null +++ b/tests/cases/fourslash/codeFixRenameUnmatchedParameterJS3.ts @@ -0,0 +1,72 @@ +/// + +// @allowJs: true +// @checkJs: true +// @filename: /a.js +/////** +//// * @param {number} notDefined1 +//// * @param {number} notDefined2 +//// * @param {number} a +//// * @param {number} b +//// */ +////function foo(a, b, typo1, typo2) { +//// a; +//// b; +//// typo1; +//// typo2; +////} + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'notDefined1'" }, + { description: "Rename '@param' tag name 'notDefined1' to 'typo1'" }, + { description: "Disable checking for this file" }, + { description: "Delete unused '@param' tag 'notDefined2'" }, + { description: "Rename '@param' tag name 'notDefined2' to 'typo1'" }, + { description: "Disable checking for this file" }, + { description: "Infer parameter types from usage" }, + { description: "Infer parameter types from usage" }, +]); + +verify.codeFix({ + description: [ts.Diagnostics.Rename_param_tag_name_0_to_1.message, "notDefined1", "typo1"], + index: 1, + newFileContent: +`/** + * @param {number} typo1 + * @param {number} notDefined2 + * @param {number} a + * @param {number} b + */ +function foo(a, b, typo1, typo2) { + a; + b; + typo1; + typo2; +}`, + applyChanges: true +}); + +verify.codeFixAvailable([ + { description: "Delete unused '@param' tag 'notDefined2'" }, + { description: "Rename '@param' tag name 'notDefined2' to 'typo2'" }, + { 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, "notDefined2", "typo2"], + index: 1, + newFileContent: +`/** + * @param {number} typo1 + * @param {number} typo2 + * @param {number} a + * @param {number} b + */ +function foo(a, b, typo1, typo2) { + a; + b; + typo1; + typo2; +}`, +}); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy