From cbc112a7617528c9449ba3c4ababacdb4baa725a Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 21 Mar 2016 08:26:56 -0700 Subject: [PATCH] Support this.prop = expr; assignments as declarations for ES6 JS classes --- src/compiler/binder.ts | 8 ++++++ src/compiler/checker.ts | 5 ++++ tests/cases/fourslash/javaScriptClass1.ts | 32 +++++++++++++++++++++++ tests/cases/fourslash/javaScriptClass2.ts | 22 ++++++++++++++++ tests/cases/fourslash/javaScriptClass3.ts | 24 +++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 tests/cases/fourslash/javaScriptClass1.ts create mode 100644 tests/cases/fourslash/javaScriptClass2.ts create mode 100644 tests/cases/fourslash/javaScriptClass3.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6957be484f3..dc0ff55b89a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1478,6 +1478,14 @@ namespace ts { // It's acceptable for multiple 'this' assignments of the same identifier to occur declareSymbol(container.symbol.members, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } + else if (container.kind === SyntaxKind.Constructor && isInJavaScriptFile(node)) { + // this.foo assignment in a JavaScript class + // Bind this property to the containing class + const saveContainer = container; + container = container.parent; + bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.None); + container = saveContainer; + } } function bindPrototypePropertyAssignment(node: BinaryExpression) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 27b08397205..63d2afe72eb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8037,6 +8037,11 @@ namespace ts { const binaryExpression = node.parent; const operator = binaryExpression.operatorToken.kind; if (operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment) { + // Don't do this for special property assignments to avoid circularity + if (getSpecialPropertyAssignmentKind(binaryExpression) !== SpecialPropertyAssignmentKind.None) { + return undefined; + } + // In an assignment expression, the right operand is contextually typed by the type of the left operand. if (node === binaryExpression.right) { return checkExpression(binaryExpression.left); diff --git a/tests/cases/fourslash/javaScriptClass1.ts b/tests/cases/fourslash/javaScriptClass1.ts new file mode 100644 index 00000000000..117fc5be9be --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass1.ts @@ -0,0 +1,32 @@ +/// + +// Classes have their shape inferred from assignments +// to properties of 'this' in the constructor + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// this.bar = 'world'; +//// this.thing = 42; +//// this.union = 'foo'; +//// this.union = 100; +//// } +//// } +//// var x = new Foo(); +//// x/**/ + + +goTo.marker(); +edit.insert('.'); +verify.completionListContains("bar", /*displayText*/ undefined, /*documentation*/ undefined, "property"); +verify.completionListContains("thing", /*displayText*/ undefined, /*documentation*/ undefined, "property"); +verify.completionListContains("union", /*displayText*/ undefined, /*documentation*/ undefined, "property"); + +edit.insert('bar.'); +verify.completionListContains("substr", /*displayText*/ undefined, /*documentation*/ undefined, "method"); + +edit.backspace('bar.'.length); + +edit.insert('union.'); +verify.completionListContains("toString", /*displayText*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/javaScriptClass2.ts b/tests/cases/fourslash/javaScriptClass2.ts new file mode 100644 index 00000000000..d6daa320c5a --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass2.ts @@ -0,0 +1,22 @@ +/// + +// In an inferred class, we can rename successfully + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// this.[|union|] = 'foo'; +//// this./*1*/[|union|] = 100; +//// } +//// method() { return this./*2*/[|union|]; } +//// } +//// var x = new Foo(); +//// x./*3*/[|union|]; + +goTo.marker('1'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); +goTo.marker('2'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); +goTo.marker('3'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); diff --git a/tests/cases/fourslash/javaScriptClass3.ts b/tests/cases/fourslash/javaScriptClass3.ts new file mode 100644 index 00000000000..47004d53b04 --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass3.ts @@ -0,0 +1,24 @@ +/// + +// In an inferred class, we can to-to-def successfully + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// /*dst1*/this.alpha = 10; +//// /*dst2*/this.beta = 'gamma'; +//// } +//// method() { return this.alpha; } +//// } +//// var x = new Foo(); +//// x.alpha/*src1*/; +//// x.beta/*src2*/; + +goTo.marker('src1'); +goTo.definition(); +verify.caretAtMarker('dst1'); + +goTo.marker('src2'); +goTo.definition(); +verify.caretAtMarker('dst2');