From 2946318df0d855b1e66f7405bcb4c17887a7a0f8 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 5 Dec 2020 02:52:12 +0200 Subject: [PATCH] fix(41526): add JSDoc type annotations before parameters (#41561) --- src/services/codefixes/inferFromUsage.ts | 43 ++++++++++++++++--- .../fourslash/codeFixInferFromUsageArrowJS.ts | 10 +---- ...codeFixInferFromUsageCallbackParameter1.ts | 19 ++++++++ ...codeFixInferFromUsageCallbackParameter2.ts | 19 ++++++++ ...codeFixInferFromUsageCallbackParameter3.ts | 23 ++++++++++ ...codeFixInferFromUsageCallbackParameter4.ts | 23 ++++++++++ ...codeFixInferFromUsageCallbackParameter5.ts | 14 ++++++ ...codeFixInferFromUsageCallbackParameter6.ts | 10 +++++ ...codeFixInferFromUsageCallbackParameter7.ts | 11 +++++ 9 files changed, 157 insertions(+), 15 deletions(-) create mode 100644 tests/cases/fourslash/codeFixInferFromUsageCallbackParameter1.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageCallbackParameter2.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageCallbackParameter3.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageCallbackParameter4.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageCallbackParameter5.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageCallbackParameter6.ts create mode 100644 tests/cases/fourslash/codeFixInferFromUsageCallbackParameter7.ts diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index efad417248..ac05ebda1d 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 0a800fd7d6..d7c4448391 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 0000000000..cf1d1a1b09 --- /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 0000000000..3dd35b667c --- /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 0000000000..e8be2d5aa2 --- /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 0000000000..f62e1ce348 --- /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 0000000000..2287f39269 --- /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 0000000000..ef4babadf6 --- /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 0000000000..618b59f4d3 --- /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();