Skip to content

Commit

Permalink
feat(26217): Add missing function declaration QF
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk committed Nov 5, 2020
1 parent 899e2d0 commit dc448f9
Show file tree
Hide file tree
Showing 28 changed files with 588 additions and 76 deletions.
9 changes: 9 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5983,6 +5983,15 @@
"category": "Message",
"code": 95148
},
"Add missing function declaration '{0}'": {
"category": "Message",
"code": 95149
},
"Add all missing function declarations": {
"category": "Message",
"code": 95150
},


"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
137 changes: 96 additions & 41 deletions src/services/codefixes/fixAddMissingMember.ts

Large diffs are not rendered by default.

68 changes: 41 additions & 27 deletions src/services/codefixes/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,41 +247,51 @@ namespace ts.codefix {
);
}

export function createMethodFromCallExpression(
export function createFunctionFromCallExpression<T extends FunctionDeclaration | MethodDeclaration>(
kind: T["kind"] | SyntaxKind.MethodDeclaration,
context: CodeFixContextBase,
importAdder: ImportAdder,
call: CallExpression,
methodName: string,
name: Identifier | string,
modifierFlags: ModifierFlags,
contextNode: Node,
inJs: boolean
): MethodDeclaration {
const body = !isInterfaceDeclaration(contextNode);
const { typeArguments, arguments: args, parent } = call;
contextNode: Node
): T {
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(
/*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);
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) {
const body = isInterfaceDeclaration(contextNode) ? undefined : createStubbedMethodBody(quotePreference);
return factory.createMethodDeclaration(/*decorators*/ undefined, modifiers, asteriskToken, name, /*questionToken*/ undefined, typeParameters, parameters, type, body) as T;
}
return factory.createFunctionDeclaration(/*decorators*/ undefined, modifiers, asteriskToken, name, typeParameters, parameters, type, createStubbedBody("Function not implemented.", quotePreference)) as T;
}

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);
Expand Down Expand Up @@ -381,14 +391,18 @@ namespace ts.codefix {
createStubbedMethodBody(quotePreference));
}

function createStubbedMethodBody(quotePreference: QuotePreference): Block {
function createStubbedMethodBody(quotePreference: QuotePreference) {
return createStubbedBody("Method not implemented.", 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
`
});
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />

////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.");
}
`
});
22 changes: 22 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration10.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />

////namespace Foo {
//// export const x = 0;
////}
////
////Foo.test<string, number>();

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "test"],
newFileContent:
`namespace Foo {
export const x = 0;
export function test<T, U>() {
throw new Error("Function not implemented.");
}
}
Foo.test<string, number>();`
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

// @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.");
}
`
}
});
28 changes: 28 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration12.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />

// @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();
23 changes: 23 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration13.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

// @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.");
}
`
}
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration14.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

// @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.");
}
`
}
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration15.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

// @filename: /test.ts
////export const x = 1;

// @filename: /foo.ts
////import * as test from "./test";
////test.foo<string, number>();

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<T, U>() {
throw new Error("Function not implemented.");
}
`
}
});
12 changes: 12 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration16.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference path='fourslash.ts' />

// @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();
22 changes: 22 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />

////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();
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />

////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.");
}
`
});
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />

////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.");
}
`
});
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingFunctionDeclaration5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />

////foo<string, number>();

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Add_missing_function_declaration_0.message, "foo"],
newFileContent:
`foo<string, number>();
function foo<T, U>() {
throw new Error("Function not implemented.");
}
`
});
Loading

0 comments on commit dc448f9

Please sign in to comment.