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");