diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4a19cb8e8c2d..7da1543904a74 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13044,14 +13044,6 @@ namespace ts { return isPropertyDeclaration(node) && !hasAccessorModifier(node) && node.questionToken; } - function isOptionalJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag { - if (!isJSDocPropertyLikeTag(node)) { - return false; - } - const { isBracketed, typeExpression } = node; - return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; - } - function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: Type | undefined): TypePredicate { return { kind, parameterName, parameterIndex, type } as TypePredicate; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0571a9c92df0d..e6b4858945d04 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7778,4 +7778,12 @@ namespace ts { return isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isInterfaceDeclaration(node) || isTypeDeclaration(node) || (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)); } + + export function isOptionalJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag { + if (!isJSDocPropertyLikeTag(node)) { + return false; + } + const { isBracketed, typeExpression } = node; + return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; + } } diff --git a/src/services/codefixes/annotateWithTypeFromJSDoc.ts b/src/services/codefixes/annotateWithTypeFromJSDoc.ts index 629006083af7e..996855f868ddd 100644 --- a/src/services/codefixes/annotateWithTypeFromJSDoc.ts +++ b/src/services/codefixes/annotateWithTypeFromJSDoc.ts @@ -90,6 +90,8 @@ namespace ts.codefix { return transformJSDocFunctionType(node as JSDocFunctionType); case SyntaxKind.TypeReference: return transformJSDocTypeReference(node as TypeReferenceNode); + case SyntaxKind.JSDocTypeLiteral: + return transformJSDocTypeLiteral(node as JSDocTypeLiteral); default: const visited = visitEachChild(node, transformJSDocType, nullTransformationContext); setEmitFlags(visited, EmitFlags.SingleLine); @@ -97,6 +99,17 @@ namespace ts.codefix { } } + function transformJSDocTypeLiteral(node: JSDocTypeLiteral) { + const typeNode = factory.createTypeLiteralNode(map(node.jsDocPropertyTags, tag => + factory.createPropertySignature( + /*modifiers*/ undefined, + isIdentifier(tag.name) ? tag.name : tag.name.right, + isOptionalJSDocPropertyLikeTag(tag) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + tag.typeExpression && visitNode(tag.typeExpression.type, transformJSDocType) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)))); + setEmitFlags(typeNode, EmitFlags.SingleLine); + return typeNode; + } + function transformJSDocOptionalType(node: JSDocOptionalType) { return factory.createUnionTypeNode([visitNode(node.type, transformJSDocType), factory.createTypeReferenceNode("undefined", emptyArray)]); } diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc24.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc24.ts new file mode 100644 index 0000000000000..3b1195414a37e --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc24.ts @@ -0,0 +1,31 @@ +/// +// @strict: true + +////class C { +//// /** +//// * @private +//// * @param {number} foo +//// * @param {Object} [bar] +//// * @param {String} bar.a +//// * @param {Number} [bar.b] +//// * @param bar.c +//// */ +//// m(foo, bar) { } +////} + +verify.codeFix({ + description: ts.Diagnostics.Annotate_with_type_from_JSDoc.message, + index: 2, + newFileContent: +`class C { + /** + * @private + * @param {number} foo + * @param {Object} [bar] + * @param {String} bar.a + * @param {Number} [bar.b] + * @param bar.c + */ + m(foo: number, bar: { a: string; b?: number; c: any; }) { } +}`, +}); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc25.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc25.ts new file mode 100644 index 0000000000000..70b5ad3532738 --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc25.ts @@ -0,0 +1,31 @@ +/// +// @strict: true + +////class C { +//// /** +//// * @private +//// * @param {number} foo +//// * @param {Object} [bar] +//// * @param {String} bar.a +//// * @param {Object} [baz] +//// * @param {number} baz.c +//// */ +//// m(foo, bar, baz) { } +////} + +verify.codeFix({ + description: ts.Diagnostics.Annotate_with_type_from_JSDoc.message, + index: 3, + newFileContent: +`class C { + /** + * @private + * @param {number} foo + * @param {Object} [bar] + * @param {String} bar.a + * @param {Object} [baz] + * @param {number} baz.c + */ + m(foo: number, bar: { a: string; }, baz: { c: number; }) { } +}`, +}); diff --git a/tests/cases/fourslash/annotateWithTypeFromJSDoc26.ts b/tests/cases/fourslash/annotateWithTypeFromJSDoc26.ts new file mode 100644 index 0000000000000..09f64edafa658 --- /dev/null +++ b/tests/cases/fourslash/annotateWithTypeFromJSDoc26.ts @@ -0,0 +1,27 @@ +/// +// @strict: true + +////class C { +//// /** +//// * @private +//// * @param {Object} [foo] +//// * @param {Object} foo.a +//// * @param {String} [foo.a.b] +//// */ +//// m(foo) { } +////} + +verify.codeFix({ + description: ts.Diagnostics.Annotate_with_type_from_JSDoc.message, + index: 1, + newFileContent: +`class C { + /** + * @private + * @param {Object} [foo] + * @param {Object} foo.a + * @param {String} [foo.a.b] + */ + m(foo: { a: { b?: string; }; }) { } +}`, +});