From 4e506f9efdf4755f2da64419fcbbfa8eacff636f Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Wed, 17 Apr 2024 07:39:19 +0000 Subject: [PATCH 01/14] Add a fixer to fix type errors for --isolatedDeclarations This code fixer fixes the following patterns. 1. Adding missing types on places that type can be explicitly annotated 2. Adding satisfies with type assertion on a value. 3. Create a namespace for expando functions and declare the property on the namespace. 4. Factor out expressions in heritage clauses (class B extends ) and give them separate names. Signed-off-by: Hana Joo --- src/compiler/diagnosticMessages.json | 40 + src/services/_namespaces/ts.codefix.ts | 1 + src/services/codeFixProvider.ts | 1 + .../fixMissingTypeAnnotationOnExports.ts | 1171 +++++++++++++++++ .../codeFixMissingTypeAnnotationOnExports.ts | 14 + ...codeFixMissingTypeAnnotationOnExports10.ts | 22 + ...codeFixMissingTypeAnnotationOnExports11.ts | 21 + ...codeFixMissingTypeAnnotationOnExports12.ts | 20 + ...codeFixMissingTypeAnnotationOnExports13.ts | 20 + ...codeFixMissingTypeAnnotationOnExports14.ts | 21 + ...codeFixMissingTypeAnnotationOnExports15.ts | 21 + ...codeFixMissingTypeAnnotationOnExports16.ts | 28 + ...TypeAnnotationOnExports17-unique-symbol.ts | 13 + ...codeFixMissingTypeAnnotationOnExports18.ts | 22 + ...codeFixMissingTypeAnnotationOnExports19.ts | 19 + .../codeFixMissingTypeAnnotationOnExports2.ts | 20 + ...codeFixMissingTypeAnnotationOnExports20.ts | 21 + ...AnnotationOnExports21-params-and-return.ts | 52 + ...ingTypeAnnotationOnExports22-formatting.ts | 22 + ...notationOnExports23-heritage-formatting.ts | 41 + ...tationOnExports24-heritage-formatting-2.ts | 29 + ...tationOnExports25-heritage-formatting-3.ts | 29 + ...otationOnExports26-fn-in-object-literal.ts | 31 + ...otationOnExports27-non-exported-bidings.ts | 21 + ...ingTypeAnnotationOnExports28-long-types.ts | 111 ++ ...MissingTypeAnnotationOnExports29-inline.ts | 23 + .../codeFixMissingTypeAnnotationOnExports3.ts | 22 + ...TypeAnnotationOnExports30-inline-import.ts | 28 + ...tationOnExports31-inline-import-default.ts | 39 + ...AnnotationOnExports32-inline-short-hand.ts | 29 + ...issingTypeAnnotationOnExports33-methods.ts | 26 + ...TypeAnnotationOnExports34-object-spread.ts | 65 + ...nnotationOnExports35-variable-releative.ts | 16 + ...tationOnExports36-conditional-releative.ts | 31 + ...gTypeAnnotationOnExports37-array-spread.ts | 72 + ...otationOnExports38-unique-symbol-return.ts | 20 + ...tionOnExports39-extract-arr-to-variable.ts | 54 + .../codeFixMissingTypeAnnotationOnExports4.ts | 25 + ...onOnExports40-extract-other-to-variable.ts | 42 + ...ionOnExports41-no-computed-enum-members.ts | 11 + ...nExports42-static-readonly-class-symbol.ts | 18 + ...notationOnExports43-expando-functions-2.ts | 24 + ...notationOnExports43-expando-functions-3.ts | 23 + ...notationOnExports43-expando-functions-4.ts | 24 + ...notationOnExports43-expando-functions-5.ts | 30 + ...AnnotationOnExports43-expando-functions.ts | 23 + ...ypeAnnotationOnExports44-default-export.ts | 16 + .../codeFixMissingTypeAnnotationOnExports5.ts | 24 + .../codeFixMissingTypeAnnotationOnExports6.ts | 14 + .../codeFixMissingTypeAnnotationOnExports7.ts | 16 + .../codeFixMissingTypeAnnotationOnExports8.ts | 18 + .../codeFixMissingTypeAnnotationOnExports9.ts | 20 + 52 files changed, 2564 insertions(+) create mode 100644 src/services/codefixes/fixMissingTypeAnnotationOnExports.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports10.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports11.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports12.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports13.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports14.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports15.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports16.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports17-unique-symbol.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports18.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports19.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports2.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports20.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports21-params-and-return.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports22-formatting.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports23-heritage-formatting.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports24-heritage-formatting-2.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports25-heritage-formatting-3.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports26-fn-in-object-literal.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports27-non-exported-bidings.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports28-long-types.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports29-inline.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports3.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports30-inline-import.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports32-inline-short-hand.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports33-methods.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports34-object-spread.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports35-variable-releative.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports37-array-spread.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports38-unique-symbol-return.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports4.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports41-no-computed-enum-members.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports42-static-readonly-class-symbol.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-2.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-3.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-4.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-5.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports44-default-export.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports5.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports6.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports7.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports8.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports9.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 4d8060daac1ff..907ab34c9597c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7184,6 +7184,46 @@ "category": "Message", "code": 90061 }, + "Add annotation of type '{0}'": { + "category": "Message", + "code": 90062 + }, + "Add return type '{0}'": { + "category": "Message", + "code": 90063 + }, + "Extract base class to variable": { + "category": "Message", + "code": 90064 + }, + "Extract default export to variable": { + "category": "Message", + "code": 90065 + }, + "Extract binding expressions to variable": { + "category": "Message", + "code": 90066 + }, + "Add all missing type annotations": { + "category": "Message", + "code": 90067 + }, + "Add satisfies and an inline type assertion with '{0}'": { + "category": "Message", + "code": 90068 + }, + "Extract to variable and replace with '{0} typeof {0}'": { + "category": "Message", + "code": 90069 + }, + "Mark array literal as const": { + "category": "Message", + "code": 90070 + }, + "Annotate types of properties expando function in a namespace": { + "category": "Message", + "code": 90071 + }, "Convert function to an ES2015 class": { "category": "Message", diff --git a/src/services/_namespaces/ts.codefix.ts b/src/services/_namespaces/ts.codefix.ts index d04e1cbb5537a..b4b2dd37846e0 100644 --- a/src/services/_namespaces/ts.codefix.ts +++ b/src/services/_namespaces/ts.codefix.ts @@ -50,6 +50,7 @@ export * from "../codefixes/fixUnreachableCode"; export * from "../codefixes/fixUnusedLabel"; export * from "../codefixes/fixJSDocTypes"; export * from "../codefixes/fixMissingCallParentheses"; +export * from "../codefixes/fixMissingTypeAnnotationOnExports"; export * from "../codefixes/fixAwaitInSyncFunction"; export * from "../codefixes/fixPropertyOverrideAccessor"; export * from "../codefixes/inferFromUsage"; diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 7ca63871fb2de..9691542a55ab0 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -127,6 +127,7 @@ function getDiagnostics({ program, sourceFile, cancellationToken }: CodeFixConte return [ ...program.getSemanticDiagnostics(sourceFile, cancellationToken), ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), + ...program.getDeclarationDiagnostics(sourceFile, cancellationToken), ...computeSuggestionDiagnostics(sourceFile, program, cancellationToken), ]; } diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts new file mode 100644 index 0000000000000..e007b797e27a7 --- /dev/null +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -0,0 +1,1171 @@ +import { + ArrayBindingPattern, + ArrayLiteralExpression, + AssertionExpression, + BinaryExpression, + BindingElement, + BindingPattern, + ClassDeclaration, + CodeFixAction, + CodeFixAllContext, + CodeFixContext, + createPrinter, + Debug, + Declaration, + defaultMaximumTruncationLength, + DiagnosticAndArguments, + DiagnosticOrDiagnosticAndArguments, + Diagnostics, + ElementAccessExpression, + EmitFlags, + EmitHint, + EntityName, + EntityNameExpression, + ExportAssignment, + Expression, + factory, + FileTextChanges, + findAncestor, + FunctionDeclaration, + GeneratedIdentifierFlags, + getEmitScriptTarget, + getSourceFileOfNode, + getSynthesizedDeepClone, + getTokenAtPosition, + getTrailingCommentRanges, + hasInitializer, + hasSyntacticModifier, + Identifier, + isArrayBindingPattern, + isArrayLiteralExpression, + isAssertionExpression, + isBinaryExpression, + isBindingPattern, + isCallExpression, + isComputedPropertyName, + isConditionalExpression, + isConstTypeReference, + isDeclaration, + isEntityNameExpression, + isEnumMember, + isExpandoPropertyDeclaration, + isExpression, + isFunctionDeclaration, + isFunctionExpressionOrArrowFunction, + isHeritageClause, + isIdentifier, + isIdentifierText, + isNumericLiteral, + isObjectBindingPattern, + isObjectLiteralExpression, + isOmittedExpression, + isParameter, + isPrefixUnaryExpression, + isPropertyAccessExpression, + isPropertyAssignment, + isPropertyDeclaration, + isShorthandPropertyAssignment, + isSpreadAssignment, + isSpreadElement, + isStatement, + isStringLiteral, + isValueSignatureDeclaration, + isVariableDeclaration, + ModifierFlags, + ModifierLike, + Node, + NodeBuilderFlags, + NodeFlags, + ObjectBindingPattern, + ObjectLiteralExpression, + ParameterDeclaration, + PropertyAccessExpression, + PropertyDeclaration, + PropertyName, + setEmitFlags, + SignatureDeclaration, + some, + SourceFile, + SpreadAssignment, + SpreadElement, + SyntaxKind, + textChanges, + TextSpan, + Type, + TypeChecker, + TypeFlags, + TypeNode, + UnionReduction, + VariableDeclaration, + VariableStatement, + walkUpParenthesizedExpressions, +} from "../_namespaces/ts"; + +import { + createCodeFixAction, + createCombinedCodeActions, + createImportAdder, + eachDiagnostic, + registerCodeFix, + typeToAutoImportableTypeNode, +} from "../_namespaces/ts.codefix"; + +const fixId = "fixMissingTypeAnnotationOnExports"; + +const addAnnotationFix = "add-annotation"; +const addInlineTypeAssertion = "add-type-assertion"; +const extractExpression = "extract-expression"; + +const errorCodes = [ + Diagnostics.Function_must_have_an_explicit_return_type_annotation_with_isolatedDeclarations.code, + Diagnostics.Method_must_have_an_explicit_return_type_annotation_with_isolatedDeclarations.code, + Diagnostics.At_least_one_accessor_must_have_an_explicit_return_type_annotation_with_isolatedDeclarations.code, + Diagnostics.Variable_must_have_an_explicit_type_annotation_with_isolatedDeclarations.code, + Diagnostics.Parameter_must_have_an_explicit_type_annotation_with_isolatedDeclarations.code, + Diagnostics.Property_must_have_an_explicit_type_annotation_with_isolatedDeclarations.code, + Diagnostics.Expression_type_can_t_be_inferred_with_isolatedDeclarations.code, + Diagnostics.Binding_elements_can_t_be_exported_directly_with_isolatedDeclarations.code, + Diagnostics.Computed_properties_must_be_number_or_string_literals_variables_or_dotted_expressions_with_isolatedDeclarations.code, + Diagnostics.Enum_member_initializers_must_be_computable_without_references_to_external_symbols_with_isolatedDeclarations.code, + Diagnostics.Extends_clause_can_t_contain_an_expression_with_isolatedDeclarations.code, + Diagnostics.Objects_that_contain_shorthand_properties_can_t_be_inferred_with_isolatedDeclarations.code, + Diagnostics.Objects_that_contain_spread_assignments_can_t_be_inferred_with_isolatedDeclarations.code, + Diagnostics.Arrays_with_spread_elements_can_t_inferred_with_isolatedDeclarations.code, + Diagnostics.Default_exports_can_t_be_inferred_with_isolatedDeclarations.code, + Diagnostics.Only_const_arrays_can_be_inferred_with_isolatedDeclarations.code, + Diagnostics.Assigning_properties_to_functions_without_declaring_them_is_not_supported_with_isolatedDeclarations_Add_an_explicit_declaration_for_the_properties_assigned_to_this_function.code, + Diagnostics.Declaration_emit_for_this_parameter_requires_implicitly_adding_undefined_to_it_s_type_This_is_not_supported_with_isolatedDeclarations.code, + Diagnostics.Add_satisfies_and_a_type_assertion_to_this_expression_satisfies_T_as_T_to_make_the_type_explicit.code, +]; + +const canHaveTypeAnnotation = new Set([ + SyntaxKind.GetAccessor, + SyntaxKind.MethodDeclaration, + SyntaxKind.PropertyDeclaration, + SyntaxKind.FunctionDeclaration, + SyntaxKind.FunctionExpression, + SyntaxKind.ArrowFunction, + SyntaxKind.VariableDeclaration, + SyntaxKind.Parameter, + SyntaxKind.ExportAssignment, + SyntaxKind.ClassDeclaration, + SyntaxKind.ObjectBindingPattern, + SyntaxKind.ArrayBindingPattern, +]); + +const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals + | NodeBuilderFlags.WriteClassExpressionAsTypeLiteral + | NodeBuilderFlags.UseTypeOfFunction + | NodeBuilderFlags.UseStructuralFallback + | NodeBuilderFlags.AllowEmptyTuple + | NodeBuilderFlags.GenerateNamesForShadowedTypeParams + | NodeBuilderFlags.NoTruncation + | NodeBuilderFlags.WriteComputedProps; + +enum TypePrintMode { + FULL, + RELATIVE, +} + +registerCodeFix({ + errorCodes, + fixIds: [fixId], + getCodeActions(context) { + const fixes: CodeFixAction[] = []; + + addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.FULL, f => f.addTypeAnnotation(context.span)); + addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.RELATIVE, f => f.addTypeAnnotation(context.span)); + + addCodeAction(addInlineTypeAssertion, fixes, context, TypePrintMode.FULL, f => f.addInlineAssertion(context.span)); + addCodeAction(addInlineTypeAssertion, fixes, context, TypePrintMode.RELATIVE, f => f.addInlineAssertion(context.span)); + + addCodeAction(extractExpression, fixes, context, TypePrintMode.FULL, f => f.extractAsVariable(context.span)); + + return fixes; + }, + getAllCodeActions: context => { + const changes = withContext(context, TypePrintMode.FULL, f => { + eachDiagnostic(context, errorCodes, diag => { + f.addTypeAnnotation(diag); + }); + }); + return createCombinedCodeActions(changes.textChanges); + }, +}); + +interface Fixer { + addTypeAnnotation(span: TextSpan): DiagnosticOrDiagnosticAndArguments | undefined; + addInlineAssertion(span: TextSpan): DiagnosticOrDiagnosticAndArguments | undefined; + extractAsVariable(span: TextSpan): DiagnosticOrDiagnosticAndArguments | undefined; +} + +function addCodeAction( + fixName: string, + fixes: CodeFixAction[], + context: CodeFixContext | CodeFixAllContext, + typePrintMode: TypePrintMode, + cb: (fixer: Fixer) => DiagnosticOrDiagnosticAndArguments | undefined) { + const changes = withContext(context, typePrintMode, cb); + if (changes.result && changes.textChanges.length) { + fixes.push(createCodeFixAction( + fixName, + changes.textChanges, + changes.result, + fixId, + Diagnostics.Add_all_missing_type_annotations, + )); + } +} + +function withContext( + context: CodeFixContext | CodeFixAllContext, + typePrintMode: TypePrintMode, + cb: (fixer: Fixer) => T, +): { + textChanges: FileTextChanges[]; + result: T; +} { + const emptyInferenceResult: InferenceResult = { typeNode: undefined, mutatedTarget: false }; + const changeTracker = textChanges.ChangeTracker.fromContext(context); + const sourceFile: SourceFile = context.sourceFile; + const program = context.program; + const typeChecker: TypeChecker = program.getTypeChecker(); + const emitResolver = typeChecker.getEmitResolver(); + const scriptTarget = getEmitScriptTarget(program.getCompilerOptions()); + const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host); + const fixedNodes = new Set(); + const expandoPropertiesAdded = new Set(); + const typePrinter = createPrinter({ + preserveSourceNewlines: false, + }); + + const result = cb({ addTypeAnnotation, addInlineAssertion, extractAsVariable }); + importAdder.writeFixes(changeTracker); + + return { + result, + textChanges: changeTracker.getChanges(), + }; + + function addTypeAnnotation(span: TextSpan) { + const nodeWithDiag = getTokenAtPosition(sourceFile, span.start); + + const expandoFunction = findExpandoFunction(nodeWithDiag); + if (expandoFunction) { + if (isFunctionDeclaration(expandoFunction)) { + return createNamespaceForExpandoProperties(expandoFunction); + } + return fixIsolatedDeclarationError(expandoFunction); + } + + const nodeMissingType = findAncestorWithMissingType(nodeWithDiag); + if (nodeMissingType) { + return fixIsolatedDeclarationError(nodeMissingType); + } + return undefined; + } + + function createNamespaceForExpandoProperties(expandoFunc: FunctionDeclaration): DiagnosticOrDiagnosticAndArguments | undefined { + if (expandoPropertiesAdded?.has(expandoFunc)) return undefined; + expandoPropertiesAdded?.add(expandoFunc); + const type = typeChecker.getTypeAtLocation(expandoFunc); + const elements = typeChecker.getPropertiesOfType(type); + if (!expandoFunc.name || elements.length === 0) return undefined; + const newProperties = []; + for (const symbol of elements) { + // non-valid names will not end up in declaration emit + if (!isIdentifierText(symbol.name, program.getCompilerOptions().target)) continue; + // already has an existing declaration + if (symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration)) continue; + + newProperties.push(factory.createVariableStatement( + [factory.createModifier(SyntaxKind.ExportKeyword)], + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + symbol.name, + /*exclamationToken*/ undefined, + typeToTypeNode(typeChecker.getTypeOfSymbol(symbol), expandoFunc), + /*initializer*/ undefined, + )], + ), + )); + } + if (newProperties.length === 0) return undefined; + const modifiers: ModifierLike[] = []; + if (expandoFunc.modifiers?.some(modifier => modifier.kind === SyntaxKind.ExportKeyword)) { + modifiers.push(factory.createModifier(SyntaxKind.ExportKeyword)); + } + modifiers.push(factory.createModifier(SyntaxKind.DeclareKeyword)); + const namespace = factory.createModuleDeclaration( + modifiers, + expandoFunc.name, + factory.createModuleBlock(newProperties), + /*flags*/ NodeFlags.Namespace | NodeFlags.ExportContext | NodeFlags.Ambient | NodeFlags.ContextFlags, + ); + changeTracker.insertNodeAfter(sourceFile, expandoFunc, namespace); + return [Diagnostics.Annotate_types_of_properties_expando_function_in_a_namespace]; + } + + function needsParenthesizedExpressionForAssertion(node: Expression) { + return !isEntityNameExpression(node) && !isCallExpression(node) && !isObjectLiteralExpression(node) && !isArrayLiteralExpression(node); + } + + function createAsExpression(node: Expression, type: TypeNode) { + if (needsParenthesizedExpressionForAssertion(node)) { + node = factory.createParenthesizedExpression(node); + } + return factory.createAsExpression(node, type); + } + + function createSatisfiesAsExpression(node: Expression, type: TypeNode) { + if (needsParenthesizedExpressionForAssertion(node)) { + node = factory.createParenthesizedExpression(node); + } + return factory.createAsExpression(factory.createSatisfiesExpression(node, getSynthesizedDeepClone(type)), type); + } + + function addInlineAssertion(span: TextSpan): DiagnosticOrDiagnosticAndArguments | undefined { + const nodeWithDiag = getTokenAtPosition(sourceFile, span.start); + const expandoFunction = findExpandoFunction(nodeWithDiag); + // No inline assertions for expando members + if (expandoFunction) return; + const targetNode = findBestFittingNode(nodeWithDiag, span); + if (!targetNode || isValueSignatureDeclaration(targetNode) || isValueSignatureDeclaration(targetNode.parent)) return; + const isExpressionTarget = isExpression(targetNode); + const isShorthandPropertyAssignmentTarget = isShorthandPropertyAssignment(targetNode); + + if (!isShorthandPropertyAssignmentTarget && isDeclaration(targetNode)) { + return undefined; + } + // No inline assertions on binding patterns + if (findAncestor(targetNode, isBindingPattern)) { + return undefined; + } + + // No inline assertions on enum members + if (findAncestor(targetNode, isEnumMember)) { + return undefined; + } + // No support for typeof in extends clauses + if (isExpressionTarget && findAncestor(targetNode, isHeritageClause)) { + return undefined; + } + // Can't inline type spread elements. Whatever you do isolated declarations will not infer from them + if (isSpreadElement(targetNode)) { + return undefined; + } + + const variableDeclaration = findAncestor(targetNode, isVariableDeclaration); + const type = variableDeclaration && typeChecker.getTypeAtLocation(variableDeclaration); + // We can't use typeof un an unique symbol. Would result in either + // const s = Symbol("") as unique symbol + // const s = Symbol("") as typeof s + // both of which are not correct + if (type && type.flags & TypeFlags.UniqueESSymbol) { + return undefined; + } + + if (!(isExpressionTarget || isShorthandPropertyAssignmentTarget)) return undefined; + + const { typeNode, mutatedTarget } = inferType(targetNode); + if (!typeNode || mutatedTarget) return undefined; + + if (isShorthandPropertyAssignmentTarget) { + changeTracker.insertNodeAt( + sourceFile, + targetNode.end, + createAsExpression( + getSynthesizedDeepClone(targetNode.name), + typeNode, + ), + { + prefix: ": ", + }, + ); + } + else if (isExpressionTarget) { + changeTracker.replaceNode( + sourceFile, + targetNode, + createSatisfiesAsExpression( + getSynthesizedDeepClone(targetNode), + typeNode, + ), + ); + } + else { + Debug.assertNever(targetNode); + } + return [Diagnostics.Add_satisfies_and_an_inline_type_assertion_with_0, typeToStringForDiag(typeNode)]; + } + + function suggestVariableName(node: Node) { + const nameParts: string[] = []; + while (!(isVariableDeclaration(node) || isPropertyDeclaration(node) || isStatement(node))) { + if (isPropertyAssignment(node)) { + const propName = node.name; + addPropertyName(propName); + } + node = node.parent; + } + if ((isVariableDeclaration(node) || isPropertyDeclaration(node)) && !isBindingPattern(node.name)) { + addPropertyName(node.name); + } + return nameParts.filter(s => isIdentifierText(s, program.getCompilerOptions().target)).reverse().join("_"); + + function addPropertyName(name: PropertyName) { + if (isIdentifier(name)) { + nameParts.push(name.text); + } + if (isStringLiteral(name)) { + nameParts.push(name.text); + } + if (isNumericLiteral(name)) { + nameParts.push(name.text); + } + if (isComputedPropertyName(name)) { + let computedName = name.expression; + + if (isStringLiteral(computedName)) { + nameParts.push(computedName.text); + } + if (isNumericLiteral(computedName)) { + nameParts.push(computedName.text); + } + if ( + isPrefixUnaryExpression(computedName) + && isNumericLiteral(computedName.operand) + ) { + if (computedName.operator === SyntaxKind.MinusToken) { + nameParts.push("M" + computedName.operand.text); + } + else if (computedName.operator === SyntaxKind.PlusToken) { + nameParts.push("M" + computedName.operand.text); + } + } + + // We only support dotted identifiers as property keys + while (true) { + if (isIdentifier(computedName)) { + nameParts.push(computedName.text); + break; + } + else if (isPropertyAccessExpression(computedName)) { + nameParts.push(computedName.name.text); + computedName = computedName.expression; + } + } + } + } + } + + function extractAsVariable(span: TextSpan): DiagnosticOrDiagnosticAndArguments | undefined { + const nodeWithDiag = getTokenAtPosition(sourceFile, span.start); + const targetNode = findBestFittingNode(nodeWithDiag, span) as Expression; + if (!targetNode || isValueSignatureDeclaration(targetNode) || isValueSignatureDeclaration(targetNode.parent)) return; + + const isExpressionTarget = isExpression(targetNode); + + // Only extract expressions + if (!isExpressionTarget) return; + + // Before any extracting array literals must be const + if (isArrayLiteralExpression(targetNode)) { + changeTracker.replaceNode( + sourceFile, + targetNode, + createAsExpression(targetNode, factory.createTypeReferenceNode("const")), + ); + return [Diagnostics.Mark_array_literal_as_const]; + } + + const parentPropertyAssignment = findAncestor(targetNode, isPropertyAssignment); + if (parentPropertyAssignment) { + // identifiers or entity names can already just be typeof-ed + if (parentPropertyAssignment === targetNode.parent && isEntityNameExpression(targetNode)) return; + + const tempName = factory.createUniqueName( + suggestVariableName(targetNode), + GeneratedIdentifierFlags.Optimistic, + ); + let replacementTarget = targetNode; + let initializationNode = targetNode; + if (isSpreadElement(replacementTarget)) { + replacementTarget = walkUpParenthesizedExpressions(replacementTarget.parent) as Expression; + if (isConstAssertion(replacementTarget.parent)) { + initializationNode = replacementTarget = replacementTarget.parent; + } + else { + initializationNode = createAsExpression( + replacementTarget, + factory.createTypeReferenceNode("const"), + ); + } + } + + if (isEntityNameExpression(replacementTarget)) return undefined; + + const variableDefinition = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration( + tempName, + /*exclamationToken*/ undefined, + /*type*/ undefined, + initializationNode, + ), + ], NodeFlags.Const), + ); + + const statement = findAncestor(targetNode, isStatement); + changeTracker.insertNodeBefore(sourceFile, statement!, variableDefinition); + + changeTracker.replaceNode( + sourceFile, + replacementTarget, + factory.createAsExpression( + factory.cloneNode(tempName), + factory.createTypeQueryNode( + factory.cloneNode(tempName), + ), + ), + ); + return [Diagnostics.Extract_to_variable_and_replace_with_0_typeof_0, typeToStringForDiag(tempName)]; + } + } + + function findExpandoFunction(node: Node) { + const expandoDeclaration = findAncestor(node, n => isStatement(n) ? "quit" : isExpandoPropertyDeclaration(n as Declaration)) as PropertyAccessExpression | ElementAccessExpression | BinaryExpression; + + if (expandoDeclaration && isExpandoPropertyDeclaration(expandoDeclaration)) { + let assignmentTarget = expandoDeclaration; + + // Some late bound expando members use thw whole expression as the declaration. + if (isBinaryExpression(assignmentTarget)) { + assignmentTarget = assignmentTarget.left as PropertyAccessExpression | ElementAccessExpression; + if (!isExpandoPropertyDeclaration(assignmentTarget)) return undefined; + } + const targetType = typeChecker.getTypeAtLocation(assignmentTarget.expression); + if (!targetType) return; + + const properties = typeChecker.getPropertiesOfType(targetType); + if (some(properties, p => p.valueDeclaration === expandoDeclaration || p.valueDeclaration === expandoDeclaration.parent)) { + const fn = targetType.symbol.valueDeclaration; + if (fn) { + if (isFunctionExpressionOrArrowFunction(fn) && isVariableDeclaration(fn.parent)) { + return fn.parent; + } + if (isFunctionDeclaration(fn)) { + return fn; + } + } + } + } + return undefined; + } + + function fixIsolatedDeclarationError(node: Node): DiagnosticOrDiagnosticAndArguments | undefined { + // Different --isolatedDeclarion errors might result in annotating type on the same node + // avoid creating a duplicated fix in those cases + if (fixedNodes?.has(node)) return undefined; + fixedNodes?.add(node); + + switch (node.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.VariableDeclaration: + return addTypeToVariableLike(node as ParameterDeclaration | PropertyDeclaration | VariableDeclaration); + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + return addTypeToSignatureDeclaration(node as SignatureDeclaration, sourceFile); + case SyntaxKind.ExportAssignment: + return transformExportAssignment(node as ExportAssignment); + case SyntaxKind.ClassDeclaration: + return transformExtendsClauseWithExpression(node as ClassDeclaration); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + return transformDestructuringPatterns(node as BindingPattern); + default: + throw new Error(`Cannot find a fix for the given node ${node.kind}`); + } + } + + function addTypeToSignatureDeclaration(func: SignatureDeclaration, sourceFile: SourceFile): DiagnosticOrDiagnosticAndArguments | undefined { + if (func.type) { + return; + } + const { typeNode } = inferType(func); + if (typeNode) { + changeTracker.tryInsertTypeAnnotation( + sourceFile, + func, + typeNode, + ); + return [Diagnostics.Add_return_type_0, typeToStringForDiag(typeNode)]; + } + } + + function transformExportAssignment(defaultExport: ExportAssignment): DiagnosticOrDiagnosticAndArguments | undefined { + if (defaultExport.isExportEquals) { + return; + } + + const { typeNode } = inferType(defaultExport.expression); + if (!typeNode) return undefined; + changeTracker.replaceNodeWithNodes(sourceFile, defaultExport, [ + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + "__default", + /*exclamationToken*/ undefined, + typeNode, + defaultExport.expression, + )], + NodeFlags.Const, + ), + ), + factory.updateExportAssignment(defaultExport, defaultExport?.modifiers, factory.createIdentifier("__default")), + ]); + return [ + Diagnostics.Extract_default_export_to_variable, + ]; + } + + /** + * Factor out expressions used extends clauses in classs definitions as a + * variable and annotate type on the new variable. + */ + function transformExtendsClauseWithExpression(classDecl: ClassDeclaration): DiagnosticAndArguments | undefined { + const extendsClause = classDecl.heritageClauses?.find(p => p.token === SyntaxKind.ExtendsKeyword); + const heritageExpression = extendsClause?.types[0]; + if (!heritageExpression) { + return undefined; + } + const { typeNode: heritageTypeNode } = inferType(heritageExpression.expression); + if (!heritageTypeNode) { + return undefined; + } + + const baseClassName = factory.createUniqueName( + classDecl.name ? classDecl.name.text + "Base" : "Anonymous", + GeneratedIdentifierFlags.Optimistic, + ); + + // e.g. const Point3DBase: typeof Point2D = mixin(Point2D); + const heritageVariable = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + baseClassName, + /*exclamationToken*/ undefined, + heritageTypeNode, + heritageExpression.expression, + )], + NodeFlags.Const, + ), + ); + // const touchingToken = getTouchingToken(heritageExpression); + changeTracker.insertNodeBefore(sourceFile, classDecl, heritageVariable); + const trailingComments = getTrailingCommentRanges(sourceFile.text, heritageExpression.end); + const realEnd = trailingComments?.[trailingComments.length - 1]?.end ?? heritageExpression.end; + changeTracker.replaceRange( + sourceFile, + { + pos: heritageExpression.getFullStart(), + end: realEnd, + }, + baseClassName, + { + prefix: " ", + }, + ); + return [Diagnostics.Extract_base_class_to_variable]; + } + + interface ExpressionReverseChain { + element?: BindingElement; + parent?: ExpressionReverseChain; + expression: SubExpression; + } + + const enum ExpressionType { + TEXT = 0, + COMPUTED = 1, + ARRAY_ACCESS = 2, + IDENTIFIER = 3, + } + + type SubExpression = + | { kind: ExpressionType.TEXT; text: string; } + | { kind: ExpressionType.COMPUTED; computed: Expression; } + | { kind: ExpressionType.ARRAY_ACCESS; arrayIndex: number; } + | { kind: ExpressionType.IDENTIFIER; identifier: Identifier; }; + + function transformDestructuringPatterns(bindingPattern: BindingPattern): DiagnosticOrDiagnosticAndArguments | undefined { + const enclosingVariableDeclaration = bindingPattern.parent as VariableDeclaration; + const enclosingVarStmt = bindingPattern.parent.parent.parent as VariableStatement; + if (!enclosingVariableDeclaration.initializer) return undefined; + + let baseExpr: ExpressionReverseChain; + const newNodes: Node[] = []; + if (!isIdentifier(enclosingVariableDeclaration.initializer)) { + // For complex expressions we want to create a temporary variable + const tempHolderForReturn = factory.createUniqueName("dest", GeneratedIdentifierFlags.Optimistic); + baseExpr = { expression: { kind: ExpressionType.IDENTIFIER, identifier: tempHolderForReturn } }; + newNodes.push(factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + tempHolderForReturn, + /*exclamationToken*/ undefined, + /*type*/ undefined, + enclosingVariableDeclaration.initializer, + )], + NodeFlags.Const, + ), + )); + } + else { + // If we are destructuring an identifier, just use that. No need for temp var. + baseExpr = { expression: { kind: ExpressionType.IDENTIFIER, identifier: enclosingVariableDeclaration.initializer } }; + } + + const bindingElements: ExpressionReverseChain[] = []; + if (isArrayBindingPattern(bindingPattern)) { + addArrayBindingPatterns(bindingPattern, bindingElements, baseExpr); + } + else { + addObjectBindingPatterns(bindingPattern, bindingElements, baseExpr); + } + + const expressionToVar = new Map(); + + for (const bindingElement of bindingElements) { + if (bindingElement.element!.propertyName && isComputedPropertyName(bindingElement.element!.propertyName)) { + const computedExpression = bindingElement.element!.propertyName.expression; + const identifierForComputedProperty = factory.getGeneratedNameForNode(computedExpression); + const variableDecl = factory.createVariableDeclaration( + identifierForComputedProperty, + /*exclamationToken*/ undefined, + /*type*/ undefined, + computedExpression, + ); + const variableList = factory.createVariableDeclarationList([variableDecl], NodeFlags.Const); + const variableStatement = factory.createVariableStatement(/*modifiers*/ undefined, variableList); + newNodes.push(variableStatement); + expressionToVar.set(computedExpression, identifierForComputedProperty); + } + + // Name is the RHS of : in case colon exists, otherwise it's just the name of the destructuring + const name = bindingElement.element!.name; + // isBindingPattern + if (isArrayBindingPattern(name)) { + addArrayBindingPatterns(name, bindingElements, bindingElement); + } + else if (isObjectBindingPattern(name)) { + addObjectBindingPatterns(name, bindingElements, bindingElement); + } + else { + const { typeNode } = inferType(name); + let variableInitializer = createChainedExpression(bindingElement, expressionToVar); + if (bindingElement.element!.initializer) { + const propertyName = bindingElement.element?.propertyName; + const tempName = factory.createUniqueName( + propertyName && isIdentifier(propertyName) ? propertyName.text : "temp", + GeneratedIdentifierFlags.Optimistic, + ); + newNodes.push(factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + tempName, + /*exclamationToken*/ undefined, + /*type*/ undefined, + variableInitializer, + )], + NodeFlags.Const, + ), + )); + variableInitializer = factory.createConditionalExpression( + factory.createBinaryExpression( + tempName, + factory.createToken(SyntaxKind.EqualsEqualsEqualsToken), + factory.createIdentifier("undefined"), + ), + factory.createToken(SyntaxKind.QuestionToken), + bindingElement.element!.initializer, + factory.createToken(SyntaxKind.ColonToken), + variableInitializer, + ); + } + const exportModifier = hasSyntacticModifier(enclosingVarStmt, ModifierFlags.Export) ? + [factory.createToken(SyntaxKind.ExportKeyword)] : + undefined; + newNodes.push(factory.createVariableStatement( + exportModifier, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + name, + /*exclamationToken*/ undefined, + typeNode, + variableInitializer, + )], + NodeFlags.Const, + ), + )); + } + } + + if (enclosingVarStmt.declarationList.declarations.length > 1) { + newNodes.push(factory.updateVariableStatement( + enclosingVarStmt, + enclosingVarStmt.modifiers, + factory.updateVariableDeclarationList( + enclosingVarStmt.declarationList, + enclosingVarStmt.declarationList.declarations.filter(node => node !== bindingPattern.parent), + ), + )); + } + changeTracker.replaceNodeWithNodes(sourceFile, enclosingVarStmt, newNodes); + return [ + Diagnostics.Extract_binding_expressions_to_variable, + ]; + } + + function addArrayBindingPatterns(bindingPattern: ArrayBindingPattern, bindingElements: ExpressionReverseChain[], parent: ExpressionReverseChain) { + for (let i = 0; i < bindingPattern.elements.length; ++i) { + const element = bindingPattern.elements[i]; + if (isOmittedExpression(element)) { + continue; + } + bindingElements.push({ + element, + parent, + expression: { kind: ExpressionType.ARRAY_ACCESS, arrayIndex: i }, + }); + } + } + + function addObjectBindingPatterns(bindingPattern: ObjectBindingPattern, bindingElements: ExpressionReverseChain[], parent: ExpressionReverseChain) { + for (const bindingElement of bindingPattern.elements) { + let name: string; + if (bindingElement.propertyName) { + if (isComputedPropertyName(bindingElement.propertyName)) { + bindingElements.push({ + element: bindingElement, + parent, + expression: { kind: ExpressionType.COMPUTED, computed: bindingElement.propertyName.expression }, + }); + continue; + } + else { + name = bindingElement.propertyName.text; + } + } + else { + name = (bindingElement.name as Identifier).text; + } + bindingElements.push({ + element: bindingElement, + parent, + expression: { kind: ExpressionType.TEXT, text: name }, + }); + } + } + + function createChainedExpression(expression: ExpressionReverseChain, expressionToVar: Map): Expression { + const reverseTraverse: ExpressionReverseChain[] = [expression]; + while (expression.parent) { + expression = expression.parent; + reverseTraverse.push(expression); + } + let chainedExpression: Expression = (reverseTraverse[reverseTraverse.length - 1].expression as { identifier: Identifier; }).identifier; + for (let i = reverseTraverse.length - 2; i >= 0; --i) { + const nextSubExpr = reverseTraverse[i].expression; + if (nextSubExpr.kind === ExpressionType.TEXT) { + chainedExpression = factory.createPropertyAccessChain( + chainedExpression, + /*questionDotToken*/ undefined, + factory.createIdentifier(nextSubExpr.text), + ); + } + else if (nextSubExpr.kind === ExpressionType.COMPUTED) { + chainedExpression = factory.createElementAccessExpression( + chainedExpression, + expressionToVar.get(nextSubExpr.computed)!, + ); + } + else if (nextSubExpr.kind === ExpressionType.ARRAY_ACCESS) { + chainedExpression = factory.createElementAccessExpression( + chainedExpression, + nextSubExpr.arrayIndex, + ); + } + } + return chainedExpression; + } + interface InferenceResult { + typeNode?: TypeNode | undefined; + mutatedTarget: boolean; + } + + function inferType(node: Node): InferenceResult { + if (typePrintMode !== TypePrintMode.FULL) { + return relativeType(node); + } + + let type = isValueSignatureDeclaration(node) ? + tryGetReturnType(node) : + typeChecker.getTypeAtLocation(node); + if (!type) { + return emptyInferenceResult; + } + if (isParameter(node) && emitResolver.requiresAddingImplicitUndefined(node)) { + type = typeChecker.getUnionType([typeChecker.getUndefinedType(), type], UnionReduction.None); + } + const flags = ( + isVariableDeclaration(node) || + (isPropertyDeclaration(node) && hasSyntacticModifier(node, ModifierFlags.Static | ModifierFlags.Readonly)) + ) && type.flags & TypeFlags.UniqueESSymbol ? + NodeBuilderFlags.AllowUniqueESSymbolType : NodeBuilderFlags.None; + return { + typeNode: typeToTypeNode(type, findAncestor(node, isDeclaration) ?? sourceFile, flags), + mutatedTarget: false, + }; + } + + function createTypeOfFromEntityNameExpression(node: EntityNameExpression) { + return factory.createTypeQueryNode(getSynthesizedDeepClone(node) as EntityName); + } + + function typeFromArraySpreadElements( + node: ArrayLiteralExpression, + name = "temp", + ) { + const isConstContext = !!findAncestor(node, isConstAssertion); + if (!isConstContext) return emptyInferenceResult; + return typeFromSpreads( + node, + name, + isConstContext, + n => n.elements, + isSpreadElement, + factory.createSpreadElement, + props => factory.createArrayLiteralExpression(props, /*multiLine*/ true), + types => factory.createTupleTypeNode(types.map(factory.createRestTypeNode)), + ); + } + + function typeFromObjectSpreadAssignment( + node: ObjectLiteralExpression, + name = "temp", + ) { + const isConstContext = !!findAncestor(node, isConstAssertion); + return typeFromSpreads( + node, + name, + isConstContext, + n => n.properties, + isSpreadAssignment, + factory.createSpreadAssignment, + props => factory.createObjectLiteralExpression(props, /*multiLine*/ true), + factory.createIntersectionTypeNode, + ); + } + + function typeFromSpreads( + node: T, + name: string, + isConstContext: boolean, + getChildren: (node: T) => readonly TElements[], + isSpread: (node: Node) => node is TSpread, + createSpread: (node: Expression) => TSpread, + makeNodeOfKind: (newElements: (TSpread | TElements)[]) => T, + finalType: (types: TypeNode[]) => TypeNode, + ): InferenceResult { + const intersectionTypes: TypeNode[] = []; + const newSpreads: TSpread[] = []; + let currentVariableProperties: TElements[] | undefined; + const statement = findAncestor(node, isStatement); + for (const prop of getChildren(node)) { + if (isSpread(prop)) { + finalizesVariablePart(); + if (isEntityNameExpression(prop.expression)) { + intersectionTypes.push(createTypeOfFromEntityNameExpression(prop.expression)); + newSpreads.push(prop); + } + else { + makeVariable(prop.expression); + } + } + else { + (currentVariableProperties ??= []).push(prop); + } + } + if (newSpreads.length === 0) { + return emptyInferenceResult; + } + finalizesVariablePart(); + changeTracker.replaceNode(sourceFile, node, makeNodeOfKind(newSpreads)); + return { + typeNode: finalType(intersectionTypes), + mutatedTarget: true, + }; + + function makeVariable(expression: Expression) { + const tempName = factory.createUniqueName( + name + "_Part" + (newSpreads.length + 1), + GeneratedIdentifierFlags.Optimistic, + ); + const initializer = !isConstContext ? expression : factory.createAsExpression( + expression, + factory.createTypeReferenceNode("const"), + ); + const variableDefinition = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration( + tempName, + /*exclamationToken*/ undefined, + /*type*/ undefined, + initializer, + ), + ], NodeFlags.Const), + ); + changeTracker.insertNodeBefore(sourceFile, statement!, variableDefinition); + + intersectionTypes.push(createTypeOfFromEntityNameExpression(tempName)); + newSpreads.push(createSpread(tempName)); + } + + function finalizesVariablePart() { + if (currentVariableProperties) { + makeVariable(makeNodeOfKind( + currentVariableProperties, + )); + currentVariableProperties = undefined; + } + } + } + + function isConstAssertion(location: Node): location is AssertionExpression { + return isAssertionExpression(location) && isConstTypeReference(location.type); + } + + function relativeType(node: Node): InferenceResult { + if (isParameter(node)) { + return emptyInferenceResult; + } + if (isShorthandPropertyAssignment(node)) { + return { + typeNode: createTypeOfFromEntityNameExpression(node.name), + mutatedTarget: false, + }; + } + if (isEntityNameExpression(node)) { + return { + typeNode: createTypeOfFromEntityNameExpression(node), + mutatedTarget: false, + }; + } + if (isConstAssertion(node)) { + return relativeType(node.expression); + } + if (isArrayLiteralExpression(node)) { + const variableDecl = findAncestor(node, isVariableDeclaration); + const partName = variableDecl && isIdentifier(variableDecl.name) ? variableDecl.name.text : undefined; + return typeFromArraySpreadElements(node, partName); + } + if (isObjectLiteralExpression(node)) { + const variableDecl = findAncestor(node, isVariableDeclaration); + const partName = variableDecl && isIdentifier(variableDecl.name) ? variableDecl.name.text : undefined; + return typeFromObjectSpreadAssignment(node, partName); + } + if (isVariableDeclaration(node) && node.initializer) { + return relativeType(node.initializer); + } + if (isConditionalExpression(node)) { + const { typeNode: trueType, mutatedTarget: mTrue } = relativeType(node.whenTrue); + if (!trueType) return emptyInferenceResult; + const { typeNode: falseType, mutatedTarget: mFalse } = relativeType(node.whenFalse); + if (!falseType) return emptyInferenceResult; + return { + typeNode: factory.createUnionTypeNode([trueType, falseType]), + mutatedTarget: mTrue || mFalse, + }; + } + + return emptyInferenceResult; + } + + function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None) { + let isTruncated = false; + const result = typeToAutoImportableTypeNode(typeChecker, importAdder, type, enclosingDeclaration, scriptTarget, declarationEmitNodeBuilderFlags | flags, { + moduleResolverHost: program, + trackSymbol() { + return true; + }, + reportTruncationError() { + isTruncated = true; + }, + }); + return isTruncated ? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) : result; + } + + function tryGetReturnType(node: SignatureDeclaration): Type | undefined { + const signature = typeChecker.getSignatureFromDeclaration(node); + if (signature) { + return typeChecker.getReturnTypeOfSignature(signature); + } + } + + function addTypeToVariableLike(decl: ParameterDeclaration | VariableDeclaration | PropertyDeclaration): DiagnosticOrDiagnosticAndArguments | undefined{ + const { typeNode } = inferType(decl); + if (typeNode) { + if (decl.type) { + changeTracker.replaceNode(getSourceFileOfNode(decl), decl.type, typeNode); + } + else { + changeTracker.tryInsertTypeAnnotation(getSourceFileOfNode(decl), decl, typeNode); + } + return [Diagnostics.Add_annotation_of_type_0, typeToStringForDiag(typeNode)]; + } + } + + function typeToStringForDiag(node: Node) { + setEmitFlags(node, EmitFlags.SingleLine); + const result = typePrinter.printNode(EmitHint.Unspecified, node, sourceFile); + if (result.length > defaultMaximumTruncationLength) { + return result.substring(0, defaultMaximumTruncationLength - "...".length) + "..."; + } + setEmitFlags(node, EmitFlags.None); + return result; + } +} + + +// Some --isolatedDeclarations errors are not present on the node that directly needs type annotation, so look in the +// ancestors to look for node that needs type annotation. This function can return undefined if the AST is ill-formed. +function findAncestorWithMissingType(node: Node): Node | undefined { + return findAncestor(node, (n) => { + return canHaveTypeAnnotation.has(n.kind) && + ((!isObjectBindingPattern(n) && !isArrayBindingPattern(n)) || isVariableDeclaration(n.parent)); + }); +} + +function findBestFittingNode(node: Node, span: TextSpan) { + while (node && node.end < span.start + span.length) { + node = node.parent; + } + while (node.parent.pos === node.pos && node.parent.end === node.end) { + node = node.parent; + } + if (isIdentifier(node) && hasInitializer(node.parent) && node.parent.initializer) { + return node.parent.initializer; + } + return node; +} diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports.ts new file mode 100644 index 0000000000000..5649333d5cd3a --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports.ts @@ -0,0 +1,14 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function foo() { return 42; } +////export const g = foo(); + +verify.codeFix({ + description: "Add annotation of type 'number'", + index: 0, + newFileContent: +`function foo() { return 42; } +export const g: number = foo();`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports10.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports10.ts new file mode 100644 index 0000000000000..83e626e81e15f --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports10.ts @@ -0,0 +1,22 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function foo() { +//// return { x: 1, y: 1 }; +////} +////export default foo(); + +verify.codeFix({ + description: "Extract default export to variable", + index: 0, + newFileContent: +`function foo() { + return { x: 1, y: 1 }; +} +const __default: { + x: number; + y: number; +} = foo(); +export default __default;`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports11.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports11.ts new file mode 100644 index 0000000000000..43ac0b20d20ec --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports11.ts @@ -0,0 +1,21 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +//// function mixin any>(ctor: T): T { +//// return ctor; +//// } +//// class Point2D { x = 0; y = 0; } +//// export class Point3D extends mixin(Point2D) { z = 0; } + +verify.codeFix({ + description: ts.Diagnostics.Extract_base_class_to_variable.message, + index: 0, + newFileContent: +`function mixin any>(ctor: T): T { + return ctor; +} +class Point2D { x = 0; y = 0; } +const Point3DBase: typeof Point2D = mixin(Point2D); +export class Point3D extends Point3DBase { z = 0; }` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports12.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports12.ts new file mode 100644 index 0000000000000..0e51849bc2afd --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports12.ts @@ -0,0 +1,20 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +//// function foo() { +//// return { x: 1, y: 1 }; +//// } +//// export const { x, y } = foo(); + +verify.codeFix({ + description: ts.Diagnostics.Extract_binding_expressions_to_variable.message, + index: 0, + newFileContent: +`function foo() { + return { x: 1, y: 1 }; +} +const dest = foo(); +export const x: number = dest.x; +export const y: number = dest.y;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports13.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports13.ts new file mode 100644 index 0000000000000..019960140d4dc --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports13.ts @@ -0,0 +1,20 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +//// function foo() { +//// return { x: 1, y: 1 }; +//// } +//// export const { x: abcd, y: defg } = foo(); + +verify.codeFix({ + description: ts.Diagnostics.Extract_binding_expressions_to_variable.message, + index: 0, + newFileContent: +`function foo() { + return { x: 1, y: 1 }; +} +const dest = foo(); +export const abcd: number = dest.x; +export const defg: number = dest.y;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports14.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports14.ts new file mode 100644 index 0000000000000..fade5b0c65cad --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports14.ts @@ -0,0 +1,21 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +//// function foo() { +//// return { x: 1, y: 1}; +//// } +//// export const { x, y = 0} = foo(), z= 42; + +verify.codeFix({ + description: ts.Diagnostics.Extract_binding_expressions_to_variable.message, + index: 0, + newFileContent: +`function foo() { + return { x: 1, y: 1}; +} +const dest = foo(); +export const x: number = dest.x; +const temp = dest.y; +export const y: number = temp === undefined ? 0 : dest.y; +export const z = 42;`}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports15.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports15.ts new file mode 100644 index 0000000000000..41ea67e189849 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports15.ts @@ -0,0 +1,21 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +//// function foo() { +//// return { x: 1, y: 1 } as const; +//// } +//// export const { x, y = 0 } = foo(); + +verify.codeFix({ + description: ts.Diagnostics.Extract_binding_expressions_to_variable.message, + index: 0, + newFileContent: +`function foo() { + return { x: 1, y: 1 } as const; +} +const dest = foo(); +export const x: 1 = dest.x; +const temp = dest.y; +export const y: 1 | 0 = temp === undefined ? 0 : dest.y;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports16.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports16.ts new file mode 100644 index 0000000000000..8eefdf01fe2a1 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports16.ts @@ -0,0 +1,28 @@ + +/// + +// @isolatedDeclarations: true +// @declaration: true +//// function foo() { +//// return { x: 1, y: {42: {dd: "45"}, b: 2} }; +//// } +//// function foo3(): "42" { +//// return "42"; +//// } +//// export const { x: a , y: { [foo3()]: {dd: e} } } = foo(); + +verify.codeFix({ + description: ts.Diagnostics.Extract_binding_expressions_to_variable.message, + index: 0, + newFileContent: +`function foo() { + return { x: 1, y: {42: {dd: "45"}, b: 2} }; +} +function foo3(): "42" { + return "42"; +} +const dest = foo(); +export const a: number = dest.x; +const _a = foo3(); +export const e: string = (dest.y)[_a].dd;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports17-unique-symbol.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports17-unique-symbol.ts new file mode 100644 index 0000000000000..409b0869af68e --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports17-unique-symbol.ts @@ -0,0 +1,13 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 +//// export const a = Symbol(); + +verify.codeFix({ + description: "Add annotation of type 'unique symbol'", + index: 0, + newFileContent: +`export const a: unique symbol = Symbol();` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports18.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports18.ts new file mode 100644 index 0000000000000..c1e9e7900ae5b --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports18.ts @@ -0,0 +1,22 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +//// function foo() { return 42; } +//// export class A { +//// readonly a = () => foo(); +//// } + +verify.codeFixAvailable([ + { description: "Add return type 'number'" }, +]); + +verify.codeFix({ + description: "Add return type 'number'", + index: 0, + newFileContent: +`function foo() { return 42; } +export class A { + readonly a = (): number => foo(); +}` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports19.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports19.ts new file mode 100644 index 0000000000000..90b8c10a4fc54 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports19.ts @@ -0,0 +1,19 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 +////export const a = { +//// z: Symbol() +////} as const; + +verify.codeFix({ + description: `Add annotation of type '{ readonly z: symbol; }'`, + index: 0, + newFileContent: +`export const a: { + readonly z: symbol; +} = { + z: Symbol() +} as const;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports2.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports2.ts new file mode 100644 index 0000000000000..c0ce15c2dc205 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports2.ts @@ -0,0 +1,20 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////const a = 42; +////const b = 43; +////export function foo() { return a + b; } + +verify.codeFixAvailable([ + { description: "Add return type 'number'" } +]); + +verify.codeFix({ + description: "Add return type 'number'", + index: 0, + newFileContent: +`const a = 42; +const b = 43; +export function foo(): number { return a + b; }`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports20.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports20.ts new file mode 100644 index 0000000000000..fb5c043266284 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports20.ts @@ -0,0 +1,21 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 +//// export function foo () { +//// return Symbol(); +//// } + +verify.codeFixAvailable([ + { description: "Add return type 'symbol'" } +]); + +verify.codeFix({ + description: "Add return type 'symbol'", + index: 0, + newFileContent: +`export function foo (): symbol { + return Symbol(); +}` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports21-params-and-return.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports21-params-and-return.ts new file mode 100644 index 0000000000000..1bb1e2f081f25 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports21-params-and-return.ts @@ -0,0 +1,52 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +/////** +//// * Test +//// */ +////export function foo(): number { return 0; } +/////** +////* Docs +////*/ +////export const bar = (a = foo()) => +//// a; +////// Trivia + + +verify.codeFix({ + description: "Add return type 'number'", + index: 0, + applyChanges: true, + newFileContent: +`/** + * Test + */ +export function foo(): number { return 0; } +/** +* Docs +*/ +export const bar = (a = foo()): number => + a; +// Trivia` +}); + + +verify.codeFix({ + description: "Add annotation of type 'number'", + index: 0, + applyChanges: true, + newFileContent: +`/** + * Test + */ +export function foo(): number { return 0; } +/** +* Docs +*/ +export const bar = (a: number = foo()): number => + a; +// Trivia` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports22-formatting.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports22-formatting.ts new file mode 100644 index 0000000000000..54f306aa8aaad --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports22-formatting.ts @@ -0,0 +1,22 @@ +/// +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 +/////** +//// * Test +//// */ +////export function foo(){} + +verify.codeFixAvailable([ + { description: "Add return type 'void'" } +]); + +verify.codeFix({ + description: "Add return type 'void'", + index: 0, + newFileContent: +`/** + * Test + */ +export function foo(): void{}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports23-heritage-formatting.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports23-heritage-formatting.ts new file mode 100644 index 0000000000000..8643743b2f32b --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports23-heritage-formatting.ts @@ -0,0 +1,41 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function mixin any>(ctor: T): T { +//// return ctor; +////} +////class Point2D { x = 0; y = 0; } +////interface I{} +////export class Point3D extends +//// /** Base class */ +//// mixin(Point2D) +//// // Test +//// implements I +//// { +//// z = 0; +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Extract_base_class_to_variable.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Extract_base_class_to_variable.message, + index: 0, + newFileContent: +`function mixin any>(ctor: T): T { + return ctor; +} +class Point2D { x = 0; y = 0; } +interface I{} +const Point3DBase: typeof Point2D = + /** Base class */ + mixin(Point2D); +export class Point3D extends Point3DBase + // Test + implements I + { + z = 0; +}` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports24-heritage-formatting-2.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports24-heritage-formatting-2.ts new file mode 100644 index 0000000000000..cd8d9df7dfba0 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports24-heritage-formatting-2.ts @@ -0,0 +1,29 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function mixin any>(ctor: T): T { +//// return ctor; +////} +////class Point2D { x = 0; y = 0; } +////export class Point3D2 extends mixin(Point2D) { +//// z = 0; +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Extract_base_class_to_variable.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Extract_base_class_to_variable.message, + index: 0, + newFileContent: +`function mixin any>(ctor: T): T { + return ctor; +} +class Point2D { x = 0; y = 0; } +const Point3D2Base: typeof Point2D = mixin(Point2D); +export class Point3D2 extends Point3D2Base { + z = 0; +}` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports25-heritage-formatting-3.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports25-heritage-formatting-3.ts new file mode 100644 index 0000000000000..40bea1eef1ee1 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports25-heritage-formatting-3.ts @@ -0,0 +1,29 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function mixin any>(ctor: T): T { +//// return ctor; +////} +////class Point2D { x = 0; y = 0; } +////export class Point3D3 extends mixin(Point2D) /* DD*/ { +//// z = 0; +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Extract_base_class_to_variable.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Extract_base_class_to_variable.message, + index: 0, + newFileContent: +`function mixin any>(ctor: T): T { + return ctor; +} +class Point2D { x = 0; y = 0; } +const Point3D3Base: typeof Point2D = mixin(Point2D) /* DD*/; +export class Point3D3 extends Point3D3Base { + z = 0; +}` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports26-fn-in-object-literal.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports26-fn-in-object-literal.ts new file mode 100644 index 0000000000000..05840331903d1 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports26-fn-in-object-literal.ts @@ -0,0 +1,31 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// fileName: code.ts +////export const extensions = { +//// /** +//// */ +//// fn: (actualValue: T, expectedValue: T) => { +//// return actualValue === expectedValue +//// }, +//// fn2: function(actualValue: T, expectedValue: T) { +//// return actualValue === expectedValue +//// } +////} + +verify.codeFixAll({ + fixId: "fixMissingTypeAnnotationOnExports", + fixAllDescription: ts.Diagnostics.Add_all_missing_type_annotations.message, + newFileContent: +`export const extensions = { + /** + */ + fn: (actualValue: T, expectedValue: T): boolean => { + return actualValue === expectedValue + }, + fn2: function(actualValue: T, expectedValue: T): boolean { + return actualValue === expectedValue + } +}` +}) diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports27-non-exported-bidings.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports27-non-exported-bidings.ts new file mode 100644 index 0000000000000..f8e71d5c411c3 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports27-non-exported-bidings.ts @@ -0,0 +1,21 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// fileName: code.ts +////let p = { x: 1, y: 2} +////const a = 1, b = 10, { x, y } = p, c = 1; +////export { x, y } +////export const d = a + b + c; + +verify.codeFixAll({ + fixId: "fixMissingTypeAnnotationOnExports", + fixAllDescription: ts.Diagnostics.Add_all_missing_type_annotations.message, + newFileContent: +`let p = { x: 1, y: 2} +const x: number = p.x; +const y: number = p.y; +const a = 1, b = 10, c = 1; +export { x, y } +export const d: number = a + b + c;` +}) diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports28-long-types.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports28-long-types.ts new file mode 100644 index 0000000000000..1de6a9ea124d7 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports28-long-types.ts @@ -0,0 +1,111 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// fileName: code.ts +////export const sessionLoader = { +//// async loadSession() { +//// if (Math.random() > 0.5) { +//// return { +//// PROP_1: { +//// name: false, +//// }, +//// PROPERTY_2: { +//// name: 1, +//// }, +//// PROPERTY_3: { +//// name: 1 +//// }, +//// PROPERTY_4: { +//// name: 315, +//// }, +//// }; +//// } +//// +//// return { +//// PROP_1: { +//// name: false, +//// }, +//// PROPERTY_2: { +//// name: undefined, +//// }, +//// PROPERTY_3: { +//// }, +//// PROPERTY_4: { +//// name: 576, +//// }, +//// }; +//// }, +////}; + + +const description = "Add return type 'Promise<{\n PROP_1: {\n name: boolean;\n };\n PROPERTY_2: {\n name: number;\n };\n PROPERTY_3: {\n name: number;\n };\n PROPE...'"; +verify.codeFixAvailable([ + { description } +]); + +verify.codeFix({ + description, + index: 0, + newFileContent: +`export const sessionLoader = { + async loadSession(): Promise<{ + PROP_1: { + name: boolean; + }; + PROPERTY_2: { + name: number; + }; + PROPERTY_3: { + name: number; + }; + PROPERTY_4: { + name: number; + }; + } | { + PROP_1: { + name: boolean; + }; + PROPERTY_2: { + name: any; + }; + PROPERTY_3: { + name?: undefined; + }; + PROPERTY_4: { + name: number; + }; + }> { + if (Math.random() > 0.5) { + return { + PROP_1: { + name: false, + }, + PROPERTY_2: { + name: 1, + }, + PROPERTY_3: { + name: 1 + }, + PROPERTY_4: { + name: 315, + }, + }; + } + + return { + PROP_1: { + name: false, + }, + PROPERTY_2: { + name: undefined, + }, + PROPERTY_3: { + }, + PROPERTY_4: { + name: 576, + }, + }; + }, +};` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports29-inline.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports29-inline.ts new file mode 100644 index 0000000000000..cc9dfe9302f26 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports29-inline.ts @@ -0,0 +1,23 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// fileName: code.ts +////function getString() { +//// return "" +////} +////export const exp = { +//// prop: getString() +////}; + +verify.codeFix({ + description: "Add satisfies and an inline type assertion with 'string'", + index: 1, + newFileContent: +`function getString() { + return "" +} +export const exp = { + prop: getString() satisfies string as string +};` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports3.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports3.ts new file mode 100644 index 0000000000000..931fedb0a6f7f --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports3.ts @@ -0,0 +1,22 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////const a = 42; +////const b = 42; +////export class C { +//// //making sure comments are not changed +//// property =a+b; // comment should stay here +////} + +verify.codeFix({ + description: "Add annotation of type 'number'", + index: 0, + newFileContent: +`const a = 42; +const b = 42; +export class C { + //making sure comments are not changed + property: number =a+b; // comment should stay here +}`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports30-inline-import.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports30-inline-import.ts new file mode 100644 index 0000000000000..e271d355a3d64 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports30-inline-import.ts @@ -0,0 +1,28 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +// @Filename: /person-code.ts +////export type Person = { x: string; } +////export function getPerson() : Person { +//// return null! +////} + +// @Filename: /code.ts +////import { getPerson } from "./person-code"; +////export const exp = { +//// person: getPerson() +////}; + +goTo.file("/code.ts"); + +verify.codeFix({ + description: "Add satisfies and an inline type assertion with 'Person'", + index: 1, + newFileContent: +`import { getPerson, Person } from "./person-code"; +export const exp = { + person: getPerson() satisfies Person as Person +};` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts new file mode 100644 index 0000000000000..a7c5789d9afa3 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts @@ -0,0 +1,39 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +// @Filename: /person-code.ts +////export type Person = { x: string; } +////export function getPerson() : Person { +//// return null! +////} + +// @Filename: /code.ts +////import { getPerson } from "./person-code"; +////export default { +//// person: getPerson() +////}; + +goTo.file("/code.ts"); +verify.codeFixAvailable([ + { + "description": "Extract default export to variable" + }, + { + "description": "Add satisfies and an inline type assertion with 'Person'" + }, + { + "description": "Extract to variable and replace with 'person_1 typeof person_1'" + } +]) + +verify.codeFix({ + description: "Add satisfies and an inline type assertion with 'Person'", + index: 1, + newFileContent: +`import { getPerson, Person } from "./person-code"; +export default { + person: getPerson() satisfies Person as Person +};` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports32-inline-short-hand.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports32-inline-short-hand.ts new file mode 100644 index 0000000000000..39e5a468f1fbe --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports32-inline-short-hand.ts @@ -0,0 +1,29 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @Filename: /code.ts +////const x = 1; +////export default { +//// x +////}; + +verify.codeFix({ + description: "Add satisfies and an inline type assertion with 'number'", + index: 1, + newFileContent: +`const x = 1; +export default { + x: x as number +};` +}); + +verify.codeFix({ + description: "Add satisfies and an inline type assertion with 'typeof x'", + index: 2, + newFileContent: +`const x = 1; +export default { + x: x as typeof x +};` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports33-methods.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports33-methods.ts new file mode 100644 index 0000000000000..45eafd535c1bd --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports33-methods.ts @@ -0,0 +1,26 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +// @Filename: /code.ts +////export class Foo { +//// m() { +//// } +////} + +verify.codeFixAvailable([ + { + "description": "Add return type 'void'" + }, +]) + +verify.codeFix({ + description: "Add return type 'void'", + index: 0, + newFileContent: +`export class Foo { + m(): void { + } +}` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports34-object-spread.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports34-object-spread.ts new file mode 100644 index 0000000000000..1528457fa32fd --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports34-object-spread.ts @@ -0,0 +1,65 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +// @Filename: /code.ts +////const Start = { +//// A: 'A', +//// B: 'B', +////} as const; +//// +////const End = { +//// Y: "Y", +//// Z: "Z" +////} as const; +////export const All_Part1 = {}; +////function getPart() { +//// return { M: "Z"} +////} +//// +////export const All = { +//// x: 1, +//// ...Start, +//// y: 1, +//// ...getPart(), +//// ...End, +//// z: 1, +////}; +verify.codeFix({ + description: "Add annotation of type 'typeof All_Part1_1 & typeof Start & typeof All_Part3 & typeof All_Part4 & typeof End & typeof All_Part6'" , + index: 1, + newFileContent: +`const Start = { + A: 'A', + B: 'B', +} as const; + +const End = { + Y: "Y", + Z: "Z" +} as const; +export const All_Part1 = {}; +function getPart() { + return { M: "Z"} +} + +const All_Part1_1 = { + x: 1 +}; +const All_Part3 = { + y: 1 +}; +const All_Part4 = getPart(); +const All_Part6 = { + z: 1 +}; +export const All: typeof All_Part1_1 & typeof Start & typeof All_Part3 & typeof All_Part4 & typeof End & typeof All_Part6 = { + ...All_Part1_1, + ...Start, + ...All_Part3, + ...All_Part4, + ...End, + ...All_Part6 +};` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports35-variable-releative.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports35-variable-releative.ts new file mode 100644 index 0000000000000..d647b8962df19 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports35-variable-releative.ts @@ -0,0 +1,16 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +// @Filename: /code.ts +////const foo = { a: 1 } +////export const exported = foo; + +verify.codeFix({ + description: "Add annotation of type 'typeof foo'" , + index: 1, + newFileContent: +`const foo = { a: 1 } +export const exported: typeof foo = foo;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts new file mode 100644 index 0000000000000..eca8c213dadef --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts @@ -0,0 +1,31 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +// @Filename: /code.ts +////const A = "A" +////const B = "B" +////export const AB = Math.random()? A: B; +verify.codeFixAvailable([ + { + "description": "Add annotation of type '\"A\" | \"B\"'" + }, + { + "description": "Add annotation of type 'typeof A | typeof B'" + }, + { + "description": "Add satisfies and an inline type assertion with '\"A\" | \"B\"'" + }, + { + "description": "Add satisfies and an inline type assertion with 'typeof A | typeof B'" + } +]) +verify.codeFix({ + description: "Add satisfies and an inline type assertion with 'typeof A | typeof B'" , + index: 3, + newFileContent: +`const A = "A" +const B = "B" +export const AB = (Math.random() ? A : B) satisfies typeof A | typeof B as typeof A | typeof B;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports37-array-spread.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports37-array-spread.ts new file mode 100644 index 0000000000000..784c891aeaff1 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports37-array-spread.ts @@ -0,0 +1,72 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +// @Filename: /code.ts +////const Start = [ +//// 'A', +//// 'B', +////] as const; +//// +////const End = [ +//// "Y", +//// "Z" +////] as const; +////export const All_Part1 = {}; +////function getPart() { +//// return ["Z"] +////} +//// +////export const All = [ +//// 1, +//// ...Start, +//// 1, +//// ...getPart(), +//// ...End, +//// 1, +////] as const; +verify.codeFix({ + description: `Add annotation of type '[...typeof All_Part1_1, ...typeof Start, ...typeof All_Part3, ...typeof All_Part4, ...typeof End, ...typeof All_Part6]'` , + index: 1, + newFileContent: +`const Start = [ + 'A', + 'B', +] as const; + +const End = [ + "Y", + "Z" +] as const; +export const All_Part1 = {}; +function getPart() { + return ["Z"] +} + +const All_Part1_1 = [ + 1 +] as const; +const All_Part3 = [ + 1 +] as const; +const All_Part4 = getPart() as const; +const All_Part6 = [ + 1 +] as const; +export const All: [ + ...typeof All_Part1_1, + ...typeof Start, + ...typeof All_Part3, + ...typeof All_Part4, + ...typeof End, + ...typeof All_Part6 +] = [ + ...All_Part1_1, + ...Start, + ...All_Part3, + ...All_Part4, + ...End, + ...All_Part6 +] as const;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports38-unique-symbol-return.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports38-unique-symbol-return.ts new file mode 100644 index 0000000000000..f386138f7a6ac --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports38-unique-symbol-return.ts @@ -0,0 +1,20 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////const u: unique symbol = Symbol(); +////export const fn = () => ({ u } as const); + +verify.codeFix({ + description: +`Add return type '{ readonly u: typeof u; }'` , + index: 0, + newFileContent: +`const u: unique symbol = Symbol(); +export const fn = (): { + readonly u: typeof u; +} => ({ u } as const);` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts new file mode 100644 index 0000000000000..d17e16706a8cb --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts @@ -0,0 +1,54 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////let c: string[] = []; +////export let o = { +//// p: [ +//// ...c +//// ] +////} + +verify.codeFix({ + description: `Mark array literal as const`, + applyChanges: true, + index: 2, + newFileContent: +`let c: string[] = []; +export let o = { + p: [ + ...c + ] as const +}` +}); + +verify.codeFix({ + description: `Extract to variable and replace with 'o_p typeof o_p'`, + applyChanges: true, + index: 1, + newFileContent: +`let c: string[] = []; +const o_p = [ + ...c +] as const; +export let o = { + p: o_p as typeof o_p +}` +}); + +verify.codeFix({ + description: `Add annotation of type 'readonly string[]'`, + applyChanges: true, + index: 0, + newFileContent: +`let c: string[] = []; +const o_p: readonly string[] = [ + ...c +] as const; +export let o = { + p: o_p as typeof o_p +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports4.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports4.ts new file mode 100644 index 0000000000000..134413013ee2d --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports4.ts @@ -0,0 +1,25 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////const a = 42; +////const b = 42; +////export class C { +//// method() { return a + b }; +////} + +verify.codeFixAvailable([ + { description: "Add return type 'number'" }, +]); + +verify.codeFix({ + description: "Add return type 'number'", + index: 0, + newFileContent: +`const a = 42; +const b = 42; +export class C { + method(): number { return a + b }; +}`, + +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts new file mode 100644 index 0000000000000..7b4c87a1363dd --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts @@ -0,0 +1,42 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////let c: string[] = []; +////export let o = { +//// p: Math.random() ? []: [ +//// ...c +//// ] +////} + +verify.codeFix({ + description: `Extract to variable and replace with 'o_p typeof o_p'`, + applyChanges: true, + index: 2, + newFileContent: +`let c: string[] = []; +const o_p = Math.random() ? [] : [ + ...c +]; +export let o = { + p: o_p as typeof o_p +}` +}); + + +verify.codeFix({ + description: `Add annotation of type 'string[]'`, + applyChanges: true, + index: 0, + newFileContent: +`let c: string[] = []; +const o_p: string[] = Math.random() ? [] : [ + ...c +]; +export let o = { + p: o_p as typeof o_p +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports41-no-computed-enum-members.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports41-no-computed-enum-members.ts new file mode 100644 index 0000000000000..49451416c9195 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports41-no-computed-enum-members.ts @@ -0,0 +1,11 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////enum E { +//// A = "foo".length +////} +verify.codeFixAvailable([]) \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports42-static-readonly-class-symbol.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports42-static-readonly-class-symbol.ts new file mode 100644 index 0000000000000..c068338746301 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports42-static-readonly-class-symbol.ts @@ -0,0 +1,18 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////class A { +//// static readonly p1 = Symbol(); +////} +verify.codeFix({ + description: "Add annotation of type 'unique symbol'", + index: 0, + newFileContent: +`class A { + static readonly p1: unique symbol = Symbol(); +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-2.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-2.ts new file mode 100644 index 0000000000000..837a2b1ba62a5 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-2.ts @@ -0,0 +1,24 @@ + +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////const foo = () => {} +////foo/*a*/["a"] = "A"; +////foo["b"] = "C" + +verify.codeFix({ + description: "Add annotation of type '{ (): void; a: string; b: string; }'", + index: 1, + newFileContent: +`const foo: { + (): void; + a: string; + b: string; +} = () => {} +foo["a"] = "A"; +foo["b"] = "C"` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-3.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-3.ts new file mode 100644 index 0000000000000..e6dcbcaf6ed87 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-3.ts @@ -0,0 +1,23 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////function foo(): void {} +////foo.x = 1; +////foo.y = 1; + +verify.codeFix({ + description: "Annotate types of properties expando function in a namespace", + index: 0, + newFileContent: +`function foo(): void {} +declare namespace foo { + export var x: number; + export var y: number; +} +foo.x = 1; +foo.y = 1;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-4.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-4.ts new file mode 100644 index 0000000000000..246dac348132e --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-4.ts @@ -0,0 +1,24 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////function foo(): void {} +////// cannot name this property because it's an invalid variable name. +////foo["@bar"] = 42; +////foo.x = 1; + +verify.codeFix({ + description: "Annotate types of properties expando function in a namespace", + index: 0, + newFileContent: +`function foo(): void {} +declare namespace foo { + export var x: number; +} +// cannot name this property because it's an invalid variable name. +foo["@bar"] = 42; +foo.x = 1;` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-5.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-5.ts new file mode 100644 index 0000000000000..82c56f8fe9180 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions-5.ts @@ -0,0 +1,30 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////function foo(): void {} +////// x already exists, so do not generate code for 'x' +////foo.x = 1; +////foo.y = 1; +////namespace foo { +//// export let x = 42; +////} + +verify.codeFix({ + description: "Annotate types of properties expando function in a namespace", + index: 0, + newFileContent: +`function foo(): void {} +declare namespace foo { + export var y: number; +} +// x already exists, so do not generate code for 'x' +foo.x = 1; +foo.y = 1; +namespace foo { + export let x = 42; +}` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions.ts new file mode 100644 index 0000000000000..f2bf792166104 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports43-expando-functions.ts @@ -0,0 +1,23 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +////const foo = (): void => {} +////foo.a = "A"; +////foo.b = "C" + +verify.codeFix({ + description: "Add annotation of type '{ (): void; a: string; b: string; }'", + index: 0, + newFileContent: +`const foo: { + (): void; + a: string; + b: string; +} = (): void => {} +foo.a = "A"; +foo.b = "C"` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports44-default-export.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports44-default-export.ts new file mode 100644 index 0000000000000..565534de23766 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports44-default-export.ts @@ -0,0 +1,16 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2019 + +// @Filename: /code.ts +//// export default 1 + 1; + +verify.codeFix({ + description: "Extract default export to variable", + index: 0, + newFileContent: +`const __default: number = 1 + 1; +export default __default;` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports5.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports5.ts new file mode 100644 index 0000000000000..8a934a215e76a --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports5.ts @@ -0,0 +1,24 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////const a = 42; +////const b = 42; +////export class C { +//// get property() { return a + b; } +////} + +verify.codeFixAvailable([ + { description: "Add return type 'number'" } +]); + +verify.codeFix({ + description: "Add return type 'number'", + index: 0, + newFileContent: +`const a = 42; +const b = 42; +export class C { + get property(): number { return a + b; } +}`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports6.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports6.ts new file mode 100644 index 0000000000000..c435f377ff451 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports6.ts @@ -0,0 +1,14 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function foo(): number[] { return [42]; } +////export const c = [...foo()]; + +verify.codeFix({ + description: "Add annotation of type 'number[]'", + index: 0, + newFileContent: +`function foo(): number[] { return [42]; } +export const c: number[] = [...foo()];`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports7.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports7.ts new file mode 100644 index 0000000000000..a7a8e0fe2d945 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports7.ts @@ -0,0 +1,16 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function foo(): number[] { return [42]; } +////export const c = { foo: foo() }; + +verify.codeFix({ + description: `Add annotation of type '{ foo: number[]; }'`, + index: 0, + newFileContent: +`function foo(): number[] { return [42]; } +export const c: { + foo: number[]; +} = { foo: foo() };`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports8.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports8.ts new file mode 100644 index 0000000000000..fa049ed7264d6 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports8.ts @@ -0,0 +1,18 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function foo() {return 42;} +////export const g = function () { return foo(); }; + +verify.codeFixAvailable([ + { description: "Add return type 'number'" }, +]); + +verify.codeFix({ + description: "Add return type 'number'", + index: 0, + newFileContent: +`function foo() {return 42;} +export const g = function (): number { return foo(); };`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports9.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports9.ts new file mode 100644 index 0000000000000..422017135c874 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports9.ts @@ -0,0 +1,20 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +////function foo( ){ +//// return 42; +////} +////const a = foo(); +////export = a; + +verify.codeFix({ + description: "Add annotation of type 'number'", + index: 0, + newFileContent: +`function foo( ){ + return 42; +} +const a: number = foo(); +export = a;`, +}); From 0191a46b52676b40caa0b1044df60caf8259d0ed Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Mon, 22 Apr 2024 08:07:12 +0000 Subject: [PATCH 02/14] (Non-functional) Apply 'npm run format' on fixMissingTypeAnnotationOnExports.ts --- .../fixMissingTypeAnnotationOnExports.ts | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index e007b797e27a7..e9b7dd4d5c276 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -204,7 +204,8 @@ function addCodeAction( fixes: CodeFixAction[], context: CodeFixContext | CodeFixAllContext, typePrintMode: TypePrintMode, - cb: (fixer: Fixer) => DiagnosticOrDiagnosticAndArguments | undefined) { + cb: (fixer: Fixer) => DiagnosticOrDiagnosticAndArguments | undefined, +) { const changes = withContext(context, typePrintMode, cb); if (changes.result && changes.textChanges.length) { fixes.push(createCodeFixAction( @@ -236,7 +237,7 @@ function withContext( const fixedNodes = new Set(); const expandoPropertiesAdded = new Set(); const typePrinter = createPrinter({ - preserveSourceNewlines: false, + preserveSourceNewlines: false, }); const result = cb({ addTypeAnnotation, addInlineAssertion, extractAsVariable }); @@ -318,10 +319,10 @@ function withContext( } function createSatisfiesAsExpression(node: Expression, type: TypeNode) { - if (needsParenthesizedExpressionForAssertion(node)) { - node = factory.createParenthesizedExpression(node); - } - return factory.createAsExpression(factory.createSatisfiesExpression(node, getSynthesizedDeepClone(type)), type); + if (needsParenthesizedExpressionForAssertion(node)) { + node = factory.createParenthesizedExpression(node); + } + return factory.createAsExpression(factory.createSatisfiesExpression(node, getSynthesizedDeepClone(type)), type); } function addInlineAssertion(span: TextSpan): DiagnosticOrDiagnosticAndArguments | undefined { @@ -574,22 +575,22 @@ function withContext( case SyntaxKind.Parameter: case SyntaxKind.PropertyDeclaration: case SyntaxKind.VariableDeclaration: - return addTypeToVariableLike(node as ParameterDeclaration | PropertyDeclaration | VariableDeclaration); + return addTypeToVariableLike(node as ParameterDeclaration | PropertyDeclaration | VariableDeclaration); case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: - return addTypeToSignatureDeclaration(node as SignatureDeclaration, sourceFile); + return addTypeToSignatureDeclaration(node as SignatureDeclaration, sourceFile); case SyntaxKind.ExportAssignment: - return transformExportAssignment(node as ExportAssignment); + return transformExportAssignment(node as ExportAssignment); case SyntaxKind.ClassDeclaration: - return transformExtendsClauseWithExpression(node as ClassDeclaration); + return transformExtendsClauseWithExpression(node as ClassDeclaration); case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: - return transformDestructuringPatterns(node as BindingPattern); + return transformDestructuringPatterns(node as BindingPattern); default: - throw new Error(`Cannot find a fix for the given node ${node.kind}`); + throw new Error(`Cannot find a fix for the given node ${node.kind}`); } } @@ -609,30 +610,30 @@ function withContext( } function transformExportAssignment(defaultExport: ExportAssignment): DiagnosticOrDiagnosticAndArguments | undefined { - if (defaultExport.isExportEquals) { - return; - } - - const { typeNode } = inferType(defaultExport.expression); - if (!typeNode) return undefined; - changeTracker.replaceNodeWithNodes(sourceFile, defaultExport, [ - factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList( - [factory.createVariableDeclaration( - "__default", - /*exclamationToken*/ undefined, - typeNode, - defaultExport.expression, - )], - NodeFlags.Const, - ), - ), - factory.updateExportAssignment(defaultExport, defaultExport?.modifiers, factory.createIdentifier("__default")), - ]); - return [ - Diagnostics.Extract_default_export_to_variable, - ]; + if (defaultExport.isExportEquals) { + return; + } + + const { typeNode } = inferType(defaultExport.expression); + if (!typeNode) return undefined; + changeTracker.replaceNodeWithNodes(sourceFile, defaultExport, [ + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + "__default", + /*exclamationToken*/ undefined, + typeNode, + defaultExport.expression, + )], + NodeFlags.Const, + ), + ), + factory.updateExportAssignment(defaultExport, defaultExport?.modifiers, factory.createIdentifier("__default")), + ]); + return [ + Diagnostics.Extract_default_export_to_variable, + ]; } /** @@ -660,7 +661,7 @@ function withContext( /*modifiers*/ undefined, factory.createVariableDeclarationList( [factory.createVariableDeclaration( - baseClassName, + baseClassName, /*exclamationToken*/ undefined, heritageTypeNode, heritageExpression.expression, @@ -915,7 +916,7 @@ function withContext( function inferType(node: Node): InferenceResult { if (typePrintMode !== TypePrintMode.FULL) { - return relativeType(node); + return relativeType(node); } let type = isValueSignatureDeclaration(node) ? @@ -1123,7 +1124,7 @@ function withContext( } } - function addTypeToVariableLike(decl: ParameterDeclaration | VariableDeclaration | PropertyDeclaration): DiagnosticOrDiagnosticAndArguments | undefined{ + function addTypeToVariableLike(decl: ParameterDeclaration | VariableDeclaration | PropertyDeclaration): DiagnosticOrDiagnosticAndArguments | undefined { const { typeNode } = inferType(decl); if (typeNode) { if (decl.type) { @@ -1147,25 +1148,24 @@ function withContext( } } - // Some --isolatedDeclarations errors are not present on the node that directly needs type annotation, so look in the // ancestors to look for node that needs type annotation. This function can return undefined if the AST is ill-formed. function findAncestorWithMissingType(node: Node): Node | undefined { - return findAncestor(node, (n) => { - return canHaveTypeAnnotation.has(n.kind) && - ((!isObjectBindingPattern(n) && !isArrayBindingPattern(n)) || isVariableDeclaration(n.parent)); - }); + return findAncestor(node, n => { + return canHaveTypeAnnotation.has(n.kind) && + ((!isObjectBindingPattern(n) && !isArrayBindingPattern(n)) || isVariableDeclaration(n.parent)); + }); } function findBestFittingNode(node: Node, span: TextSpan) { - while (node && node.end < span.start + span.length) { - node = node.parent; - } - while (node.parent.pos === node.pos && node.parent.end === node.end) { - node = node.parent; - } - if (isIdentifier(node) && hasInitializer(node.parent) && node.parent.initializer) { - return node.parent.initializer; - } - return node; + while (node && node.end < span.start + span.length) { + node = node.parent; + } + while (node.parent.pos === node.pos && node.parent.end === node.end) { + node = node.parent; + } + if (isIdentifier(node) && hasInitializer(node.parent) && node.parent.initializer) { + return node.parent.initializer; + } + return node; } From c9698a3261dd2be351108d7ffe1681771ef4266d Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Mon, 22 Apr 2024 08:14:39 +0000 Subject: [PATCH 03/14] Only get code fixes from declaration diagnostics when --isolatedDeclaration is on. --- src/services/codeFixProvider.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 9691542a55ab0..764a6dbd6426c 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -124,10 +124,15 @@ export function eachDiagnostic(context: CodeFixAllContext, errorCodes: readonly } function getDiagnostics({ program, sourceFile, cancellationToken }: CodeFixContextBase) { - return [ + const diagnostics = [ ...program.getSemanticDiagnostics(sourceFile, cancellationToken), ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), - ...program.getDeclarationDiagnostics(sourceFile, cancellationToken), ...computeSuggestionDiagnostics(sourceFile, program, cancellationToken), ]; + if (program.getCompilerOptions().isolatedDeclarations) { + diagnostics.push( + ...program.getDeclarationDiagnostics(sourceFile, cancellationToken), + ); + } + return diagnostics; } From 33e82b3f1254f4dfbd4203d409ac4aa3509b6a7a Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Wed, 24 Apr 2024 13:16:02 +0000 Subject: [PATCH 04/14] Add test cases for experimental / non-experimental decorators --- ...ingTypeAnnotationOnExports45-decorators.ts | 67 +++++++++++++++++++ ...tionOnExports46-decorators-experimental.ts | 66 ++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports45-decorators.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports46-decorators-experimental.ts diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports45-decorators.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports45-decorators.ts new file mode 100644 index 0000000000000..4add7e69f7817 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports45-decorators.ts @@ -0,0 +1,67 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +// @Filename: /code.ts + +//// function classDecorator (value: T, context: ClassDecoratorContext) {} +//// function methodDecorator ( +//// target: (...args: number[])=> number, +//// context: ClassMethodDecoratorContext number>) {} +//// function getterDecorator(value: Function, context: ClassGetterDecoratorContext) {} +//// function setterDecorator(value: Function, context: ClassSetterDecoratorContext) {} +//// function fieldDecorator(value: undefined, context: ClassFieldDecoratorContext) {} +//// function foo() { return 42;} +//// +//// @classDecorator +//// export class A { +//// @methodDecorator +//// sum(...args: number[]) { +//// return args.reduce((a, b) => a + b, 0); +//// } +//// getSelf() { +//// return this; +//// } +//// @getterDecorator +//// get a() { +//// return foo(); +//// } +//// @setterDecorator +//// set a(value) {} +//// +//// @fieldDecorator classProp = foo(); +//// } + +verify.codeFixAll({ + fixId: "fixMissingTypeAnnotationOnExports", + fixAllDescription: ts.Diagnostics.Add_all_missing_type_annotations.message, + newFileContent: +`function classDecorator (value: T, context: ClassDecoratorContext) {} +function methodDecorator ( + target: (...args: number[])=> number, + context: ClassMethodDecoratorContext number>) {} +function getterDecorator(value: Function, context: ClassGetterDecoratorContext) {} +function setterDecorator(value: Function, context: ClassSetterDecoratorContext) {} +function fieldDecorator(value: undefined, context: ClassFieldDecoratorContext) {} +function foo() { return 42;} + +@classDecorator +export class A { + @methodDecorator + sum(...args: number[]): number { + return args.reduce((a, b) => a + b, 0); + } + getSelf(): this { + return this; + } + @getterDecorator + get a(): number { + return foo(); + } + @setterDecorator + set a(value) {} + + @fieldDecorator classProp: number = foo(); +}` +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports46-decorators-experimental.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports46-decorators-experimental.ts new file mode 100644 index 0000000000000..25f3ea60e5e76 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports46-decorators-experimental.ts @@ -0,0 +1,66 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @experimentalDecorators: true + +// @Filename: /code.ts + +//// function classDecorator() { return (target: T) => target; } +//// function methodDecorator() { return (target: any, key: string, descriptor: PropertyDescriptor) => descriptor;} +//// function parameterDecorator() { return (target: any, key: string, idx: number) => {};} +//// function getterDecorator() { return (target: any, key: string) => {}; } +//// function setterDecorator() { return (target: any, key: string) => {}; } +//// function fieldDecorator() { return (target: any, key: string) => {}; } +//// function foo() { return 42; } +//// +//// @classDecorator() +//// export class A { +//// @methodDecorator() +//// sum(...args: number[]) { +//// return args.reduce((a, b) => a + b, 0); +//// } +//// getSelf() { +//// return this; +//// } +//// passParameter(@parameterDecorator() param = foo()) {} +//// @getterDecorator() +//// get a() { +//// return foo(); +//// } +//// @setterDecorator() +//// set a(value) {} +//// @fieldDecorator() classProp = foo(); +//// } + +verify.codeFixAll({ + fixId: "fixMissingTypeAnnotationOnExports", + fixAllDescription: ts.Diagnostics.Add_all_missing_type_annotations.message, + newFileContent: +`function classDecorator() { return (target: T) => target; } +function methodDecorator() { return (target: any, key: string, descriptor: PropertyDescriptor) => descriptor;} +function parameterDecorator() { return (target: any, key: string, idx: number) => {};} +function getterDecorator() { return (target: any, key: string) => {}; } +function setterDecorator() { return (target: any, key: string) => {}; } +function fieldDecorator() { return (target: any, key: string) => {}; } +function foo() { return 42; } + +@classDecorator() +export class A { + @methodDecorator() + sum(...args: number[]): number { + return args.reduce((a, b) => a + b, 0); + } + getSelf(): this { + return this; + } + passParameter(@parameterDecorator() param: number = foo()): void {} + @getterDecorator() + get a(): number { + return foo(); + } + @setterDecorator() + set a(value) {} + @fieldDecorator() classProp: number = foo(); +}` +}); From 5704f4e4c6fce4ad02a2838dd8ec97479c7561d9 Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Wed, 24 Apr 2024 15:43:08 +0000 Subject: [PATCH 05/14] Suggest a type annotation for widened unions of type literals such as "A"|"B" --> string in case the d.ts emitter widens it. Also expose 'getWidenedLiteralType' as an internal API for the fixer to be able to imitate what the type checker is doing. --- src/compiler/checker.ts | 1 + src/compiler/types.ts | 2 ++ .../fixMissingTypeAnnotationOnExports.ts | 18 +++++++++++++++++- ...otationOnExports36-conditional-releative.ts | 3 +++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0e45abf93737d..db28dc0df578c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1605,6 +1605,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getBaseTypes, getBaseTypeOfLiteralType, getWidenedType, + getWidenedLiteralType, getTypeFromTypeNode: nodeIn => { const node = getParseTreeNode(nodeIn, isTypeNode); return node ? getTypeFromTypeNode(node) : errorType; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3e2f35e491981..ac51237cbf684 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5006,6 +5006,8 @@ export interface TypeChecker { getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; /** @internal */ + getWidenedLiteralType(type: Type): Type; + /** @internal */ getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined; /** @internal */ getAwaitedType(type: Type): Type | undefined; diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index e9b7dd4d5c276..3b2ba4f332650 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -163,8 +163,13 @@ const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals | NodeBuilderFlags.WriteComputedProps; enum TypePrintMode { + // Prints it's fully spelled out type FULL, + // Prints a relative type i.e. typeof X RELATIVE, + // Prints a widened type in case the expression is known to + // e.g. export const a = Math.random() ? "0" : "1"; the type will be `string` in d.ts files + WIDENED, } registerCodeFix({ @@ -175,9 +180,11 @@ registerCodeFix({ addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.FULL, f => f.addTypeAnnotation(context.span)); addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.RELATIVE, f => f.addTypeAnnotation(context.span)); + addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.WIDENED, f => f.addTypeAnnotation(context.span)); addCodeAction(addInlineTypeAssertion, fixes, context, TypePrintMode.FULL, f => f.addInlineAssertion(context.span)); addCodeAction(addInlineTypeAssertion, fixes, context, TypePrintMode.RELATIVE, f => f.addInlineAssertion(context.span)); + addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.WIDENED, f => f.addInlineAssertion(context.span)); addCodeAction(extractExpression, fixes, context, TypePrintMode.FULL, f => f.extractAsVariable(context.span)); @@ -915,7 +922,7 @@ function withContext( } function inferType(node: Node): InferenceResult { - if (typePrintMode !== TypePrintMode.FULL) { + if (typePrintMode === TypePrintMode.RELATIVE) { return relativeType(node); } @@ -925,6 +932,15 @@ function withContext( if (!type) { return emptyInferenceResult; } + + if (typePrintMode === TypePrintMode.WIDENED) { + const widenedType = typeChecker.getWidenedLiteralType(type); + if (typeChecker.isTypeAssignableTo(widenedType, type)) { + return emptyInferenceResult; + } + type = widenedType; + } + if (isParameter(node) && emitResolver.requiresAddingImplicitUndefined(node)) { type = typeChecker.getUnionType([typeChecker.getUndefinedType(), type], UnionReduction.None); } diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts index eca8c213dadef..efdd051f52bc3 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts @@ -14,6 +14,9 @@ verify.codeFixAvailable([ { "description": "Add annotation of type 'typeof A | typeof B'" }, + { + "description": "Add annotation of type 'string'" + }, { "description": "Add satisfies and an inline type assertion with '\"A\" | \"B\"'" }, From cb45ffe1e3405ecea2bf38b8737163bbb5814fbb Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Fri, 26 Apr 2024 17:09:56 +0000 Subject: [PATCH 06/14] Call widening functions on the correct type when adding satisfies to expressions Widening of types only work on types from variable declaration, so when getWidenedLiteralType is being called on expressions the compiler wouldn't widen it. So call getWidenedLiteralType on the type of variables instead on those cases. --- .../codefixes/fixMissingTypeAnnotationOnExports.ts | 9 +++++++-- ...ingTypeAnnotationOnExports36-conditional-releative.ts | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index 3b2ba4f332650..4f5a9c66150f6 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -375,7 +375,7 @@ function withContext( if (!(isExpressionTarget || isShorthandPropertyAssignmentTarget)) return undefined; - const { typeNode, mutatedTarget } = inferType(targetNode); + const { typeNode, mutatedTarget } = inferType(targetNode, type); if (!typeNode || mutatedTarget) return undefined; if (isShorthandPropertyAssignmentTarget) { @@ -921,7 +921,7 @@ function withContext( mutatedTarget: boolean; } - function inferType(node: Node): InferenceResult { + function inferType(node: Node, variableType?: Type | undefined): InferenceResult { if (typePrintMode === TypePrintMode.RELATIVE) { return relativeType(node); } @@ -934,6 +934,11 @@ function withContext( } if (typePrintMode === TypePrintMode.WIDENED) { + if (variableType) { + type = variableType; + } + // Widening of types can happen on union of type literals on + // declaration emit so we query it. const widenedType = typeChecker.getWidenedLiteralType(type); if (typeChecker.isTypeAssignableTo(widenedType, type)) { return emptyInferenceResult; diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts index efdd051f52bc3..0158c20fc6997 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts @@ -22,11 +22,14 @@ verify.codeFixAvailable([ }, { "description": "Add satisfies and an inline type assertion with 'typeof A | typeof B'" + }, + { + "description": "Add satisfies and an inline type assertion with 'string'" } ]) verify.codeFix({ description: "Add satisfies and an inline type assertion with 'typeof A | typeof B'" , - index: 3, + index: 4, newFileContent: `const A = "A" const B = "B" From 00e507c13d69bd6d11a50ec7337be0a72d1f159a Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Fri, 26 Apr 2024 18:09:22 +0000 Subject: [PATCH 07/14] npm run format --- src/services/codefixes/fixMissingTypeAnnotationOnExports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index 4f5a9c66150f6..612276e469866 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -935,7 +935,7 @@ function withContext( if (typePrintMode === TypePrintMode.WIDENED) { if (variableType) { - type = variableType; + type = variableType; } // Widening of types can happen on union of type literals on // declaration emit so we query it. From 023d32ff3b86234789e2dea3100a38cdd02d9a69 Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Tue, 30 Apr 2024 14:25:00 +0000 Subject: [PATCH 08/14] Take into account stylish changes from PR comments --- .../fixMissingTypeAnnotationOnExports.ts | 69 ++++++++++--------- ...codeFixMissingTypeAnnotationOnExports10.ts | 4 +- ...ypeAnnotationOnExports44-default-export.ts | 6 +- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index 612276e469866..7350dac5e1187 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -163,13 +163,13 @@ const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals | NodeBuilderFlags.WriteComputedProps; enum TypePrintMode { - // Prints it's fully spelled out type - FULL, + // Prints its fully spelled out type + Full, // Prints a relative type i.e. typeof X - RELATIVE, + Relative, // Prints a widened type in case the expression is known to // e.g. export const a = Math.random() ? "0" : "1"; the type will be `string` in d.ts files - WIDENED, + Widened, } registerCodeFix({ @@ -178,20 +178,20 @@ registerCodeFix({ getCodeActions(context) { const fixes: CodeFixAction[] = []; - addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.FULL, f => f.addTypeAnnotation(context.span)); - addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.RELATIVE, f => f.addTypeAnnotation(context.span)); - addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.WIDENED, f => f.addTypeAnnotation(context.span)); + addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.Full, f => f.addTypeAnnotation(context.span)); + addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.Relative, f => f.addTypeAnnotation(context.span)); + addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.Widened, f => f.addTypeAnnotation(context.span)); - addCodeAction(addInlineTypeAssertion, fixes, context, TypePrintMode.FULL, f => f.addInlineAssertion(context.span)); - addCodeAction(addInlineTypeAssertion, fixes, context, TypePrintMode.RELATIVE, f => f.addInlineAssertion(context.span)); - addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.WIDENED, f => f.addInlineAssertion(context.span)); + addCodeAction(addInlineTypeAssertion, fixes, context, TypePrintMode.Full, f => f.addInlineAssertion(context.span)); + addCodeAction(addInlineTypeAssertion, fixes, context, TypePrintMode.Relative, f => f.addInlineAssertion(context.span)); + addCodeAction(addAnnotationFix, fixes, context, TypePrintMode.Widened, f => f.addInlineAssertion(context.span)); - addCodeAction(extractExpression, fixes, context, TypePrintMode.FULL, f => f.extractAsVariable(context.span)); + addCodeAction(extractExpression, fixes, context, TypePrintMode.Full, f => f.extractAsVariable(context.span)); return fixes; }, getAllCodeActions: context => { - const changes = withContext(context, TypePrintMode.FULL, f => { + const changes = withContext(context, TypePrintMode.Full, f => { eachDiagnostic(context, errorCodes, diag => { f.addTypeAnnotation(diag); }); @@ -282,7 +282,7 @@ function withContext( const newProperties = []; for (const symbol of elements) { // non-valid names will not end up in declaration emit - if (!isIdentifierText(symbol.name, program.getCompilerOptions().target)) continue; + if (!isIdentifierText(symbol.name, getEmitScriptTarget(program.getCompilerOptions()))) continue; // already has an existing declaration if (symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration)) continue; @@ -419,7 +419,7 @@ function withContext( if ((isVariableDeclaration(node) || isPropertyDeclaration(node)) && !isBindingPattern(node.name)) { addPropertyName(node.name); } - return nameParts.filter(s => isIdentifierText(s, program.getCompilerOptions().target)).reverse().join("_"); + return nameParts.filter(s => isIdentifierText(s, getEmitScriptTarget(program.getCompilerOptions()))).reverse().join("_"); function addPropertyName(name: PropertyName) { if (isIdentifier(name)) { @@ -623,12 +623,13 @@ function withContext( const { typeNode } = inferType(defaultExport.expression); if (!typeNode) return undefined; + const defaultIdentifier = factory.createUniqueName("_default"); changeTracker.replaceNodeWithNodes(sourceFile, defaultExport, [ factory.createVariableStatement( /*modifiers*/ undefined, factory.createVariableDeclarationList( [factory.createVariableDeclaration( - "__default", + defaultIdentifier, /*exclamationToken*/ undefined, typeNode, defaultExport.expression, @@ -636,7 +637,7 @@ function withContext( NodeFlags.Const, ), ), - factory.updateExportAssignment(defaultExport, defaultExport?.modifiers, factory.createIdentifier("__default")), + factory.updateExportAssignment(defaultExport, defaultExport?.modifiers, defaultIdentifier), ]); return [ Diagnostics.Extract_default_export_to_variable, @@ -701,17 +702,17 @@ function withContext( } const enum ExpressionType { - TEXT = 0, - COMPUTED = 1, - ARRAY_ACCESS = 2, - IDENTIFIER = 3, + Text = 0, + Computed = 1, + ArrayAccess = 2, + Identifier = 3, } type SubExpression = - | { kind: ExpressionType.TEXT; text: string; } - | { kind: ExpressionType.COMPUTED; computed: Expression; } - | { kind: ExpressionType.ARRAY_ACCESS; arrayIndex: number; } - | { kind: ExpressionType.IDENTIFIER; identifier: Identifier; }; + | { kind: ExpressionType.Text; text: string; } + | { kind: ExpressionType.Computed; computed: Expression; } + | { kind: ExpressionType.ArrayAccess; arrayIndex: number; } + | { kind: ExpressionType.Identifier; identifier: Identifier; }; function transformDestructuringPatterns(bindingPattern: BindingPattern): DiagnosticOrDiagnosticAndArguments | undefined { const enclosingVariableDeclaration = bindingPattern.parent as VariableDeclaration; @@ -723,7 +724,7 @@ function withContext( if (!isIdentifier(enclosingVariableDeclaration.initializer)) { // For complex expressions we want to create a temporary variable const tempHolderForReturn = factory.createUniqueName("dest", GeneratedIdentifierFlags.Optimistic); - baseExpr = { expression: { kind: ExpressionType.IDENTIFIER, identifier: tempHolderForReturn } }; + baseExpr = { expression: { kind: ExpressionType.Identifier, identifier: tempHolderForReturn } }; newNodes.push(factory.createVariableStatement( /*modifiers*/ undefined, factory.createVariableDeclarationList( @@ -739,7 +740,7 @@ function withContext( } else { // If we are destructuring an identifier, just use that. No need for temp var. - baseExpr = { expression: { kind: ExpressionType.IDENTIFIER, identifier: enclosingVariableDeclaration.initializer } }; + baseExpr = { expression: { kind: ExpressionType.Identifier, identifier: enclosingVariableDeclaration.initializer } }; } const bindingElements: ExpressionReverseChain[] = []; @@ -853,7 +854,7 @@ function withContext( bindingElements.push({ element, parent, - expression: { kind: ExpressionType.ARRAY_ACCESS, arrayIndex: i }, + expression: { kind: ExpressionType.ArrayAccess, arrayIndex: i }, }); } } @@ -866,7 +867,7 @@ function withContext( bindingElements.push({ element: bindingElement, parent, - expression: { kind: ExpressionType.COMPUTED, computed: bindingElement.propertyName.expression }, + expression: { kind: ExpressionType.Computed, computed: bindingElement.propertyName.expression }, }); continue; } @@ -880,7 +881,7 @@ function withContext( bindingElements.push({ element: bindingElement, parent, - expression: { kind: ExpressionType.TEXT, text: name }, + expression: { kind: ExpressionType.Text, text: name }, }); } } @@ -894,20 +895,20 @@ function withContext( let chainedExpression: Expression = (reverseTraverse[reverseTraverse.length - 1].expression as { identifier: Identifier; }).identifier; for (let i = reverseTraverse.length - 2; i >= 0; --i) { const nextSubExpr = reverseTraverse[i].expression; - if (nextSubExpr.kind === ExpressionType.TEXT) { + if (nextSubExpr.kind === ExpressionType.Text) { chainedExpression = factory.createPropertyAccessChain( chainedExpression, /*questionDotToken*/ undefined, factory.createIdentifier(nextSubExpr.text), ); } - else if (nextSubExpr.kind === ExpressionType.COMPUTED) { + else if (nextSubExpr.kind === ExpressionType.Computed) { chainedExpression = factory.createElementAccessExpression( chainedExpression, expressionToVar.get(nextSubExpr.computed)!, ); } - else if (nextSubExpr.kind === ExpressionType.ARRAY_ACCESS) { + else if (nextSubExpr.kind === ExpressionType.ArrayAccess) { chainedExpression = factory.createElementAccessExpression( chainedExpression, nextSubExpr.arrayIndex, @@ -922,7 +923,7 @@ function withContext( } function inferType(node: Node, variableType?: Type | undefined): InferenceResult { - if (typePrintMode === TypePrintMode.RELATIVE) { + if (typePrintMode === TypePrintMode.Relative) { return relativeType(node); } @@ -933,7 +934,7 @@ function withContext( return emptyInferenceResult; } - if (typePrintMode === TypePrintMode.WIDENED) { + if (typePrintMode === TypePrintMode.Widened) { if (variableType) { type = variableType; } diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports10.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports10.ts index 83e626e81e15f..3b00247f4c0de 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports10.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports10.ts @@ -14,9 +14,9 @@ verify.codeFix({ `function foo() { return { x: 1, y: 1 }; } -const __default: { +const _default_1: { x: number; y: number; } = foo(); -export default __default;`, +export default _default_1;`, }); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports44-default-export.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports44-default-export.ts index 565534de23766..3a05faee7cd57 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports44-default-export.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports44-default-export.ts @@ -11,6 +11,6 @@ verify.codeFix({ description: "Extract default export to variable", index: 0, newFileContent: -`const __default: number = 1 + 1; -export default __default;` -}); \ No newline at end of file +`const _default_1: number = 1 + 1; +export default _default_1;` +}); From 38f6a3d89f379d273cf38ba7cd1abfa1a9b6b92c Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Tue, 30 Apr 2024 16:21:37 +0000 Subject: [PATCH 09/14] Factor out a functionality to generate a variable name and reuse it. Remove suggestVariableName as this adds very little value compared to the code size it had. --- .../fixMissingTypeAnnotationOnExports.ts | 64 +------------------ src/services/refactors/extractSymbol.ts | 6 +- src/services/refactors/helpers.ts | 27 ++++++++ ...tationOnExports31-inline-import-default.ts | 2 +- ...tionOnExports39-extract-arr-to-variable.ts | 12 ++-- ...onOnExports40-extract-other-to-variable.ts | 12 ++-- 6 files changed, 45 insertions(+), 78 deletions(-) diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index 7350dac5e1187..c7d3262aca00e 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -109,6 +109,7 @@ import { registerCodeFix, typeToAutoImportableTypeNode, } from "../_namespaces/ts.codefix"; +import { getIdentifierForNode } from "../refactors/helpers"; const fixId = "fixMissingTypeAnnotationOnExports"; @@ -407,66 +408,6 @@ function withContext( return [Diagnostics.Add_satisfies_and_an_inline_type_assertion_with_0, typeToStringForDiag(typeNode)]; } - function suggestVariableName(node: Node) { - const nameParts: string[] = []; - while (!(isVariableDeclaration(node) || isPropertyDeclaration(node) || isStatement(node))) { - if (isPropertyAssignment(node)) { - const propName = node.name; - addPropertyName(propName); - } - node = node.parent; - } - if ((isVariableDeclaration(node) || isPropertyDeclaration(node)) && !isBindingPattern(node.name)) { - addPropertyName(node.name); - } - return nameParts.filter(s => isIdentifierText(s, getEmitScriptTarget(program.getCompilerOptions()))).reverse().join("_"); - - function addPropertyName(name: PropertyName) { - if (isIdentifier(name)) { - nameParts.push(name.text); - } - if (isStringLiteral(name)) { - nameParts.push(name.text); - } - if (isNumericLiteral(name)) { - nameParts.push(name.text); - } - if (isComputedPropertyName(name)) { - let computedName = name.expression; - - if (isStringLiteral(computedName)) { - nameParts.push(computedName.text); - } - if (isNumericLiteral(computedName)) { - nameParts.push(computedName.text); - } - if ( - isPrefixUnaryExpression(computedName) - && isNumericLiteral(computedName.operand) - ) { - if (computedName.operator === SyntaxKind.MinusToken) { - nameParts.push("M" + computedName.operand.text); - } - else if (computedName.operator === SyntaxKind.PlusToken) { - nameParts.push("M" + computedName.operand.text); - } - } - - // We only support dotted identifiers as property keys - while (true) { - if (isIdentifier(computedName)) { - nameParts.push(computedName.text); - break; - } - else if (isPropertyAccessExpression(computedName)) { - nameParts.push(computedName.name.text); - computedName = computedName.expression; - } - } - } - } - } - function extractAsVariable(span: TextSpan): DiagnosticOrDiagnosticAndArguments | undefined { const nodeWithDiag = getTokenAtPosition(sourceFile, span.start); const targetNode = findBestFittingNode(nodeWithDiag, span) as Expression; @@ -493,7 +434,7 @@ function withContext( if (parentPropertyAssignment === targetNode.parent && isEntityNameExpression(targetNode)) return; const tempName = factory.createUniqueName( - suggestVariableName(targetNode), + getIdentifierForNode(targetNode, sourceFile, typeChecker, sourceFile), GeneratedIdentifierFlags.Optimistic, ); let replacementTarget = targetNode; @@ -917,6 +858,7 @@ function withContext( } return chainedExpression; } + interface InferenceResult { typeNode?: TypeNode | undefined; mutatedTarget: boolean; diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 484112be14421..50e3a82484c0e 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -61,7 +61,6 @@ import { hasEffectiveModifier, hasSyntacticModifier, Identifier, - identifierToKeywordKind, isArray, isArrowFunction, isAssignmentExpression, @@ -161,6 +160,7 @@ import { VisitResult, } from "../_namespaces/ts"; import { + getIdentifierForNode, refactorKindBeginsWith, registerRefactor, } from "../_namespaces/ts.refactor"; @@ -1374,9 +1374,7 @@ function extractConstantInScope( // Make a unique name for the extracted variable const file = scope.getSourceFile(); - const localNameText = isPropertyAccessExpression(node) && !isClassLike(scope) && !checker.resolveName(node.name.text, node, SymbolFlags.Value, /*excludeGlobals*/ false) && !isPrivateIdentifier(node.name) && !identifierToKeywordKind(node.name) - ? node.name.text - : getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file); + const localNameText = getIdentifierForNode(node, scope, checker, file); const isJS = isInJSFile(scope); let variableType = isJS || !checker.isContextSensitive(node) diff --git a/src/services/refactors/helpers.ts b/src/services/refactors/helpers.ts index ce50f672d23b8..39184ddf554a5 100644 --- a/src/services/refactors/helpers.ts +++ b/src/services/refactors/helpers.ts @@ -1,3 +1,18 @@ +import { + ClassLikeDeclaration, + FunctionLikeDeclaration, + getUniqueName, + identifierToKeywordKind, + isClassLike, + isPrivateIdentifier, + isPropertyAccessExpression, + ModuleBlock, + Node, + SourceFile, + SymbolFlags, + TypeChecker, +} from "../_namespaces/ts"; + /** * Returned by refactor functions when some error message needs to be surfaced to users. * @@ -26,3 +41,15 @@ export function refactorKindBeginsWith(known: string, requested: string | undefi if (!requested) return true; return known.substr(0, requested.length) === requested; } + +/** + * Try to come up with a unique name for a given node within the scope for the + * use of being used as a property/variable name. + * + * @internal + */ +export function getIdentifierForNode(node: Node, scope: FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration, checker: TypeChecker, file: SourceFile) { + return isPropertyAccessExpression(node) && !isClassLike(scope) && !checker.resolveName(node.name.text, node, SymbolFlags.Value, /*excludeGlobals*/ false) && !isPrivateIdentifier(node.name) && !identifierToKeywordKind(node.name) + ? node.name.text + : getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file); +} diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts index a7c5789d9afa3..7efcf89138b64 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts @@ -24,7 +24,7 @@ verify.codeFixAvailable([ "description": "Add satisfies and an inline type assertion with 'Person'" }, { - "description": "Extract to variable and replace with 'person_1 typeof person_1'" + "description": "Extract to variable and replace with 'newLocal typeof newLocal'" } ]) diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts index d17e16706a8cb..08852dbf471c7 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts @@ -26,16 +26,16 @@ export let o = { }); verify.codeFix({ - description: `Extract to variable and replace with 'o_p typeof o_p'`, + description: `Extract to variable and replace with 'newLocal typeof newLocal'`, applyChanges: true, index: 1, newFileContent: `let c: string[] = []; -const o_p = [ +const newLocal = [ ...c ] as const; export let o = { - p: o_p as typeof o_p + p: newLocal as typeof newLocal }` }); @@ -45,10 +45,10 @@ verify.codeFix({ index: 0, newFileContent: `let c: string[] = []; -const o_p: readonly string[] = [ +const newLocal: readonly string[] = [ ...c ] as const; export let o = { - p: o_p as typeof o_p + p: newLocal as typeof newLocal }` -}); \ No newline at end of file +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts index 7b4c87a1363dd..81e94beb4ac83 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts @@ -13,16 +13,16 @@ ////} verify.codeFix({ - description: `Extract to variable and replace with 'o_p typeof o_p'`, + description: `Extract to variable and replace with 'newLocal typeof newLocal'`, applyChanges: true, index: 2, newFileContent: `let c: string[] = []; -const o_p = Math.random() ? [] : [ +const newLocal = Math.random() ? [] : [ ...c ]; export let o = { - p: o_p as typeof o_p + p: newLocal as typeof newLocal }` }); @@ -33,10 +33,10 @@ verify.codeFix({ index: 0, newFileContent: `let c: string[] = []; -const o_p: string[] = Math.random() ? [] : [ +const newLocal: string[] = Math.random() ? [] : [ ...c ]; export let o = { - p: o_p as typeof o_p + p: newLocal as typeof newLocal }` -}); \ No newline at end of file +}); From 2086cb9ef2b389ed5e4e96d16ec3b5e5eccb63ba Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Tue, 30 Apr 2024 19:05:33 +0200 Subject: [PATCH 10/14] Update src/compiler/diagnosticMessages.json Co-authored-by: Andrew Branch --- src/compiler/diagnosticMessages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 907ab34c9597c..371a9b63b3623 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7212,7 +7212,7 @@ "category": "Message", "code": 90068 }, - "Extract to variable and replace with '{0} typeof {0}'": { + "Extract to variable and replace with '{0} as typeof {0}'": { "category": "Message", "code": 90069 }, From 03cc990e8d0fe4225010e116133ff9cccc2215dd Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Tue, 30 Apr 2024 17:36:48 +0000 Subject: [PATCH 11/14] Update tests --- src/services/codefixes/fixMissingTypeAnnotationOnExports.ts | 2 +- ...FixMissingTypeAnnotationOnExports31-inline-import-default.ts | 2 +- ...xMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts | 2 +- ...issingTypeAnnotationOnExports40-extract-other-to-variable.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index c7d3262aca00e..f74eb328d8bbd 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -479,7 +479,7 @@ function withContext( ), ), ); - return [Diagnostics.Extract_to_variable_and_replace_with_0_typeof_0, typeToStringForDiag(tempName)]; + return [Diagnostics.Extract_to_variable_and_replace_with_0_as_typeof_0, typeToStringForDiag(tempName)]; } } diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts index 7efcf89138b64..fde537a48c805 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports31-inline-import-default.ts @@ -24,7 +24,7 @@ verify.codeFixAvailable([ "description": "Add satisfies and an inline type assertion with 'Person'" }, { - "description": "Extract to variable and replace with 'newLocal typeof newLocal'" + "description": "Extract to variable and replace with 'newLocal as typeof newLocal'" } ]) diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts index 08852dbf471c7..26b719b707ed9 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports39-extract-arr-to-variable.ts @@ -26,7 +26,7 @@ export let o = { }); verify.codeFix({ - description: `Extract to variable and replace with 'newLocal typeof newLocal'`, + description: `Extract to variable and replace with 'newLocal as typeof newLocal'`, applyChanges: true, index: 1, newFileContent: diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts index 81e94beb4ac83..1b37daceb20dc 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports40-extract-other-to-variable.ts @@ -13,7 +13,7 @@ ////} verify.codeFix({ - description: `Extract to variable and replace with 'newLocal typeof newLocal'`, + description: `Extract to variable and replace with 'newLocal as typeof newLocal'`, applyChanges: true, index: 2, newFileContent: From c4f0b8112750386dbbb2da36fe194c8d21ec4ff5 Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Tue, 30 Apr 2024 17:48:20 +0000 Subject: [PATCH 12/14] Remove unused imports, also use getEmitDeclarations to check to provide fixes for them --- src/services/codeFixProvider.ts | 3 ++- src/services/codefixes/fixMissingTypeAnnotationOnExports.ts | 5 ----- src/services/refactors/extractSymbol.ts | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 764a6dbd6426c..2e2282afbc6e1 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -18,6 +18,7 @@ import { DiagnosticWithLocation, FileTextChanges, flatMap, + getEmitDeclarations, isString, map, TextChange, @@ -129,7 +130,7 @@ function getDiagnostics({ program, sourceFile, cancellationToken }: CodeFixConte ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), ...computeSuggestionDiagnostics(sourceFile, program, cancellationToken), ]; - if (program.getCompilerOptions().isolatedDeclarations) { + if (getEmitDeclarations(program.getCompilerOptions())) { diagnostics.push( ...program.getDeclarationDiagnostics(sourceFile, cancellationToken), ); diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index f74eb328d8bbd..9abf1758b64d2 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -55,20 +55,16 @@ import { isHeritageClause, isIdentifier, isIdentifierText, - isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isOmittedExpression, isParameter, - isPrefixUnaryExpression, - isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStatement, - isStringLiteral, isValueSignatureDeclaration, isVariableDeclaration, ModifierFlags, @@ -81,7 +77,6 @@ import { ParameterDeclaration, PropertyAccessExpression, PropertyDeclaration, - PropertyName, setEmitFlags, SignatureDeclaration, some, diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 50e3a82484c0e..9e04d91d10d8e 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -91,7 +91,6 @@ import { isModuleBlock, isParenthesizedTypeNode, isPartOfTypeNode, - isPrivateIdentifier, isPropertyAccessExpression, isPropertyDeclaration, isQualifiedName, From 48ed32bd0d8f97bfe73a8a6c4b2c73449a343854 Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Tue, 30 Apr 2024 18:08:23 +0000 Subject: [PATCH 13/14] Fix the mis-resolution of merge conflict --- src/services/refactors/helpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/refactors/helpers.ts b/src/services/refactors/helpers.ts index 1a4e4e82e9fe8..e29af188cc543 100644 --- a/src/services/refactors/helpers.ts +++ b/src/services/refactors/helpers.ts @@ -1,4 +1,5 @@ import { + ClassLikeDeclaration, codefix, Debug, findAncestor, @@ -58,7 +59,8 @@ export function getIdentifierForNode(node: Node, scope: FunctionLikeDeclaration return isPropertyAccessExpression(node) && !isClassLike(scope) && !checker.resolveName(node.name.text, node, SymbolFlags.Value, /*excludeGlobals*/ false) && !isPrivateIdentifier(node.name) && !identifierToKeywordKind(node.name) ? node.name.text : getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file); - +} + /** @internal */ export function addTargetFileImports( oldFile: SourceFile, From 16324894eac7fa20f8c1cc8b58e53030cf9fabd7 Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Tue, 30 Apr 2024 18:27:42 +0000 Subject: [PATCH 14/14] npm run format --- src/services/refactors/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/refactors/helpers.ts b/src/services/refactors/helpers.ts index e29af188cc543..e72eea44693a6 100644 --- a/src/services/refactors/helpers.ts +++ b/src/services/refactors/helpers.ts @@ -6,8 +6,8 @@ import { FunctionLikeDeclaration, getUniqueName, identifierToKeywordKind, - isClassLike, isAnyImportOrRequireStatement, + isClassLike, isPrivateIdentifier, isPropertyAccessExpression, ModuleBlock,