diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts
index efad417248a..ac05ebda1d9 100644
--- a/src/services/codefixes/inferFromUsage.ts
+++ b/src/services/codefixes/inferFromUsage.ts
@@ -337,17 +337,46 @@ namespace ts.codefix {
if (!signature) {
return;
}
- const paramTags = mapDefined(parameterInferences, inference => {
+
+ const inferences = mapDefined(parameterInferences, inference => {
const param = inference.declaration;
// only infer parameters that have (1) no type and (2) an accessible inferred type
- if (param.initializer || getJSDocType(param) || !isIdentifier(param.name)) return;
-
+ if (param.initializer || getJSDocType(param) || !isIdentifier(param.name)) {
+ return;
+ }
const typeNode = inference.type && getTypeNodeIfAccessible(inference.type, param, program, host);
- const name = factory.cloneNode(param.name);
- setEmitFlags(name, EmitFlags.NoComments | EmitFlags.NoNestedComments);
- return typeNode && factory.createJSDocParameterTag(/*tagName*/ undefined, name, /*isBracketed*/ !!inference.isOptional, factory.createJSDocTypeExpression(typeNode), /* isNameFirst */ false, "");
+ if (typeNode) {
+ const name = factory.cloneNode(param.name);
+ setEmitFlags(name, EmitFlags.NoComments | EmitFlags.NoNestedComments);
+ return { name: factory.cloneNode(param.name), param, isOptional: !!inference.isOptional, typeNode };
+ }
});
- addJSDocTags(changes, sourceFile, signature, paramTags);
+
+ if (!inferences.length) {
+ return;
+ }
+
+ if (isArrowFunction(signature) || isFunctionExpression(signature)) {
+ const needParens = isArrowFunction(signature) && !findChildOfKind(signature, SyntaxKind.OpenParenToken, sourceFile);
+ if (needParens) {
+ changes.insertNodeBefore(sourceFile, first(signature.parameters), factory.createToken(SyntaxKind.OpenParenToken));
+ }
+
+ forEach(inferences, ({ typeNode, param }) => {
+ const typeTag = factory.createJSDocTypeTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode));
+ const jsDoc = factory.createJSDocComment(/*comment*/ undefined, [typeTag]);
+ changes.insertNodeAt(sourceFile, param.getStart(sourceFile), jsDoc, { suffix: " " });
+ });
+
+ if (needParens) {
+ changes.insertNodeAfter(sourceFile, last(signature.parameters), factory.createToken(SyntaxKind.CloseParenToken));
+ }
+ }
+ else {
+ const paramTags = map(inferences, ({ name, typeNode, isOptional }) =>
+ factory.createJSDocParameterTag(/*tagName*/ undefined, name, /*isBracketed*/ !!isOptional, factory.createJSDocTypeExpression(typeNode), /* isNameFirst */ false, ""));
+ addJSDocTags(changes, sourceFile, signature, paramTags);
+ }
}
export function addJSDocTags(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void {
diff --git a/tests/cases/fourslash/codeFixInferFromUsageArrowJS.ts b/tests/cases/fourslash/codeFixInferFromUsageArrowJS.ts
index 0a800fd7d63..d7c44483918 100644
--- a/tests/cases/fourslash/codeFixInferFromUsageArrowJS.ts
+++ b/tests/cases/fourslash/codeFixInferFromUsageArrowJS.ts
@@ -14,14 +14,8 @@ verify.codeFixAll({
fixId: "inferFromUsage",
fixAllDescription: "Infer all types from usage",
newFileContent:
-`/**
- * @param {{ y: number; }} x
- */
-const foo = x => x.y + 1;
+`const foo = (/** @type {{ y: number; }} */ x) => x.y + 1;
class C {
- /**
- * @param {{ y: number; }} x
- */
- m = x => x.y + 1;
+ m = (/** @type {{ y: number; }} */ x) => x.y + 1;
}`,
});
\ No newline at end of file
diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter1.ts b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter1.ts
new file mode 100644
index 00000000000..cf1d1a1b097
--- /dev/null
+++ b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter1.ts
@@ -0,0 +1,19 @@
+///
+
+// @allowJs: true
+// @checkJs: true
+// @noImplicitAny: false
+
+// @filename: /foo.js
+////function foo(names) {
+//// return names.filter((name, index) => name === "foo" && index === 1);
+////}
+
+verify.codeFix({
+ description: "Infer parameter types from usage",
+ index: 1,
+ newFileContent:
+`function foo(names) {
+ return names.filter((/** @type {string} */ name, /** @type {number} */ index) => name === "foo" && index === 1);
+}`
+});
diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter2.ts b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter2.ts
new file mode 100644
index 00000000000..3dd35b667cd
--- /dev/null
+++ b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter2.ts
@@ -0,0 +1,19 @@
+///
+
+// @allowJs: true
+// @checkJs: true
+// @noImplicitAny: false
+
+// @filename: /foo.js
+////function foo(names) {
+//// return names.filter(name => name === "foo");
+////}
+
+verify.codeFix({
+ description: "Infer parameter types from usage",
+ index: 1,
+ newFileContent:
+`function foo(names) {
+ return names.filter((/** @type {string} */ name) => name === "foo");
+}`
+});
diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter3.ts b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter3.ts
new file mode 100644
index 00000000000..e8be2d5aa27
--- /dev/null
+++ b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter3.ts
@@ -0,0 +1,23 @@
+///
+
+// @allowJs: true
+// @checkJs: true
+// @noImplicitAny: false
+
+// @filename: /foo.js
+////function foo(names) {
+//// return names.filter(function (name) {
+//// return name === "foo";
+//// });
+////}
+
+verify.codeFix({
+ description: "Infer parameter types from usage",
+ index: 1,
+ newFileContent:
+`function foo(names) {
+ return names.filter(function (/** @type {string} */ name) {
+ return name === "foo";
+ });
+}`
+});
diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter4.ts b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter4.ts
new file mode 100644
index 00000000000..f62e1ce348e
--- /dev/null
+++ b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter4.ts
@@ -0,0 +1,23 @@
+///
+
+// @allowJs: true
+// @checkJs: true
+// @noImplicitAny: false
+
+// @filename: /foo.js
+////function foo(names) {
+//// return names.filter(function (name, index) {
+//// return name === "foo" && index === 1;
+//// });
+////}
+
+verify.codeFix({
+ description: "Infer parameter types from usage",
+ index: 1,
+ newFileContent:
+`function foo(names) {
+ return names.filter(function (/** @type {string} */ name, /** @type {number} */ index) {
+ return name === "foo" && index === 1;
+ });
+}`
+});
diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter5.ts b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter5.ts
new file mode 100644
index 00000000000..2287f39269a
--- /dev/null
+++ b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter5.ts
@@ -0,0 +1,14 @@
+///
+
+// @allowJs: true
+// @checkJs: true
+// @noImplicitAny: false
+
+// @filename: /foo.js
+////const foo = [x => x + 1];
+
+verify.codeFix({
+ description: "Infer parameter types from usage",
+ index: 0,
+ newFileContent: `const foo = [(/** @type {number} */ x) => x + 1];`
+});
diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter6.ts b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter6.ts
new file mode 100644
index 00000000000..ef4babadf69
--- /dev/null
+++ b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter6.ts
@@ -0,0 +1,10 @@
+///
+
+// @allowJs: true
+// @checkJs: true
+// @noImplicitAny: false
+
+// @filename: /foo.js
+////const foo = [(/** @type {number} */ x) => x + 1];
+
+verify.not.codeFixAvailable();
diff --git a/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter7.ts b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter7.ts
new file mode 100644
index 00000000000..618b59f4d3e
--- /dev/null
+++ b/tests/cases/fourslash/codeFixInferFromUsageCallbackParameter7.ts
@@ -0,0 +1,11 @@
+///
+
+// @allowJs: true
+// @checkJs: true
+// @noImplicitAny: false
+
+// @filename: /foo.js
+/////** @type {(x: number) => number} */
+////const foo = x => x + 1;
+
+verify.not.codeFixAvailable();