mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
JavaScript class inference from prototype property assignment
This commit is contained in:
@@ -622,6 +622,7 @@ namespace ts {
|
||||
function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: string) {
|
||||
let symbol = createSymbol(symbolFlags, name);
|
||||
addDeclarationToSymbol(symbol, node, symbolFlags);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
function bindBlockScopedDeclaration(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) {
|
||||
@@ -867,6 +868,9 @@ namespace ts {
|
||||
else if (isModuleExportsAssignment(node)) {
|
||||
bindModuleExportsAssignment(<BinaryExpression>node);
|
||||
}
|
||||
else if (isPrototypePropertyAssignment(node)) {
|
||||
bindPrototypePropertyAssignment(node);
|
||||
}
|
||||
}
|
||||
return checkStrictModeBinaryExpression(<BinaryExpression>node);
|
||||
case SyntaxKind.CatchClause:
|
||||
@@ -1034,6 +1038,34 @@ namespace ts {
|
||||
bindExportAssignment(node);
|
||||
}
|
||||
|
||||
function bindPrototypePropertyAssignment(node: BinaryExpression) {
|
||||
// We saw a node of the form 'x.prototype.y = z'.
|
||||
// This does two things: turns 'x' into a constructor function, and
|
||||
// adds a member 'y' to the result of that constructor function
|
||||
// Get 'x', the class
|
||||
let classId = <Identifier>(<PropertyAccessExpression>(<PropertyAccessExpression>node.left).expression).expression;
|
||||
|
||||
// Look up the function in the local scope, since prototype assignments should immediately
|
||||
// follow the function declaration
|
||||
let funcSymbol = container.locals[classId.text];
|
||||
if (!funcSymbol) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The function is now a constructor rather than a normal function
|
||||
if (!funcSymbol.inferredConstructor) {
|
||||
funcSymbol.flags = (funcSymbol.flags | SymbolFlags.Class) & ~SymbolFlags.Function;
|
||||
funcSymbol.members = funcSymbol.members || {};
|
||||
funcSymbol.members["__constructor"] = funcSymbol;
|
||||
funcSymbol.inferredConstructor = true;
|
||||
}
|
||||
|
||||
// Get 'y', the property name, and add it to the type of the class
|
||||
let propertyName = (<PropertyAccessExpression>node.left).name;
|
||||
let prototypeSymbol = declareSymbol(funcSymbol.members, funcSymbol, <PropertyAccessExpression>(<PropertyAccessExpression>node.left).expression, SymbolFlags.HasMembers, SymbolFlags.None);
|
||||
declareSymbol(prototypeSymbol.members, prototypeSymbol, <PropertyAccessExpression>node.left, SymbolFlags.Method | SymbolFlags.Property, SymbolFlags.None);
|
||||
}
|
||||
|
||||
function bindCallExpression(node: CallExpression) {
|
||||
// We're only inspecting call expressions to detect CommonJS modules, so we can skip
|
||||
// this check if we've already seen the module indicator
|
||||
|
||||
+29
-14
@@ -122,8 +122,8 @@ namespace ts {
|
||||
|
||||
let noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
||||
|
||||
let anySignature = createSignature(undefined, undefined, emptyArray, anyType, undefined, 0, false, false);
|
||||
let unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, undefined, 0, false, false);
|
||||
let anySignature = createSignature(undefined, undefined, emptyArray, undefined, anyType, undefined, 0, false, false);
|
||||
let unknownSignature = createSignature(undefined, undefined, emptyArray, undefined, unknownType, undefined, 0, false, false);
|
||||
|
||||
let globals: SymbolTable = {};
|
||||
|
||||
@@ -3247,12 +3247,13 @@ namespace ts {
|
||||
resolveObjectTypeMembers(type, source, typeParameters, typeArguments);
|
||||
}
|
||||
|
||||
function createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], parameters: Symbol[],
|
||||
function createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], parameters: Symbol[], kind: SignatureKind,
|
||||
resolvedReturnType: Type, typePredicate: TypePredicate, minArgumentCount: number, hasRestParameter: boolean, hasStringLiterals: boolean): Signature {
|
||||
let sig = new Signature(checker);
|
||||
sig.declaration = declaration;
|
||||
sig.typeParameters = typeParameters;
|
||||
sig.parameters = parameters;
|
||||
sig.kind = kind;
|
||||
sig.resolvedReturnType = resolvedReturnType;
|
||||
sig.typePredicate = typePredicate;
|
||||
sig.minArgumentCount = minArgumentCount;
|
||||
@@ -3262,13 +3263,13 @@ namespace ts {
|
||||
}
|
||||
|
||||
function cloneSignature(sig: Signature): Signature {
|
||||
return createSignature(sig.declaration, sig.typeParameters, sig.parameters, sig.resolvedReturnType, sig.typePredicate,
|
||||
return createSignature(sig.declaration, sig.typeParameters, sig.parameters, sig.kind, sig.resolvedReturnType, sig.typePredicate,
|
||||
sig.minArgumentCount, sig.hasRestParameter, sig.hasStringLiterals);
|
||||
}
|
||||
|
||||
function getDefaultConstructSignatures(classType: InterfaceType): Signature[] {
|
||||
if (!getBaseTypes(classType).length) {
|
||||
return [createSignature(undefined, classType.localTypeParameters, emptyArray, classType, undefined, 0, false, false)];
|
||||
return [createSignature(undefined, classType.localTypeParameters, emptyArray, SignatureKind.Construct, classType, undefined, 0, false, false)];
|
||||
}
|
||||
let baseConstructorType = getBaseConstructorTypeOfClass(classType);
|
||||
let baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct);
|
||||
@@ -3788,7 +3789,26 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
links.resolvedSignature = createSignature(declaration, typeParameters, parameters, returnType, typePredicate,
|
||||
let kind: SignatureKind;
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
case SyntaxKind.ConstructorType:
|
||||
kind = SignatureKind.Construct;
|
||||
break;
|
||||
default:
|
||||
if (declaration.symbol.inferredConstructor) {
|
||||
kind = SignatureKind.Construct;
|
||||
let proto = declaration.symbol.members["prototype"];
|
||||
returnType = createAnonymousType(createSymbol(SymbolFlags.None, "__jsClass"), proto.members, emptyArray, emptyArray, undefined, undefined);
|
||||
}
|
||||
else {
|
||||
kind = SignatureKind.Call;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
links.resolvedSignature = createSignature(declaration, typeParameters, parameters, kind, returnType, typePredicate,
|
||||
minArgumentCount, hasRestParameter(declaration), hasStringLiterals);
|
||||
}
|
||||
return links.resolvedSignature;
|
||||
@@ -3905,7 +3925,7 @@ namespace ts {
|
||||
// object type literal or interface (using the new keyword). Each way of declaring a constructor
|
||||
// will result in a different declaration kind.
|
||||
if (!signature.isolatedSignatureType) {
|
||||
let isConstructor = signature.declaration.kind === SyntaxKind.Constructor || signature.declaration.kind === SyntaxKind.ConstructSignature;
|
||||
let isConstructor = signature.kind === SignatureKind.Construct;
|
||||
let type = <ResolvedType>createObjectType(TypeFlags.Anonymous | TypeFlags.FromSignature);
|
||||
type.members = emptySymbols;
|
||||
type.properties = emptyArray;
|
||||
@@ -4611,6 +4631,7 @@ namespace ts {
|
||||
}
|
||||
let result = createSignature(signature.declaration, freshTypeParameters,
|
||||
instantiateList(signature.parameters, mapper, instantiateSymbol),
|
||||
signature.kind,
|
||||
instantiateType(signature.resolvedReturnType, mapper),
|
||||
freshTypePredicate,
|
||||
signature.minArgumentCount, signature.hasRestParameter, signature.hasStringLiterals);
|
||||
@@ -9359,13 +9380,7 @@ namespace ts {
|
||||
return voidType;
|
||||
}
|
||||
if (node.kind === SyntaxKind.NewExpression) {
|
||||
let declaration = signature.declaration;
|
||||
|
||||
if (declaration &&
|
||||
declaration.kind !== SyntaxKind.Constructor &&
|
||||
declaration.kind !== SyntaxKind.ConstructSignature &&
|
||||
declaration.kind !== SyntaxKind.ConstructorType) {
|
||||
|
||||
if (signature.kind === SignatureKind.Call) {
|
||||
// When resolved signature is a call signature (and not a construct signature) the result type is any
|
||||
if (compilerOptions.noImplicitAny) {
|
||||
error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type);
|
||||
|
||||
@@ -1714,6 +1714,7 @@ namespace ts {
|
||||
/* @internal */ parent?: Symbol; // Parent symbol
|
||||
/* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol
|
||||
/* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums
|
||||
/* @internal */ inferredConstructor?: boolean; // A function promoted to constructor as the result of a prototype property assignment
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -1958,6 +1959,7 @@ namespace ts {
|
||||
declaration: SignatureDeclaration; // Originating declaration
|
||||
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic)
|
||||
parameters: Symbol[]; // Parameters
|
||||
kind: SignatureKind; // Call or Construct
|
||||
typePredicate?: TypePredicate; // Type predicate
|
||||
/* @internal */
|
||||
resolvedReturnType: Type; // Resolved return type
|
||||
|
||||
@@ -1086,6 +1086,35 @@ namespace ts {
|
||||
((<PropertyAccessExpression>(<BinaryExpression>expression).left).name.text === "exports");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this expression is an assignment to the given named property
|
||||
*/
|
||||
function isAssignmentToProperty(expression: Node, name?: string): expression is BinaryExpression {
|
||||
return (expression.kind === SyntaxKind.BinaryExpression) &&
|
||||
((<BinaryExpression>expression).operatorToken.kind === SyntaxKind.EqualsToken) &&
|
||||
isNamedPropertyAccess((<BinaryExpression>expression).left, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this expression is a PropertyAccessExpression where the property name is the provided name
|
||||
*/
|
||||
function isNamedPropertyAccess(expression: Node, name?: string): expression is PropertyAccessExpression {
|
||||
return expression.kind === SyntaxKind.PropertyAccessExpression &&
|
||||
(!name || (<PropertyAccessExpression>expression).name.text === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the node is an assignment in the form 'id1.prototype.id2 = expr' where id1 and id2
|
||||
* are any identifier.
|
||||
* This function does not test if the node is in a JavaScript file or not.
|
||||
*/
|
||||
export function isPrototypePropertyAssignment(expression: Node): expression is BinaryExpression {
|
||||
return isAssignmentToProperty(expression) &&
|
||||
isNamedPropertyAccess(expression.left) &&
|
||||
isNamedPropertyAccess((<PropertyAccessExpression>expression.left).expression, "prototype") &&
|
||||
(<PropertyAccessExpression>(<PropertyAccessExpression>expression.left).expression).expression.kind === SyntaxKind.Identifier;
|
||||
}
|
||||
|
||||
export function getExternalModuleName(node: Node): Expression {
|
||||
if (node.kind === SyntaxKind.ImportDeclaration) {
|
||||
return (<ImportDeclaration>node).moduleSpecifier;
|
||||
|
||||
@@ -735,6 +735,7 @@ namespace ts {
|
||||
declaration: SignatureDeclaration;
|
||||
typeParameters: TypeParameter[];
|
||||
parameters: Symbol[];
|
||||
kind: SignatureKind;
|
||||
resolvedReturnType: Type;
|
||||
minArgumentCount: number;
|
||||
hasRestParameter: boolean;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
///<reference path="fourslash.ts" />
|
||||
|
||||
// Assignments to the 'prototype' property of a function create a class
|
||||
|
||||
// @allowNonTsExtensions: true
|
||||
// @Filename: myMod.js
|
||||
//// function myCtor(x) {
|
||||
//// }
|
||||
//// myCtor.prototype.foo = function() { return 32 };
|
||||
//// myCtor.prototype.bar = function() { return '' };
|
||||
////
|
||||
//// var m = new myCtor(10);
|
||||
//// m/*1*/
|
||||
//// var x = m.foo();
|
||||
//// x/*2*/
|
||||
//// var y = m.bar();
|
||||
//// y/*3*/
|
||||
|
||||
goTo.marker('1');
|
||||
edit.insert('.');
|
||||
verify.memberListContains('foo', undefined, undefined, 'method');
|
||||
edit.insert('foo');
|
||||
|
||||
edit.backspace();
|
||||
edit.backspace();
|
||||
|
||||
goTo.marker('2');
|
||||
edit.insert('.');
|
||||
verify.memberListContains('toFixed', undefined, undefined, 'method');
|
||||
verify.not.memberListContains('substr', undefined, undefined, 'method');
|
||||
edit.backspace();
|
||||
|
||||
goTo.marker('3');
|
||||
edit.insert('.');
|
||||
verify.memberListContains('substr', undefined, undefined, 'method');
|
||||
verify.not.memberListContains('toFixed', undefined, undefined, 'method');
|
||||
Reference in New Issue
Block a user