diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b209a878804..529484956e6 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4990,7 +4990,10 @@ "category": "Message", "code": 95079 }, - + "Infer 'this' type of '{0}' from usage": { + "category": "Message", + "code": 95080 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", "code": 18004 diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 5d30a9344c1..f67ec39b0a4 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2204,6 +2204,13 @@ namespace ts { return tag; } + /** @internal */ + export function createJSDocThisTag(typeExpression?: JSDocTypeExpression): JSDocThisTag { + const tag = createJSDocTag(SyntaxKind.JSDocThisTag, "this"); + tag.typeExpression = typeExpression; + return tag; + } + /* @internal */ export function createJSDocParamTag(name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, comment?: string): JSDocParameterTag { const tag = createJSDocTag(SyntaxKind.JSDocParameterTag, "param"); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0ec81502cf7..0e2488a0233 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5187,6 +5187,9 @@ namespace ts { return node.parent.left.name; } } + else if (isVariableDeclaration(node.parent) && isIdentifier(node.parent.name)) { + return node.parent.name; + } } /** diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index ecf2841e249..53ca335c7a5 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -42,6 +42,9 @@ namespace ts.codefix { // Property declarations Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code, + + // Function expressions and declarations + Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code, ]; registerCodeFix({ errorCodes, @@ -73,6 +76,8 @@ namespace ts.codefix { case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code: case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code: return Diagnostics.Infer_parameter_types_from_usage; + case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code: + return Diagnostics.Infer_this_type_of_0_from_usage; default: return Diagnostics.Infer_type_of_0_from_usage; } @@ -176,6 +181,14 @@ namespace ts.codefix { } return undefined; + // Function 'this' + case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code: + if (textChanges.isThisTypeAnnotatable(containingFunction) && markSeen(containingFunction)) { + annotateThis(changes, sourceFile, containingFunction, program, host, cancellationToken); + return containingFunction; + } + return undefined; + default: return Debug.fail(String(errorCode)); } @@ -191,7 +204,9 @@ namespace ts.codefix { if (!isIdentifier(parameterDeclaration.name)) { return; } - const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) || + + const references = inferFunctionReferencesFromUsage(containingFunction, sourceFile, program, cancellationToken); + const parameterInferences = InferFromReference.inferTypeForParametersFromReferences(references, containingFunction, program, cancellationToken) || containingFunction.parameters.map(p => ({ declaration: p, type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : program.getTypeChecker().getAnyType() @@ -213,6 +228,36 @@ namespace ts.codefix { } } + function annotateThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: textChanges.ThisTypeAnnotatable, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken) { + const references = inferFunctionReferencesFromUsage(containingFunction, sourceFile, program, cancellationToken); + if (!references) { + return; + } + + const thisInference = InferFromReference.inferTypeForThisFromReferences(references, program, cancellationToken); + if (!thisInference) { + return; + } + + const typeNode = getTypeNodeIfAccessible(thisInference, containingFunction, program, host); + if (!typeNode) { + return; + } + + if (isInJSFile(containingFunction)) { + annotateJSDocThis(changes, sourceFile, containingFunction, typeNode); + } + else { + changes.tryInsertThisTypeAnnotation(sourceFile, containingFunction, typeNode); + } + } + + function annotateJSDocThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: FunctionLike, typeNode: TypeNode) { + addJSDocTags(changes, sourceFile, containingFunction, [ + createJSDocThisTag(createJSDocTypeExpression(typeNode)), + ]); + } + function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void { const param = firstOrUndefined(setAccessorDeclaration.parameters); if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) { @@ -317,7 +362,7 @@ namespace ts.codefix { return InferFromReference.unifyFromContext(types, checker); } - function inferTypeForParametersFromUsage(containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined { + function inferFunctionReferencesFromUsage(containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ReadonlyArray | undefined { let searchToken; switch (containingFunction.kind) { case SyntaxKind.Constructor: @@ -335,9 +380,12 @@ namespace ts.codefix { searchToken = containingFunction.name; break; } - if (searchToken) { - return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, program, cancellationToken), containingFunction, program, cancellationToken); + + if (!searchToken) { + return undefined; } + + return getReferences(searchToken, program, cancellationToken); } interface ParameterInference { @@ -364,6 +412,7 @@ namespace ts.codefix { constructContexts?: CallContext[]; numberIndexContext?: UsageContext; stringIndexContext?: UsageContext; + candidateThisTypes?: Type[]; } export function inferTypesFromReferences(references: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken): Type[] { @@ -375,15 +424,12 @@ namespace ts.codefix { return inferFromContext(usageContext, checker); } - export function inferTypeForParametersFromReferences(references: ReadonlyArray, declaration: FunctionLike, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined { - const checker = program.getTypeChecker(); - if (references.length === 0) { - return undefined; - } - if (!declaration.parameters) { + export function inferTypeForParametersFromReferences(references: ReadonlyArray | undefined, declaration: FunctionLike, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined { + if (references === undefined || references.length === 0 || !declaration.parameters) { return undefined; } + const checker = program.getTypeChecker(); const usageContext: UsageContext = {}; for (const reference of references) { cancellationToken.throwIfCancellationRequested(); @@ -421,6 +467,22 @@ namespace ts.codefix { }); } + export function inferTypeForThisFromReferences(references: ReadonlyArray, program: Program, cancellationToken: CancellationToken) { + if (references.length === 0) { + return undefined; + } + + const checker = program.getTypeChecker(); + const usageContext: UsageContext = {}; + + for (const reference of references) { + cancellationToken.throwIfCancellationRequested(); + inferTypeFromContext(reference, checker, usageContext); + } + + return unifyFromContext(usageContext.candidateThisTypes || emptyArray, checker); + } + function inferTypeFromContext(node: Expression, checker: TypeChecker, usageContext: UsageContext): void { while (isRightSideOfQualifiedNameOrPropertyAccess(node)) { node = node.parent; @@ -455,6 +517,13 @@ namespace ts.codefix { case SyntaxKind.ElementAccessExpression: inferTypeFromPropertyElementExpressionContext(node.parent, node, checker, usageContext); break; + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + inferTypeFromPropertyAssignment(node.parent, checker, usageContext); + break; + case SyntaxKind.PropertyDeclaration: + inferTypeFromPropertyDeclaration(node.parent, checker, usageContext); + break; case SyntaxKind.VariableDeclaration: { const { name, initializer } = node.parent as VariableDeclaration; if (node === name) { @@ -647,6 +716,21 @@ namespace ts.codefix { } } + function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, checker: TypeChecker, usageContext: UsageContext) { + const objectLiteral = isShorthandPropertyAssignment(assignment) ? + assignment.parent : + assignment.parent.parent; + const nodeWithRealType = isVariableDeclaration(objectLiteral.parent) ? + objectLiteral.parent : + objectLiteral; + + addCandidateThisType(usageContext, checker.getTypeAtLocation(nodeWithRealType)); + } + + function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, checker: TypeChecker, usageContext: UsageContext) { + addCandidateThisType(usageContext, checker.getTypeAtLocation(declaration.parent)); + } + interface Priority { high: (t: Type) => boolean; low: (t: Type) => boolean; @@ -841,6 +925,12 @@ namespace ts.codefix { } } + function addCandidateThisType(context: UsageContext, type: Type | undefined) { + if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) { + (context.candidateThisTypes || (context.candidateThisTypes = [])).push(type); + } + } + function hasCallContext(usageContext: UsageContext | undefined): boolean { return !!usageContext && !!usageContext.callContexts; } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 81441627635..bdd335b36bd 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -222,6 +222,12 @@ namespace ts.textChanges { export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature; + export type ThisTypeAnnotatable = FunctionDeclaration | FunctionExpression; + + export function isThisTypeAnnotatable(containingFunction: FunctionLike): containingFunction is ThisTypeAnnotatable { + return isFunctionExpression(containingFunction) || isFunctionDeclaration(containingFunction); + } + export class ChangeTracker { private readonly changes: Change[] = []; private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: ReadonlyArray }[] = []; @@ -393,6 +399,13 @@ namespace ts.textChanges { this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " }); } + public tryInsertThisTypeAnnotation(sourceFile: SourceFile, node: ThisTypeAnnotatable, type: TypeNode): void { + const start = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile)!.getStart(sourceFile) + 1; + const suffix = node.parameters.length ? ", " : ""; + + this.insertNodeAt(sourceFile, start, type, { prefix: "this: ", suffix }); + } + public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: ReadonlyArray): void { // If no `(`, is an arrow function `x => x`, so use the pos of the first parameter const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile); diff --git a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js index 7e6b452302b..9ca3f8998a2 100644 --- a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js +++ b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js @@ -128,10 +128,10 @@ function foo3() { } function foo4() { var y = /** @class */ (function () { - function class_1() { + function y() { } - class_1.prototype.m = function () { return x; }; - return class_1; + y.prototype.m = function () { return x; }; + return y; }()); var x; } @@ -156,19 +156,19 @@ function foo7() { } function foo8() { var y = /** @class */ (function () { - function class_2() { + function class_1() { this.a = x; } - return class_2; + return class_1; }()); var x; } function foo9() { var _a; var y = (_a = /** @class */ (function () { - function class_3() { + function class_2() { } - return class_3; + return class_2; }()), _a.a = x, _a); @@ -187,9 +187,9 @@ function foo11() { function f() { var _a; var y = (_a = /** @class */ (function () { - function class_4() { + function class_3() { } - return class_4; + return class_3; }()), _a.a = x, _a); @@ -199,10 +199,10 @@ function foo11() { function foo12() { function f() { var y = /** @class */ (function () { - function class_5() { + function class_4() { this.a = x; } - return class_5; + return class_4; }()); } var x; diff --git a/tests/baselines/reference/classExpression4.js b/tests/baselines/reference/classExpression4.js index b2ad6db6b2c..c8af94883ce 100644 --- a/tests/baselines/reference/classExpression4.js +++ b/tests/baselines/reference/classExpression4.js @@ -9,11 +9,11 @@ let x = (new C).foo(); //// [classExpression4.js] var C = /** @class */ (function () { - function class_1() { + function C() { } - class_1.prototype.foo = function () { + C.prototype.foo = function () { return new C(); }; - return class_1; + return C; }()); var x = (new C).foo(); diff --git a/tests/baselines/reference/classExpressionExtendingAbstractClass.js b/tests/baselines/reference/classExpressionExtendingAbstractClass.js index 7af44941b28..fe8e4ce19df 100644 --- a/tests/baselines/reference/classExpressionExtendingAbstractClass.js +++ b/tests/baselines/reference/classExpressionExtendingAbstractClass.js @@ -28,9 +28,9 @@ var A = /** @class */ (function () { return A; }()); var C = /** @class */ (function (_super) { - __extends(class_1, _super); - function class_1() { + __extends(C, _super); + function C() { return _super !== null && _super.apply(this, arguments) || this; } - return class_1; + return C; }(A)); diff --git a/tests/baselines/reference/emitClassExpressionInDeclarationFile.js b/tests/baselines/reference/emitClassExpressionInDeclarationFile.js index e9b43403f93..1ea94bf1edd 100644 --- a/tests/baselines/reference/emitClassExpressionInDeclarationFile.js +++ b/tests/baselines/reference/emitClassExpressionInDeclarationFile.js @@ -47,11 +47,11 @@ var __extends = (this && this.__extends) || (function () { })(); exports.__esModule = true; exports.simpleExample = /** @class */ (function () { - function class_1() { + function simpleExample() { } - class_1.getTags = function () { }; - class_1.prototype.tags = function () { }; - return class_1; + simpleExample.getTags = function () { }; + simpleExample.prototype.tags = function () { }; + return simpleExample; }()); exports.circularReference = /** @class */ (function () { function C() { @@ -70,13 +70,13 @@ var FooItem = /** @class */ (function () { exports.FooItem = FooItem; function WithTags(Base) { return /** @class */ (function (_super) { - __extends(class_2, _super); - function class_2() { + __extends(class_1, _super); + function class_1() { return _super !== null && _super.apply(this, arguments) || this; } - class_2.getTags = function () { }; - class_2.prototype.tags = function () { }; - return class_2; + class_1.getTags = function () { }; + class_1.prototype.tags = function () { }; + return class_1; }(Base)); } exports.WithTags = WithTags; diff --git a/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt b/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt index 441d27952ae..d41b3860b83 100644 --- a/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt +++ b/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt @@ -3,7 +3,7 @@ tests/cases/compiler/implicitAnyFromCircularInference.ts(5,5): error TS2502: 'b' tests/cases/compiler/implicitAnyFromCircularInference.ts(6,5): error TS2502: 'c' is referenced directly or indirectly in its own type annotation. tests/cases/compiler/implicitAnyFromCircularInference.ts(9,5): error TS2502: 'd' is referenced directly or indirectly in its own type annotation. tests/cases/compiler/implicitAnyFromCircularInference.ts(14,10): error TS7023: 'g' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -tests/cases/compiler/implicitAnyFromCircularInference.ts(17,10): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +tests/cases/compiler/implicitAnyFromCircularInference.ts(17,5): error TS7023: 'f1' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. tests/cases/compiler/implicitAnyFromCircularInference.ts(22,10): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. tests/cases/compiler/implicitAnyFromCircularInference.ts(25,10): error TS7023: 'h' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. tests/cases/compiler/implicitAnyFromCircularInference.ts(27,14): error TS7023: 'foo' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. @@ -38,8 +38,8 @@ tests/cases/compiler/implicitAnyFromCircularInference.ts(45,9): error TS7023: 'x // Error expected var f1 = function () { - ~~~~~~~~ -!!! error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + ~~ +!!! error TS7023: 'f1' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. return f1(); }; diff --git a/tests/baselines/reference/newTarget.es5.js b/tests/baselines/reference/newTarget.es5.js index 7fa733880b4..92be98d08fb 100644 --- a/tests/baselines/reference/newTarget.es5.js +++ b/tests/baselines/reference/newTarget.es5.js @@ -78,8 +78,8 @@ function f1() { var g = _newTarget; var h = function () { return _newTarget; }; } -var f2 = function _b() { - var _newTarget = this && this instanceof _b ? this.constructor : void 0; +var f2 = function f2() { + var _newTarget = this && this instanceof f2 ? this.constructor : void 0; var i = _newTarget; var j = function () { return _newTarget; }; }; diff --git a/tests/baselines/reference/staticPropertyNameConflicts.js b/tests/baselines/reference/staticPropertyNameConflicts.js index 6167d06dd84..b50f6a440ae 100644 --- a/tests/baselines/reference/staticPropertyNameConflicts.js +++ b/tests/baselines/reference/staticPropertyNameConflicts.js @@ -265,63 +265,63 @@ var StaticName_Anonymous = /** @class */ (function () { return class_1; }()); var StaticNameFn_Anonymous = /** @class */ (function () { - function class_2() { + function StaticNameFn_Anonymous() { } - class_2.name = function () { }; // error - class_2.prototype.name = function () { }; // ok - return class_2; + StaticNameFn_Anonymous.name = function () { }; // error + StaticNameFn_Anonymous.prototype.name = function () { }; // ok + return StaticNameFn_Anonymous; }()); // length var StaticLength_Anonymous = /** @class */ (function () { + function class_2() { + } + return class_2; +}()); +var StaticLengthFn_Anonymous = /** @class */ (function () { + function StaticLengthFn_Anonymous() { + } + StaticLengthFn_Anonymous.length = function () { }; // error + StaticLengthFn_Anonymous.prototype.length = function () { }; // ok + return StaticLengthFn_Anonymous; +}()); +// prototype +var StaticPrototype_Anonymous = /** @class */ (function () { function class_3() { } return class_3; }()); -var StaticLengthFn_Anonymous = /** @class */ (function () { +var StaticPrototypeFn_Anonymous = /** @class */ (function () { + function StaticPrototypeFn_Anonymous() { + } + StaticPrototypeFn_Anonymous.prototype = function () { }; // error + StaticPrototypeFn_Anonymous.prototype.prototype = function () { }; // ok + return StaticPrototypeFn_Anonymous; +}()); +// caller +var StaticCaller_Anonymous = /** @class */ (function () { function class_4() { } - class_4.length = function () { }; // error - class_4.prototype.length = function () { }; // ok return class_4; }()); -// prototype -var StaticPrototype_Anonymous = /** @class */ (function () { +var StaticCallerFn_Anonymous = /** @class */ (function () { + function StaticCallerFn_Anonymous() { + } + StaticCallerFn_Anonymous.caller = function () { }; // error + StaticCallerFn_Anonymous.prototype.caller = function () { }; // ok + return StaticCallerFn_Anonymous; +}()); +// arguments +var StaticArguments_Anonymous = /** @class */ (function () { function class_5() { } return class_5; }()); -var StaticPrototypeFn_Anonymous = /** @class */ (function () { - function class_6() { - } - class_6.prototype = function () { }; // error - class_6.prototype.prototype = function () { }; // ok - return class_6; -}()); -// caller -var StaticCaller_Anonymous = /** @class */ (function () { - function class_7() { - } - return class_7; -}()); -var StaticCallerFn_Anonymous = /** @class */ (function () { - function class_8() { - } - class_8.caller = function () { }; // error - class_8.prototype.caller = function () { }; // ok - return class_8; -}()); -// arguments -var StaticArguments_Anonymous = /** @class */ (function () { - function class_9() { - } - return class_9; -}()); var StaticArgumentsFn_Anonymous = /** @class */ (function () { - function class_10() { + function StaticArgumentsFn_Anonymous() { } - class_10.arguments = function () { }; // error - class_10.prototype.arguments = function () { }; // ok - return class_10; + StaticArgumentsFn_Anonymous.arguments = function () { }; // error + StaticArgumentsFn_Anonymous.prototype.arguments = function () { }; // ok + return StaticArgumentsFn_Anonymous; }()); // === Static properties on default exported classes === // name diff --git a/tests/baselines/reference/superCallInsideClassExpression.js b/tests/baselines/reference/superCallInsideClassExpression.js index 330a5de2808..52a96213326 100644 --- a/tests/baselines/reference/superCallInsideClassExpression.js +++ b/tests/baselines/reference/superCallInsideClassExpression.js @@ -45,11 +45,11 @@ var B = /** @class */ (function (_super) { function B() { var _this = this; var D = /** @class */ (function (_super) { - __extends(class_1, _super); - function class_1() { + __extends(D, _super); + function D() { return _super.call(this) || this; } - return class_1; + return D; }(C)); return _this; } diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageExplicitAny.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageExplicitAny.ts new file mode 100644 index 00000000000..a93750cedc0 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageExplicitAny.ts @@ -0,0 +1,19 @@ +/// + +// @noImplicitThis: true +//// function returnThisMember([| |]) { +//// return this.member; +//// } +//// +//// const container: any = { +//// member: "sample", +//// returnThisMember: returnThisMember, +//// }; +//// +//// container.returnThisMember(); + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newRangeContent: "this: any ", +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageFunctionExpression.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageFunctionExpression.ts new file mode 100644 index 00000000000..8e4182b1b05 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageFunctionExpression.ts @@ -0,0 +1,22 @@ +/// + +// @noImplicitThis: true +//// const returnThisMember = function ([| |]) { +//// return this.member; +//// } +//// +//// interface Container { +//// member: string; +//// returnThisMember(): string; +//// } +//// +//// const container: Container = { +//// member: "sample", +//// returnThisMember: returnThisMember, +//// }; + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newRangeContent: "this: Container ", +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageImplicitAny.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageImplicitAny.ts new file mode 100644 index 00000000000..3f71ae9ddd2 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageImplicitAny.ts @@ -0,0 +1,25 @@ +/// + +// @noImplicitThis: true +////function returnThisMember([| |]) { +//// return this.member; +//// } +//// +//// interface Container { +//// member: string; +//// returnThisMember(): string; +//// } +//// +//// let container; +//// container = { +//// member: "sample", +//// returnThisMember: returnThisMember, +//// }; +//// +//// container.returnThisMember(); + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newRangeContent: "this: { member: string; returnThisMember: () => any; } ", +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocExistingDocsClass.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocExistingDocsClass.ts new file mode 100644 index 00000000000..20dfb17ea37 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocExistingDocsClass.ts @@ -0,0 +1,39 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitThis: true + +// @Filename: /consumesType.js +/////** +//// * @returns {string} +//// */ +////function [|returnThisMember|]() { +//// return this.member; +////} +//// +////class Container { +//// member = "sample"; +//// returnThisMember = returnThisMember; +////}; +//// +////container.returnThisMember(); + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newFileContent: `/** + * @returns {string} + * @this {Container} + */ +function returnThisMember() { + return this.member; +} + +class Container { + member = "sample"; + returnThisMember = returnThisMember; +}; + +container.returnThisMember();` +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsClass.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsClass.ts new file mode 100644 index 00000000000..0ea75ec0e8c --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsClass.ts @@ -0,0 +1,35 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitThis: true + +// @Filename: /consumesType.js +////function [|returnThisMember|]() { +//// return this.member; +////} +//// +////class Container { +//// member = "sample"; +//// returnThisMember = returnThisMember; +////}; +//// +////container.returnThisMember(); + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newFileContent: `/** + * @this {Container} + */ +function returnThisMember() { + return this.member; +} + +class Container { + member = "sample"; + returnThisMember = returnThisMember; +}; + +container.returnThisMember();` +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsInaccessible.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsInaccessible.ts new file mode 100644 index 00000000000..14dc7fb4ae7 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsInaccessible.ts @@ -0,0 +1,47 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitThis: true + +// @Filename: /consumesType.js +////function [|returnThisMember|]() { +//// return this.member; +////} +//// +/////** +//// * @type {import("/providesType").Container} +//// */ +////const container = { +//// member: "sample", +//// returnThisMember: returnThisMember, +////}; +//// +////container.returnThisMember(); + +// @Filename: /providesType.ts +////interface Container { +//// member: string; +//// returnThisMember(): string; +////} + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newFileContent: `/** + * @this {any} + */ +function returnThisMember() { + return this.member; +} + +/** + * @type {import("/providesType").Container} + */ +const container = { + member: "sample", + returnThisMember: returnThisMember, +}; + +container.returnThisMember();` +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsLiteral.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsLiteral.ts new file mode 100644 index 00000000000..4a39ddbf09f --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageJsDocNewDocsLiteral.ts @@ -0,0 +1,35 @@ +/// + +// @allowJs: true +// @checkJs: true +// @noImplicitThis: true + +// @Filename: /consumesType.js +////function [|returnThisMember|]() { +//// return this.member; +////} +//// +////const container = { +//// member: "sample", +//// returnThisMember: returnThisMember, +////}; +//// +////container.returnThisMember(); + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newFileContent: `/** + * @this {{ member: string; returnThisMember: () => any; }} + */ +function returnThisMember() { + return this.member; +} + +const container = { + member: "sample", + returnThisMember: returnThisMember, +}; + +container.returnThisMember();` +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageLiteral.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageLiteral.ts new file mode 100644 index 00000000000..f626e4cba2d --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageLiteral.ts @@ -0,0 +1,17 @@ +/// + +// @noImplicitThis: true +////function returnThisMember([| |]) { +//// return this.member; +//// } +//// +//// const container = { +//// member: "sample", +//// returnThisMember: returnThisMember, +//// }; + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newRangeContent: "this: { member: string; returnThisMember: () => any; } ", +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageNoUses.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageNoUses.ts new file mode 100644 index 00000000000..3a9bef17a47 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageNoUses.ts @@ -0,0 +1,12 @@ +/// + +// @noImplicitThis: true +////function returnThisMember([| |]) { +//// return this.member; +//// } + +verify.codeFix({ + description: "Infer 'this' type of 'returnThisMember' from usage", + index: 0, + newRangeContent: "this: any ", +}); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectProperty.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectProperty.ts new file mode 100644 index 00000000000..a4fcfbab717 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectProperty.ts @@ -0,0 +1,20 @@ +/// + +// @noImplicitThis: true +////function returnThisMember([| |]) { +//// return this.member; +//// } +//// +//// interface Container { +//// member: string; +//// returnThisMember(): string; +//// } +//// +//// const container: Container = { +//// member: "sample", +//// returnThisMember: returnThisMember, +//// }; +//// +//// container.returnThisMember(); + +verify.rangeAfterCodeFix("this: Container"); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyParameter.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyParameter.ts new file mode 100644 index 00000000000..4d9ddfd9cc7 --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyParameter.ts @@ -0,0 +1,20 @@ +/// + +// @noImplicitThis: true +////function returnThisMember([| |]suffix: string) { +//// return this.member + suffix; +//// } +//// +//// interface Container { +//// member: string; +//// returnThisMember(suffix: string): string; +//// } +//// +//// const container: Container = { +//// member: "sample", +//// returnThisMember: returnThisMember, +//// }; +//// +//// container.returnThisMember(""); + +verify.rangeAfterCodeFix("this: Container, "); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyShorthand.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyShorthand.ts new file mode 100644 index 00000000000..9d8c229a02e --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyShorthand.ts @@ -0,0 +1,20 @@ +/// + +// @noImplicitThis: true +////function returnThisMember([| |]) { +//// return this.member; +//// } +//// +//// interface Container { +//// member: string; +//// returnThisMember(): string; +//// } +//// +//// const container: Container = { +//// member: "sample", +//// returnThisMember, +//// }; +//// +//// container.returnThisMember(); + +verify.rangeAfterCodeFix("this: Container"); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyShorthandParameter.ts b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyShorthandParameter.ts new file mode 100644 index 00000000000..3dea3f034bb --- /dev/null +++ b/tests/cases/fourslash/codeFixInferFromFunctionThisUsageObjectPropertyShorthandParameter.ts @@ -0,0 +1,20 @@ +/// + +// @noImplicitThis: true +////function returnThisMember([| |]suffix: string) { +//// return this.member + suffix; +//// } +//// +//// interface Container { +//// member: string; +//// returnThisMember(suffix: string): string; +//// } +//// +//// const container: Container = { +//// member: "sample", +//// returnThisMember, +//// }; +//// +//// container.returnThisMember(""); + +verify.rangeAfterCodeFix("this: Container, ");