diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 5d3300ba19be8..35d63b7e2e77e 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -117,6 +117,7 @@ namespace ts {
},
getParameterType: getTypeAtPosition,
getReturnTypeOfSignature,
+ getNullableType,
getNonNullableType,
typeToTypeNode: nodeBuilder.typeToTypeNode,
indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration,
@@ -9954,6 +9955,11 @@ namespace ts {
neverType;
}
+ /**
+ * Add undefined or null or both to a type if they are missing.
+ * @param type - type to add undefined and/or null to if not present
+ * @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both
+ */
function getNullableType(type: Type, flags: TypeFlags): Type {
const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null);
return missing === 0 ? type :
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 6ba1591d54f4b..c3c570582847e 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -2520,6 +2520,7 @@ namespace ts {
* Returns `any` if the index is not valid.
*/
/* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type;
+ getNullableType(type: Type, flags: TypeFlags): Type;
getNonNullableType(type: Type): Type;
/** Note that the resulting nodes cannot be checked. */
diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts
index b5f9e5587a34c..291dc61c32f74 100644
--- a/src/services/codefixes/disableJsDiagnostics.ts
+++ b/src/services/codefixes/disableJsDiagnostics.ts
@@ -32,7 +32,7 @@ namespace ts.codefix {
}
}
- // If all fails, add an extra new line immediatlly before the error span.
+ // If all fails, add an extra new line immediately before the error span.
return {
span: { start: position, length: 0 },
newText: `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}`
@@ -67,4 +67,4 @@ namespace ts.codefix {
}]
}];
}
-}
\ No newline at end of file
+}
diff --git a/src/services/codefixes/fixJSDocTypes.ts b/src/services/codefixes/fixJSDocTypes.ts
new file mode 100644
index 0000000000000..249bc32dbf73d
--- /dev/null
+++ b/src/services/codefixes/fixJSDocTypes.ts
@@ -0,0 +1,40 @@
+/* @internal */
+namespace ts.codefix {
+ registerCodeFix({
+ errorCodes: [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code],
+ getCodeActions: getActionsForJSDocTypes
+ });
+
+ function getActionsForJSDocTypes(context: CodeFixContext): CodeAction[] | undefined {
+ const sourceFile = context.sourceFile;
+ const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
+ const decl = ts.findAncestor(node, n => n.kind === SyntaxKind.VariableDeclaration);
+ if (!decl) return;
+ const checker = context.program.getTypeChecker();
+
+ const jsdocType = (decl as VariableDeclaration).type;
+ const original = getTextOfNode(jsdocType);
+ const type = checker.getTypeFromTypeNode(jsdocType);
+ const actions = [createAction(jsdocType, sourceFile.fileName, original, checker.typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation))];
+ if (jsdocType.kind === SyntaxKind.JSDocNullableType) {
+ // for nullable types, suggest the flow-compatible `T | null | undefined`
+ // in addition to the jsdoc/closure-compatible `T | null`
+ const replacementWithUndefined = checker.typeToString(checker.getNullableType(type, TypeFlags.Undefined), /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation);
+ actions.push(createAction(jsdocType, sourceFile.fileName, original, replacementWithUndefined));
+ }
+ return actions;
+ }
+
+ function createAction(declaration: TypeNode, fileName: string, original: string, replacement: string): CodeAction {
+ return {
+ description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, replacement]),
+ changes: [{
+ fileName,
+ textChanges: [{
+ span: { start: declaration.getStart(), length: declaration.getWidth() },
+ newText: replacement
+ }]
+ }],
+ };
+ }
+}
diff --git a/src/services/codefixes/fixes.ts b/src/services/codefixes/fixes.ts
index c38820231b00c..ef670c81ba9d6 100644
--- a/src/services/codefixes/fixes.ts
+++ b/src/services/codefixes/fixes.ts
@@ -7,6 +7,7 @@
///
///
///
+///
///
///
///
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax1.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax1.ts
new file mode 100644
index 0000000000000..93107ef669bb8
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax1.ts
@@ -0,0 +1,4 @@
+///
+//// var x: [|?|] = 12;
+
+verify.rangeAfterCodeFix("any");
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax2.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax2.ts
new file mode 100644
index 0000000000000..333b108538fd5
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax2.ts
@@ -0,0 +1,4 @@
+///
+//// var x: [|*|] = 12;
+
+verify.rangeAfterCodeFix("any");
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax3.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax3.ts
new file mode 100644
index 0000000000000..f3b02cb84f13c
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax3.ts
@@ -0,0 +1,4 @@
+///
+//// var x: [|......number[][]|] = 12;
+
+verify.rangeAfterCodeFix("number[][][][]");
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax4.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax4.ts
new file mode 100644
index 0000000000000..e9522331d389e
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax4.ts
@@ -0,0 +1,4 @@
+///
+//// var x: [|Array.|] = 12;
+
+verify.rangeAfterCodeFix("number[]");
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax5.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax5.ts
new file mode 100644
index 0000000000000..6f46f3082e13c
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax5.ts
@@ -0,0 +1,5 @@
+// @strict: true
+///
+//// var x: [|?number|] = 12;
+
+verify.rangeAfterCodeFix("number | null", /*includeWhiteSpace*/ false, /*errorCode*/ 8020, 0);
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax6.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax6.ts
new file mode 100644
index 0000000000000..8af9f09d99dd5
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax6.ts
@@ -0,0 +1,5 @@
+// @strict: true
+///
+//// var x: [|number?|] = 12;
+
+verify.rangeAfterCodeFix("number | null | undefined", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 1);
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax7.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax7.ts
new file mode 100644
index 0000000000000..c80d08b3bac58
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax7.ts
@@ -0,0 +1,4 @@
+///
+//// var x: [|!number|] = 12;
+
+verify.rangeAfterCodeFix("number");
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax8.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax8.ts
new file mode 100644
index 0000000000000..0fa7ddf229cdb
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax8.ts
@@ -0,0 +1,4 @@
+///
+//// var x: [|function(this: number, number): string|] = 12;
+
+verify.rangeAfterCodeFix("(this: number, arg1: number) => string");
diff --git a/tests/cases/fourslash/codeFixChangeJSDocSyntax9.ts b/tests/cases/fourslash/codeFixChangeJSDocSyntax9.ts
new file mode 100644
index 0000000000000..061ded158ead8
--- /dev/null
+++ b/tests/cases/fourslash/codeFixChangeJSDocSyntax9.ts
@@ -0,0 +1,4 @@
+///
+//// var x: [|function(new: number)|] = 12;
+
+verify.rangeAfterCodeFix("new () => number");