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; }; }) { }
+}`,
+});