diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 53e9c4387974c..ef644044c52d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1611,6 +1611,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/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 25fda5c8ce0b6..1db3b8c70bc30 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7336,6 +7336,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} as 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/compiler/types.ts b/src/compiler/types.ts index f22978b464898..9197a83415663 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5022,6 +5022,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/_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..2e2282afbc6e1 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -18,6 +18,7 @@ import { DiagnosticWithLocation, FileTextChanges, flatMap, + getEmitDeclarations, isString, map, TextChange, @@ -124,9 +125,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), ...computeSuggestionDiagnostics(sourceFile, program, cancellationToken), ]; + if (getEmitDeclarations(program.getCompilerOptions())) { + diagnostics.push( + ...program.getDeclarationDiagnostics(sourceFile, cancellationToken), + ); + } + return diagnostics; } diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts new file mode 100644 index 0000000000000..9abf1758b64d2 --- /dev/null +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -0,0 +1,1130 @@ +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, + isObjectBindingPattern, + isObjectLiteralExpression, + isOmittedExpression, + isParameter, + isPropertyAssignment, + isPropertyDeclaration, + isShorthandPropertyAssignment, + isSpreadAssignment, + isSpreadElement, + isStatement, + isValueSignatureDeclaration, + isVariableDeclaration, + ModifierFlags, + ModifierLike, + Node, + NodeBuilderFlags, + NodeFlags, + ObjectBindingPattern, + ObjectLiteralExpression, + ParameterDeclaration, + PropertyAccessExpression, + PropertyDeclaration, + 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"; +import { getIdentifierForNode } from "../refactors/helpers"; + +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 { + // Prints its 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({ + 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(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)); + + 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, getEmitScriptTarget(program.getCompilerOptions()))) 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, type); + 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 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( + getIdentifierForNode(targetNode, sourceFile, typeChecker, sourceFile), + 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_as_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; + const defaultIdentifier = factory.createUniqueName("_default"); + changeTracker.replaceNodeWithNodes(sourceFile, defaultExport, [ + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + defaultIdentifier, + /*exclamationToken*/ undefined, + typeNode, + defaultExport.expression, + )], + NodeFlags.Const, + ), + ), + factory.updateExportAssignment(defaultExport, defaultExport?.modifiers, defaultIdentifier), + ]); + 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, + ArrayAccess = 2, + Identifier = 3, + } + + type SubExpression = + | { 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; + 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.ArrayAccess, 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.ArrayAccess) { + chainedExpression = factory.createElementAccessExpression( + chainedExpression, + nextSubExpr.arrayIndex, + ); + } + } + return chainedExpression; + } + + interface InferenceResult { + typeNode?: TypeNode | undefined; + mutatedTarget: boolean; + } + + function inferType(node: Node, variableType?: Type | undefined): InferenceResult { + if (typePrintMode === TypePrintMode.Relative) { + return relativeType(node); + } + + let type = isValueSignatureDeclaration(node) ? + tryGetReturnType(node) : + typeChecker.getTypeAtLocation(node); + if (!type) { + return emptyInferenceResult; + } + + 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; + } + type = widenedType; + } + + 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/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 484112be14421..9e04d91d10d8e 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, @@ -92,7 +91,6 @@ import { isModuleBlock, isParenthesizedTypeNode, isPartOfTypeNode, - isPrivateIdentifier, isPropertyAccessExpression, isPropertyDeclaration, isQualifiedName, @@ -161,6 +159,7 @@ import { VisitResult, } from "../_namespaces/ts"; import { + getIdentifierForNode, refactorKindBeginsWith, registerRefactor, } from "../_namespaces/ts.refactor"; @@ -1374,9 +1373,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 51ef3895c7816..e72eea44693a6 100644 --- a/src/services/refactors/helpers.ts +++ b/src/services/refactors/helpers.ts @@ -1,12 +1,22 @@ import { + ClassLikeDeclaration, codefix, Debug, findAncestor, + FunctionLikeDeclaration, + getUniqueName, + identifierToKeywordKind, isAnyImportOrRequireStatement, + isClassLike, + isPrivateIdentifier, + isPropertyAccessExpression, + ModuleBlock, + Node, Program, skipAlias, SourceFile, Symbol, + SymbolFlags, TypeChecker, } from "../_namespaces/ts"; import { addImportsForMovedSymbols } from "./moveToFile"; @@ -39,6 +49,18 @@ export function refactorKindBeginsWith(known: string, requested: string | undefi 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); +} + /** @internal */ export function addTargetFileImports( oldFile: SourceFile, 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..3b00247f4c0de --- /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_1: { + x: number; + y: number; +} = foo(); +export default _default_1;`, +}); 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..fde537a48c805 --- /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 'newLocal as typeof newLocal'" + } +]) + +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..0158c20fc6997 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports36-conditional-releative.ts @@ -0,0 +1,37 @@ +/// + +// @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 annotation of type 'string'" + }, + { + "description": "Add satisfies and an inline type assertion with '\"A\" | \"B\"'" + }, + { + "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: 4, + 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..26b719b707ed9 --- /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 'newLocal as typeof newLocal'`, + applyChanges: true, + index: 1, + newFileContent: +`let c: string[] = []; +const newLocal = [ + ...c +] as const; +export let o = { + p: newLocal as typeof newLocal +}` +}); + +verify.codeFix({ + description: `Add annotation of type 'readonly string[]'`, + applyChanges: true, + index: 0, + newFileContent: +`let c: string[] = []; +const newLocal: readonly string[] = [ + ...c +] as const; +export let o = { + p: newLocal as typeof newLocal +}` +}); 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..1b37daceb20dc --- /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 'newLocal as typeof newLocal'`, + applyChanges: true, + index: 2, + newFileContent: +`let c: string[] = []; +const newLocal = Math.random() ? [] : [ + ...c +]; +export let o = { + p: newLocal as typeof newLocal +}` +}); + + +verify.codeFix({ + description: `Add annotation of type 'string[]'`, + applyChanges: true, + index: 0, + newFileContent: +`let c: string[] = []; +const newLocal: string[] = Math.random() ? [] : [ + ...c +]; +export let o = { + p: newLocal as typeof newLocal +}` +}); 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..3a05faee7cd57 --- /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_1: number = 1 + 1; +export default _default_1;` +}); 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(); +}` +}); 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;`, +}); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy