diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 37f12224c75fa..e04adaae100a8 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6171,6 +6171,23 @@ "category": "Message", "code": 95155 }, + "Add missing function declaration '{0}'": { + "category": "Message", + "code": 95156 + }, + "Add all missing function declarations": { + "category": "Message", + "code": 95157 + }, + "Method not implemented.": { + "category": "Message", + "code": 95158 + }, + "Function not implemented.": { + "category": "Message", + "code": 95159 + }, + "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 28bc1da2d6bdc..3e696435aa967 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -1,34 +1,39 @@ /* @internal */ namespace ts.codefix { - const fixName = "addMissingMember"; + const fixMissingMember = "fixMissingMember"; + const fixMissingFunctionDeclaration = "fixMissingFunctionDeclaration"; const errorCodes = [ Diagnostics.Property_0_does_not_exist_on_type_1.code, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2.code, Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2.code, - Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more.code + Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more.code, + Diagnostics.Cannot_find_name_0.code ]; - const fixId = "addMissingMember"; + registerCodeFix({ errorCodes, getCodeActions(context) { - const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker(), context.program); + const typeChecker = context.program.getTypeChecker(); + const info = getInfo(context.sourceFile, context.span.start, typeChecker, context.program); if (!info) { return undefined; } + if (info.kind === InfoKind.Function) { + const changes = textChanges.ChangeTracker.with(context, t => addFunctionDeclaration(t, context, info)); + return [createCodeFixAction(fixMissingFunctionDeclaration, changes, [Diagnostics.Add_missing_function_declaration_0, info.token.text], fixMissingFunctionDeclaration, Diagnostics.Add_all_missing_function_declarations)]; + } if (info.kind === InfoKind.Enum) { - const { token, parentDeclaration } = info; - const changes = textChanges.ChangeTracker.with(context, t => addEnumMemberDeclaration(t, context.program.getTypeChecker(), token, parentDeclaration)); - return [createCodeFixAction(fixName, changes, [Diagnostics.Add_missing_enum_member_0, token.text], fixId, Diagnostics.Add_all_missing_members)]; + const changes = textChanges.ChangeTracker.with(context, t => addEnumMemberDeclaration(t, context.program.getTypeChecker(), info)); + return [createCodeFixAction(fixMissingMember, changes, [Diagnostics.Add_missing_enum_member_0, info.token.text], fixMissingMember, Diagnostics.Add_all_missing_members)]; } return concatenate(getActionsForMissingMethodDeclaration(context, info), getActionsForMissingMemberDeclaration(context, info)); }, - fixIds: [fixId], + fixIds: [fixMissingMember, fixMissingFunctionDeclaration], getAllCodeActions: context => { - const { program } = context; + const { program, fixId } = context; const checker = program.getTypeChecker(); const seen = new Map(); - const typeDeclToMembers = new Map(); return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { @@ -38,14 +43,23 @@ namespace ts.codefix { return; } - if (info.kind === InfoKind.Enum) { - const { token, parentDeclaration } = info; - addEnumMemberDeclaration(changes, checker, token, parentDeclaration); + if (fixId === fixMissingFunctionDeclaration) { + if (info.kind === InfoKind.Function) { + addFunctionDeclaration(changes, context, info); + } } else { - const { parentDeclaration, token } = info; - const infos = getOrUpdate(typeDeclToMembers, parentDeclaration, () => []); - if (!infos.some(i => i.token.text === token.text)) infos.push(info); + if (info.kind === InfoKind.Enum) { + addEnumMemberDeclaration(changes, checker, info); + } + + if (info.kind === InfoKind.ClassOrInterface) { + const { parentDeclaration, token } = info; + const infos = getOrUpdate(typeDeclToMembers, parentDeclaration, () => []); + if (!infos.some(i => i.token.text === token.text)) { + infos.push(info); + } + } } }); @@ -61,7 +75,7 @@ namespace ts.codefix { const { parentDeclaration, declSourceFile, modifierFlags, token, call, isJSFile } = info; // Always prefer to add a method declaration if possible. if (call && !isPrivateIdentifier(token)) { - addMethodDeclaration(context, changes, call, token.text, modifierFlags & ModifierFlags.Static, parentDeclaration, declSourceFile, isJSFile); + addMethodDeclaration(context, changes, call, token, modifierFlags & ModifierFlags.Static, parentDeclaration, declSourceFile); } else { if (isJSFile && !isInterfaceDeclaration(parentDeclaration)) { @@ -78,12 +92,15 @@ namespace ts.codefix { }, }); - const enum InfoKind { Enum, ClassOrInterface } + const enum InfoKind { Enum, ClassOrInterface, Function } + type Info = EnumInfo | ClassOrInterfaceInfo | FunctionInfo; + interface EnumInfo { readonly kind: InfoKind.Enum; readonly token: Identifier; readonly parentDeclaration: EnumDeclaration; } + interface ClassOrInterfaceInfo { readonly kind: InfoKind.ClassOrInterface; readonly call: CallExpression | undefined; @@ -93,23 +110,56 @@ namespace ts.codefix { readonly declSourceFile: SourceFile; readonly isJSFile: boolean; } - type Info = EnumInfo | ClassOrInterfaceInfo; - function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker, program: Program): Info | undefined { + interface FunctionInfo { + readonly kind: InfoKind.Function; + readonly call: CallExpression; + readonly token: Identifier; + readonly sourceFile: SourceFile; + readonly modifierFlags: ModifierFlags; + readonly parentDeclaration: SourceFile | ModuleDeclaration; + } + + function getInfo(sourceFile: SourceFile, tokenPos: number, checker: TypeChecker, program: Program): Info | undefined { // The identifier of the missing property. eg: // this.missing = 1; // ^^^^^^^ - const token = getTokenAtPosition(tokenSourceFile, tokenPos); + const token = getTokenAtPosition(sourceFile, tokenPos); if (!isIdentifier(token) && !isPrivateIdentifier(token)) { return undefined; } const { parent } = token; - if (!isPropertyAccessExpression(parent)) return undefined; + if (isIdentifier(token) && isCallExpression(parent)) { + return { kind: InfoKind.Function, token, call: parent, sourceFile, modifierFlags: ModifierFlags.None, parentDeclaration: sourceFile }; + } + + if (!isPropertyAccessExpression(parent)) { + return undefined; + } const leftExpressionType = skipConstraint(checker.getTypeAtLocation(parent.expression)); const { symbol } = leftExpressionType; - if (!symbol || !symbol.declarations) return undefined; + if (!symbol || !symbol.declarations) { + return undefined; + } + + if (isIdentifier(token) && isCallExpression(parent.parent)) { + const moduleDeclaration = find(symbol.declarations, isModuleDeclaration); + const moduleDeclarationSourceFile = moduleDeclaration?.getSourceFile(); + if (moduleDeclaration && moduleDeclarationSourceFile && !program.isSourceFileFromExternalLibrary(moduleDeclarationSourceFile)) { + return { kind: InfoKind.Function, token, call: parent.parent, sourceFile, modifierFlags: ModifierFlags.Export, parentDeclaration: moduleDeclaration }; + } + + const moduleSourceFile = find(symbol.declarations, isSourceFile); + if (sourceFile.commonJsModuleIndicator) { + return; + } + + if (moduleSourceFile && !program.isSourceFileFromExternalLibrary(moduleSourceFile)) { + return { kind: InfoKind.Function, token, call: parent.parent, sourceFile: moduleSourceFile, modifierFlags: ModifierFlags.Export, parentDeclaration: moduleSourceFile }; + } + } const classDeclaration = find(symbol.declarations, isClassLike); // Don't suggest adding private identifiers to anything other than a class. @@ -157,7 +207,7 @@ namespace ts.codefix { const diagnostic = modifierFlags & ModifierFlags.Static ? Diagnostics.Initialize_static_property_0 : isPrivateIdentifier(token) ? Diagnostics.Declare_a_private_field_named_0 : Diagnostics.Initialize_property_0_in_the_constructor; - return createCodeFixAction(fixName, changes, [diagnostic, token.text], fixId, Diagnostics.Add_all_missing_members); + return createCodeFixAction(fixMissingMember, changes, [diagnostic, token.text], fixMissingMember, Diagnostics.Add_all_missing_members); } function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier | PrivateIdentifier, makeStatic: boolean): void { @@ -207,13 +257,13 @@ namespace ts.codefix { const typeNode = getTypeNode(context.program.getTypeChecker(), parentDeclaration, token); const addPropertyDeclarationChanges = (modifierFlags: ModifierFlags) => textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, declSourceFile, parentDeclaration, memberName, typeNode, modifierFlags)); - const actions = [createCodeFixAction(fixName, addPropertyDeclarationChanges(modifierFlags & ModifierFlags.Static), [isStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0, memberName], fixId, Diagnostics.Add_all_missing_members)]; + const actions = [createCodeFixAction(fixMissingMember, addPropertyDeclarationChanges(modifierFlags & ModifierFlags.Static), [isStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0, memberName], fixMissingMember, Diagnostics.Add_all_missing_members)]; if (isStatic || isPrivateIdentifier(token)) { return actions; } if (modifierFlags & ModifierFlags.Private) { - actions.unshift(createCodeFixActionWithoutFixAll(fixName, addPropertyDeclarationChanges(ModifierFlags.Private), [Diagnostics.Declare_private_property_0, memberName])); + actions.unshift(createCodeFixActionWithoutFixAll(fixMissingMember, addPropertyDeclarationChanges(ModifierFlags.Private), [Diagnostics.Declare_private_property_0, memberName])); } actions.push(createAddIndexSignatureAction(context, declSourceFile, parentDeclaration, token.text, typeNode)); @@ -282,11 +332,11 @@ namespace ts.codefix { const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(declSourceFile, classDeclaration, indexSignature)); // No fixId here because code-fix-all currently only works on adding individual named properties. - return createCodeFixActionWithoutFixAll(fixName, changes, [Diagnostics.Add_index_signature_for_property_0, tokenName]); + return createCodeFixActionWithoutFixAll(fixMissingMember, changes, [Diagnostics.Add_index_signature_for_property_0, tokenName]); } function getActionsForMissingMethodDeclaration(context: CodeFixContext, info: ClassOrInterfaceInfo): CodeFixAction[] | undefined { - const { parentDeclaration, declSourceFile, modifierFlags, token, call, isJSFile } = info; + const { parentDeclaration, declSourceFile, modifierFlags, token, call } = info; if (call === undefined) { return undefined; } @@ -297,10 +347,10 @@ namespace ts.codefix { } const methodName = token.text; - const addMethodDeclarationChanges = (modifierFlags: ModifierFlags) => textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, call, methodName, modifierFlags, parentDeclaration, declSourceFile, isJSFile)); - const actions = [createCodeFixAction(fixName, addMethodDeclarationChanges(modifierFlags & ModifierFlags.Static), [modifierFlags & ModifierFlags.Static ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, methodName], fixId, Diagnostics.Add_all_missing_members)]; + const addMethodDeclarationChanges = (modifierFlags: ModifierFlags) => textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, call, token, modifierFlags, parentDeclaration, declSourceFile)); + const actions = [createCodeFixAction(fixMissingMember, addMethodDeclarationChanges(modifierFlags & ModifierFlags.Static), [modifierFlags & ModifierFlags.Static ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, methodName], fixMissingMember, Diagnostics.Add_all_missing_members)]; if (modifierFlags & ModifierFlags.Private) { - actions.unshift(createCodeFixActionWithoutFixAll(fixName, addMethodDeclarationChanges(ModifierFlags.Private), [Diagnostics.Declare_private_method_0, methodName])); + actions.unshift(createCodeFixActionWithoutFixAll(fixMissingMember, addMethodDeclarationChanges(ModifierFlags.Private), [Diagnostics.Declare_private_method_0, methodName])); } return actions; } @@ -309,14 +359,13 @@ namespace ts.codefix { context: CodeFixContextBase, changes: textChanges.ChangeTracker, callExpression: CallExpression, - methodName: string, + name: Identifier, modifierFlags: ModifierFlags, parentDeclaration: ClassOrInterface, sourceFile: SourceFile, - isJSFile: boolean ): void { const importAdder = createImportAdder(sourceFile, context.program, context.preferences, context.host); - const methodDeclaration = createMethodFromCallExpression(context, importAdder, callExpression, methodName, modifierFlags, parentDeclaration, isJSFile); + const methodDeclaration = createSignatureDeclarationFromCallExpression(SyntaxKind.MethodDeclaration, context, importAdder, callExpression, name, modifierFlags, parentDeclaration) as MethodDeclaration; const containingMethodDeclaration = findAncestor(callExpression, n => isMethodDeclaration(n) || isConstructorDeclaration(n)); if (containingMethodDeclaration && containingMethodDeclaration.parent === parentDeclaration) { changes.insertNodeAfter(sourceFile, containingMethodDeclaration, methodDeclaration); @@ -327,27 +376,33 @@ namespace ts.codefix { importAdder.writeFixes(changes); } - function addEnumMemberDeclaration(changes: textChanges.ChangeTracker, checker: TypeChecker, token: Identifier, enumDeclaration: EnumDeclaration) { + function addEnumMemberDeclaration(changes: textChanges.ChangeTracker, checker: TypeChecker, { token, parentDeclaration }: EnumInfo) { /** * create initializer only literal enum that has string initializer. * value of initializer is a string literal that equal to name of enum member. * numeric enum or empty enum will not create initializer. */ - const hasStringInitializer = some(enumDeclaration.members, member => { + const hasStringInitializer = some(parentDeclaration.members, member => { const type = checker.getTypeAtLocation(member); return !!(type && type.flags & TypeFlags.StringLike); }); const enumMember = factory.createEnumMember(token, hasStringInitializer ? factory.createStringLiteral(token.text) : undefined); - changes.replaceNode(enumDeclaration.getSourceFile(), enumDeclaration, factory.updateEnumDeclaration( - enumDeclaration, - enumDeclaration.decorators, - enumDeclaration.modifiers, - enumDeclaration.name, - concatenate(enumDeclaration.members, singleElementArray(enumMember)) + changes.replaceNode(parentDeclaration.getSourceFile(), parentDeclaration, factory.updateEnumDeclaration( + parentDeclaration, + parentDeclaration.decorators, + parentDeclaration.modifiers, + parentDeclaration.name, + concatenate(parentDeclaration.members, singleElementArray(enumMember)) ), { leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); } + + function addFunctionDeclaration(changes: textChanges.ChangeTracker, context: CodeFixContextBase, info: FunctionInfo) { + const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host); + const functionDeclaration = createSignatureDeclarationFromCallExpression(SyntaxKind.FunctionDeclaration, context, importAdder, info.call, info.token, info.modifierFlags, info.parentDeclaration) as FunctionDeclaration; + changes.insertNodeAtEndOfScope(info.sourceFile, info.parentDeclaration, functionDeclaration); + } } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 3c5669cb20c58..e590081323f2e 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -247,41 +247,69 @@ namespace ts.codefix { ); } - export function createMethodFromCallExpression( + export function createSignatureDeclarationFromCallExpression( + kind: SyntaxKind.MethodDeclaration | SyntaxKind.FunctionDeclaration, context: CodeFixContextBase, importAdder: ImportAdder, call: CallExpression, - methodName: string, + name: Identifier, modifierFlags: ModifierFlags, - contextNode: Node, - inJs: boolean - ): MethodDeclaration { - const body = !isInterfaceDeclaration(contextNode); - const { typeArguments, arguments: args, parent } = call; + contextNode: Node + ) { + const quotePreference = getQuotePreference(context.sourceFile, context.preferences); const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions()); - const checker = context.program.getTypeChecker(); const tracker = getNoopSymbolTrackerWithResolver(context); - const types = map(args, arg => - typeToAutoImportableTypeNode(checker, importAdder, checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(arg)), contextNode, scriptTarget, /*flags*/ undefined, tracker)); + const checker = context.program.getTypeChecker(); + const isJs = isInJSFile(contextNode); + const { typeArguments, arguments: args, parent } = call; + + const contextualType = isJs ? undefined : checker.getContextualType(call); const names = map(args, arg => isIdentifier(arg) ? arg.text : isPropertyAccessExpression(arg) && isIdentifier(arg.name) ? arg.name.text : undefined); - const contextualType = checker.getContextualType(call); - const returnType = (inJs || !contextualType) ? undefined : checker.typeToTypeNode(contextualType, contextNode, /*flags*/ undefined, tracker); - const quotePreference = getQuotePreference(context.sourceFile, context.preferences); - return factory.createMethodDeclaration( + const types = isJs ? [] : map(args, arg => + typeToAutoImportableTypeNode(checker, importAdder, checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(arg)), contextNode, scriptTarget, /*flags*/ undefined, tracker)); + + const modifiers = modifierFlags + ? factory.createNodeArray(factory.createModifiersFromModifierFlags(modifierFlags)) + : undefined; + const asteriskToken = isYieldExpression(parent) + ? factory.createToken(SyntaxKind.AsteriskToken) + : undefined; + const typeParameters = isJs || typeArguments === undefined + ? undefined + : map(typeArguments, (_, i) => + factory.createTypeParameterDeclaration(CharacterCodes.T + typeArguments.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)); + const parameters = createDummyParameters(args.length, names, types, /*minArgumentCount*/ undefined, isJs); + const type = isJs || contextualType === undefined + ? undefined + : checker.typeToTypeNode(contextualType, contextNode, /*flags*/ undefined, tracker); + + if (kind === SyntaxKind.MethodDeclaration) { + return factory.createMethodDeclaration( + /*decorators*/ undefined, + modifiers, + asteriskToken, + name, + /*questionToken*/ undefined, + typeParameters, + parameters, + type, + isInterfaceDeclaration(contextNode) ? undefined : createStubbedMethodBody(quotePreference) + ); + } + return factory.createFunctionDeclaration( /*decorators*/ undefined, - /*modifiers*/ modifierFlags ? factory.createNodeArray(factory.createModifiersFromModifierFlags(modifierFlags)) : undefined, - /*asteriskToken*/ isYieldExpression(parent) ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, - methodName, - /*questionToken*/ undefined, - /*typeParameters*/ inJs ? undefined : map(typeArguments, (_, i) => - factory.createTypeParameterDeclaration(CharacterCodes.T + typeArguments!.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)), - /*parameters*/ createDummyParameters(args.length, names, types, /*minArgumentCount*/ undefined, inJs), - /*type*/ returnType, - body ? createStubbedMethodBody(quotePreference) : undefined); + modifiers, + asteriskToken, + name, + typeParameters, + parameters, + type, + createStubbedBody(Diagnostics.Function_not_implemented.message, quotePreference) + ); } - export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { + export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node | undefined, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { const typeNode = checker.typeToTypeNode(type, contextNode, flags, tracker); if (typeNode && isImportTypeNode(typeNode)) { const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); @@ -381,14 +409,18 @@ namespace ts.codefix { createStubbedMethodBody(quotePreference)); } - function createStubbedMethodBody(quotePreference: QuotePreference): Block { + function createStubbedMethodBody(quotePreference: QuotePreference) { + return createStubbedBody(Diagnostics.Method_not_implemented.message, quotePreference); + } + + export function createStubbedBody(text: string, quotePreference: QuotePreference): Block { return factory.createBlock( [factory.createThrowStatement( factory.createNewExpression( factory.createIdentifier("Error"), /*typeArguments*/ undefined, // TODO Handle auto quote preference. - [factory.createStringLiteral("Method not implemented.", /*isSingleQuote*/ quotePreference === QuotePreference.Single)]))], + [factory.createStringLiteral(text, /*isSingleQuote*/ quotePreference === QuotePreference.Single)]))], /*multiline*/ true); } diff --git a/tests/cases/fourslash/codeFixAddMissingConstToArrayDestructuring4.ts b/tests/cases/fourslash/codeFixAddMissingConstToArrayDestructuring4.ts index 2c077e4c1490e..35a6689ff4a8e 100644 --- a/tests/cases/fourslash/codeFixAddMissingConstToArrayDestructuring4.ts +++ b/tests/cases/fourslash/codeFixAddMissingConstToArrayDestructuring4.ts @@ -2,4 +2,14 @@ ////[x, y()] = [0, () => 1]; -verify.not.codeFixAvailable(); +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "y"], + newFileContent: +`[x, y()] = [0, () => 1]; + +function y() { + throw new Error("Function not implemented."); +} +` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration1.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration1.ts new file mode 100644 index 0000000000000..367201ab8666d --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration1.ts @@ -0,0 +1,15 @@ +/// + +////foo(); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + newFileContent: +`foo(); + +function foo() { + throw new Error("Function not implemented."); +} +` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration10.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration10.ts new file mode 100644 index 0000000000000..dd87d3fb126e9 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration10.ts @@ -0,0 +1,22 @@ +/// + +////namespace Foo { +//// export const x = 0; +////} +//// +////Foo.test(); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "test"], + newFileContent: +`namespace Foo { + export const x = 0; + + export function test() { + throw new Error("Function not implemented."); + } +} + +Foo.test();` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration11.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration11.ts new file mode 100644 index 0000000000000..6fe05a9c53a34 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration11.ts @@ -0,0 +1,23 @@ +/// + +// @filename: /test.ts +////export const x = 1; + +// @filename: /foo.ts +////import * as test from "./test"; +////test.foo(); + +goTo.file("/foo.ts"); +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + newFileContent: { + "/test.ts": +`export const x = 1; + +export function foo() { + throw new Error("Function not implemented."); +} +` + } +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration12.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration12.ts new file mode 100644 index 0000000000000..abc2e390a1c7a --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration12.ts @@ -0,0 +1,28 @@ +/// + +// @filename: /test.ts +////export const x = 1; + +// @filename: /foo.ts +////import * as test from "./test"; +////test.foo(); +////test.foo(); +////test.foo(); + +goTo.file("/foo.ts"); +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + applyChanges: true, + newFileContent: { + "/test.ts": +`export const x = 1; + +export function foo() { + throw new Error("Function not implemented."); +} +` + } +}); + +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration13.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration13.ts new file mode 100644 index 0000000000000..8ed4794d34ca3 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration13.ts @@ -0,0 +1,23 @@ +/// + +// @filename: /test.ts +////export const x = 1; + +// @filename: /foo.ts +////import * as test from "./test"; +////test.foo(1, "", { x: 1, y: 1 }); + +goTo.file("/foo.ts"); +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + newFileContent: { + "/test.ts": +`export const x = 1; + +export function foo(arg0: number, arg1: string, arg2: { x: number; y: number; }) { + throw new Error("Function not implemented."); +} +` + } +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration14.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration14.ts new file mode 100644 index 0000000000000..8af3d6a418365 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration14.ts @@ -0,0 +1,23 @@ +/// + +// @filename: /test.ts +////export const x = 1; + +// @filename: /foo.ts +////import * as test from "./test"; +////const foo: string = test.foo(); + +goTo.file("/foo.ts"); +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + newFileContent: { + "/test.ts": +`export const x = 1; + +export function foo(): string { + throw new Error("Function not implemented."); +} +` + } +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration15.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration15.ts new file mode 100644 index 0000000000000..cfac3508122be --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration15.ts @@ -0,0 +1,23 @@ +/// + +// @filename: /test.ts +////export const x = 1; + +// @filename: /foo.ts +////import * as test from "./test"; +////test.foo(); + +goTo.file("/foo.ts"); +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + newFileContent: { + "/test.ts": +`export const x = 1; + +export function foo() { + throw new Error("Function not implemented."); +} +` + } +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration16.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration16.ts new file mode 100644 index 0000000000000..c9b3450016e40 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration16.ts @@ -0,0 +1,12 @@ +/// + +// @moduleResolution: node +// @filename: /node_modules/test/index.js +////export const x = 1; + +// @filename: /foo.ts +////import * as test from "test"; +////test.foo(); + +goTo.file("/foo.ts"); +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration2.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration2.ts new file mode 100644 index 0000000000000..5f1fdd96c8a23 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration2.ts @@ -0,0 +1,22 @@ +/// + +////foo(); +////foo(); +////foo(); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + applyChanges: true, + newFileContent: +`foo(); +foo(); +foo(); + +function foo() { + throw new Error("Function not implemented."); +} +` +}); + +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration3.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration3.ts new file mode 100644 index 0000000000000..46decad36d58f --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration3.ts @@ -0,0 +1,15 @@ +/// + +////foo(1, "", { x: 1, y: 1 }); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + newFileContent: +`foo(1, "", { x: 1, y: 1 }); + +function foo(arg0: number, arg1: string, arg2: { x: number; y: number; }) { + throw new Error("Function not implemented."); +} +` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration4.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration4.ts new file mode 100644 index 0000000000000..74d6b5733d2ff --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration4.ts @@ -0,0 +1,15 @@ +/// + +////const test: string = foo(); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + newFileContent: +`const test: string = foo(); + +function foo(): string { + throw new Error("Function not implemented."); +} +` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration5.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration5.ts new file mode 100644 index 0000000000000..bd60b007778f2 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration5.ts @@ -0,0 +1,15 @@ +/// + +////foo(); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"], + newFileContent: +`foo(); + +function foo() { + throw new Error("Function not implemented."); +} +` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration6.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration6.ts new file mode 100644 index 0000000000000..ec1c89e6fb125 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration6.ts @@ -0,0 +1,22 @@ +/// + +////namespace Foo { +//// export const x = 0; +////} +//// +////Foo.test(); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "test"], + newFileContent: +`namespace Foo { + export const x = 0; + + export function test() { + throw new Error("Function not implemented."); + } +} + +Foo.test();` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration7.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration7.ts new file mode 100644 index 0000000000000..0f5318989ecf4 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration7.ts @@ -0,0 +1,29 @@ +/// + +////namespace Foo { +//// export const x = 0; +////} +//// +////Foo.test(); +////Foo.test(); +////Foo.test(); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "test"], + applyChanges: true, + newFileContent: +`namespace Foo { + export const x = 0; + + export function test() { + throw new Error("Function not implemented."); + } +} + +Foo.test(); +Foo.test(); +Foo.test();` +}); + +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration8.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration8.ts new file mode 100644 index 0000000000000..4c88c89e34984 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration8.ts @@ -0,0 +1,22 @@ +/// + +////namespace Foo { +//// export const x = 0; +////} +//// +////Foo.test(1, "", { x: 1, y: 1 }); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "test"], + newFileContent: +`namespace Foo { + export const x = 0; + + export function test(arg0: number, arg1: string, arg2: { x: number; y: number; }) { + throw new Error("Function not implemented."); + } +} + +Foo.test(1, "", { x: 1, y: 1 });` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration9.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration9.ts new file mode 100644 index 0000000000000..cdeedc5dd4d4c --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration9.ts @@ -0,0 +1,22 @@ +/// + +////namespace Foo { +//// export const x = 0; +////} +//// +////const test: string = Foo.test(); + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "test"], + newFileContent: +`namespace Foo { + export const x = 0; + + export function test(): string { + throw new Error("Function not implemented."); + } +} + +const test: string = Foo.test();` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration_all.ts b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration_all.ts new file mode 100644 index 0000000000000..7f7e338dab577 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingFunctionDeclaration_all.ts @@ -0,0 +1,49 @@ +/// + +// @filename: /test.ts +////export const x = 1; + +// @filename: /foo.ts +////import * as test from "./test"; +//// +////namespace Foo { +//// export const x = 0; +////} +//// +////test.f(); +////Foo.f(); +////f(); + +goTo.file("/foo.ts"); +verify.codeFixAll({ + fixId: "fixMissingFunctionDeclaration", + fixAllDescription: ts.Diagnostics.Add_all_missing_function_declarations.message, + newFileContent: { + "/test.ts": +`export const x = 1; + +export function f() { + throw new Error("Function not implemented."); +} +`, + "/foo.ts": +`import * as test from "./test"; + +namespace Foo { + export const x = 0; + + export function f() { + throw new Error("Function not implemented."); + } +} + +test.f(); +Foo.f(); +f(); + +function f() { + throw new Error("Function not implemented."); +} +` + } +}); diff --git a/tests/cases/fourslash/codeFixAddMissingMember16.ts b/tests/cases/fourslash/codeFixAddMissingMember16.ts index 6f3918af90632..96ea4764a57dc 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember16.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember16.ts @@ -6,4 +6,18 @@ ////} ////Foo.test(); -verify.not.codeFixAvailable(); +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "test"], + applyChanges: true, + newFileContent: +`interface Foo {} +namespace Foo { + export function bar() { } + + export function test() { + throw new Error("Function not implemented."); + } +} +Foo.test();` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingMember_all.ts b/tests/cases/fourslash/codeFixAddMissingMember_all.ts index 9c0c86f282dcf..cd82089109342 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember_all.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember_all.ts @@ -26,7 +26,7 @@ ////En.A; verify.codeFixAll({ - fixId: "addMissingMember", + fixId: "fixMissingMember", fixAllDescription: "Add all missing members", newFileContent: `class C { diff --git a/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts b/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts index 7c900be76f39c..3a087215bbbdb 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember_all_js.ts @@ -14,7 +14,7 @@ ////} verify.codeFixAll({ - fixId: "addMissingMember", + fixId: "fixMissingMember", fixAllDescription: "Add all missing members", newFileContent: `class C { diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts index a467e9ee0cef1..080d7bd4606c9 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts @@ -7,6 +7,7 @@ ////} verify.codeFix({ + index: 0, description: "Add async modifier to containing function", newFileContent: `async function f() { diff --git a/tests/cases/fourslash/importNameCodeFixNewImportExportEqualsPrimitive.ts b/tests/cases/fourslash/importNameCodeFixNewImportExportEqualsPrimitive.ts index 0b9dbb489dd62..3154f11b70306 100644 --- a/tests/cases/fourslash/importNameCodeFixNewImportExportEqualsPrimitive.ts +++ b/tests/cases/fourslash/importNameCodeFixNewImportExportEqualsPrimitive.ts @@ -6,4 +6,6 @@ ////declare var x: number; ////export = x; -verify.not.codeFixAvailable(); // See GH#20191 +verify.codeFixAvailable([ + { description: "Add missing function declaration 'valueOf'" } +]); diff --git a/tests/cases/fourslash/importNameCodeFixNewImportNodeModules5.ts b/tests/cases/fourslash/importNameCodeFixNewImportNodeModules5.ts index fa483a8ddee5d..a8e3775cd2f70 100644 --- a/tests/cases/fourslash/importNameCodeFixNewImportNodeModules5.ts +++ b/tests/cases/fourslash/importNameCodeFixNewImportNodeModules5.ts @@ -18,5 +18,6 @@ //// "types": "bin/lib/libfile.d.ts" //// } -// No fix because this accesses a nested node_modules -verify.not.codeFixAvailable(); +verify.codeFixAvailable([ + { description: "Add missing function declaration 'f1'" } +]); diff --git a/tests/cases/fourslash/incompleteFunctionCallCodefix3.ts b/tests/cases/fourslash/incompleteFunctionCallCodefix3.ts index 29072c7c85548..cbb2af43c3ddd 100644 --- a/tests/cases/fourslash/incompleteFunctionCallCodefix3.ts +++ b/tests/cases/fourslash/incompleteFunctionCallCodefix3.ts @@ -3,4 +3,6 @@ // @noImplicitAny: true //// function ...q) {}} f(10); -verify.not.codeFixAvailable(); +verify.codeFixAvailable([ + { description: "Add missing function declaration 'f'" } +]);