Merge pull request #10358 from Microsoft/classPropertyInference

Support this.prop = expr; assignments as declarations for ES6 JS classes — Take 2
This commit is contained in:
Nathan Shively-Sanders 2016-08-15 16:20:20 -07:00 committed by GitHub
commit 80c04f8e97
6 changed files with 123 additions and 1 deletions

View file

@ -1972,7 +1972,18 @@ namespace ts {
assignee = container;
}
else if (container.kind === SyntaxKind.Constructor) {
assignee = container.parent;
if (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;
return;
}
else {
assignee = container.parent;
}
}
else {
return;

View file

@ -3248,6 +3248,13 @@ namespace ts {
// * className.prototype.method = expr
if (declaration.kind === SyntaxKind.BinaryExpression ||
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
// Use JS Doc type if present on parent expression statement
if (declaration.flags & NodeFlags.JavaScriptFile) {
const typeTag = getJSDocTypeTag(declaration.parent);
if (typeTag && typeTag.typeExpression) {
return links.type = getTypeFromTypeNode(typeTag.typeExpression.type);
}
}
const declaredTypes = map(symbol.declarations,
decl => decl.kind === SyntaxKind.BinaryExpression ?
checkExpressionCached((<BinaryExpression>decl).right) :
@ -9456,6 +9463,11 @@ namespace ts {
const binaryExpression = <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);

View file

@ -0,0 +1,31 @@
///<reference path="fourslash.ts" />
// 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 = () => 0;
//// 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");

View file

@ -0,0 +1,22 @@
///<reference path="fourslash.ts" />
// 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);

View file

@ -0,0 +1,24 @@
///<reference path="fourslash.ts" />
// 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');

View file

@ -0,0 +1,22 @@
///<reference path="fourslash.ts" />
// Classes have their shape inferred from assignments
// to properties of 'this' in the constructor
// @allowNonTsExtensions: true
// @Filename: Foo.js
//// class Foo {
//// constructor() {
//// /**
//// * @type {string}
//// */
//// this.baz = null;
//// }
//// }
//// var x = new Foo();
//// x/**/
goTo.marker();
edit.insert('.baz.');
verify.completionListContains("substr", /*displayText*/ undefined, /*documentation*/ undefined, "method");