/*******************************************************************************
* Copyright (c) 2005, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.jsdt.core.infer;
import org.eclipse.wst.jsdt.core.ast.ASTVisitor;
import org.eclipse.wst.jsdt.core.ast.IASTNode;
import org.eclipse.wst.jsdt.core.ast.IAbstractFunctionDeclaration;
import org.eclipse.wst.jsdt.core.ast.IAbstractVariableDeclaration;
import org.eclipse.wst.jsdt.core.ast.IAllocationExpression;
import org.eclipse.wst.jsdt.core.ast.IArgument;
import org.eclipse.wst.jsdt.core.ast.IAssignment;
import org.eclipse.wst.jsdt.core.ast.IExpression;
import org.eclipse.wst.jsdt.core.ast.IFalseLiteral;
import org.eclipse.wst.jsdt.core.ast.IFieldReference;
import org.eclipse.wst.jsdt.core.ast.IFunctionCall;
import org.eclipse.wst.jsdt.core.ast.IFunctionDeclaration;
import org.eclipse.wst.jsdt.core.ast.IFunctionExpression;
import org.eclipse.wst.jsdt.core.ast.IJsDoc;
import org.eclipse.wst.jsdt.core.ast.ILocalDeclaration;
import org.eclipse.wst.jsdt.core.ast.INumberLiteral;
import org.eclipse.wst.jsdt.core.ast.IObjectLiteral;
import org.eclipse.wst.jsdt.core.ast.IObjectLiteralField;
import org.eclipse.wst.jsdt.core.ast.IProgramElement;
import org.eclipse.wst.jsdt.core.ast.IReturnStatement;
import org.eclipse.wst.jsdt.core.ast.IScriptFileDeclaration;
import org.eclipse.wst.jsdt.core.ast.ISingleNameReference;
import org.eclipse.wst.jsdt.core.ast.IStringLiteral;
import org.eclipse.wst.jsdt.core.ast.IThisReference;
import org.eclipse.wst.jsdt.core.ast.ITrueLiteral;
import org.eclipse.wst.jsdt.core.compiler.CharOperation;
import org.eclipse.wst.jsdt.internal.compiler.ast.ASTNode;
import org.eclipse.wst.jsdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.Argument;
import org.eclipse.wst.jsdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.wst.jsdt.internal.compiler.ast.ArrayReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.Assignment;
import org.eclipse.wst.jsdt.internal.compiler.ast.BinaryExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.Expression;
import org.eclipse.wst.jsdt.internal.compiler.ast.FieldReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.FunctionExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.Javadoc;
import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocSingleNameReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.MessageSend;
import org.eclipse.wst.jsdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.ObjectLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.OperatorIds;
import org.eclipse.wst.jsdt.internal.compiler.ast.ProgramElement;
import org.eclipse.wst.jsdt.internal.compiler.ast.Reference;
import org.eclipse.wst.jsdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.StringLiteral;
import org.eclipse.wst.jsdt.internal.compiler.ast.UnaryExpression;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.wst.jsdt.internal.compiler.util.HashtableOfObject;
import org.eclipse.wst.jsdt.internal.compiler.util.Util;
/**
* The default inference engine.
*
* <p>Clients may subclass this class but should expect some breakage by future releases.</p>
*
* Provisional API: This class/interface is part of an interim API that is still under development and expected to
* change significantly before reaching stability. It is being made available at this early stage to solicit feedback
* from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken
* (repeatedly) as the API evolves.
*/
public class InferEngine extends ASTVisitor implements IInferEngine {
InferOptions inferOptions;
CompilationUnitDeclaration compUnit;
Context [] contexts=new Context[100];
int contextPtr=-1;
Context currentContext=new Context();
protected int passNumber=1;
boolean isTopLevelAnonymousFunction;
int anonymousCount=0;
public InferrenceProvider inferenceProvider;
public InferredType StringType=new InferredType(new char[]{'S','t','r','i','n','g'});
public InferredType NumberType=new InferredType(new char[]{'N','u','m','b','e','r'});
public InferredType BooleanType=new InferredType(new char[]{'B','o','o','l','e','a','n'});
public InferredType FunctionType=new InferredType(InferredType.FUNCTION_NAME);
public InferredType ArrayType=new InferredType(InferredType.ARRAY_NAME);
public InferredType VoidType=new InferredType(new char[]{'v','o','i','d'});
public InferredType ObjectType=new InferredType(InferredType.OBJECT_NAME);
public InferredType GlobalType=new InferredType(InferredType.GLOBAL_NAME);
public static HashtableOfObject WellKnownTypes=new HashtableOfObject();
{
WellKnownTypes.put(InferredType.OBJECT_NAME,null);
WellKnownTypes.put(InferredType.ARRAY_NAME,null);
WellKnownTypes.put(new char[]{'S','t','r','i','n','g'},null);
WellKnownTypes.put(new char[]{'N','u','m','b','e','r'},null);
WellKnownTypes.put(new char[]{'B','o','o','l','e','a','n'},null);
WellKnownTypes.put(InferredType.FUNCTION_NAME,null);
WellKnownTypes.put(new char[]{'D','a','t','e'},null);
WellKnownTypes.put(new char[]{'M','a','t','h'},null);
WellKnownTypes.put(new char[]{'R','e','g','E','x','p'},null);
WellKnownTypes.put(new char[]{'E','r','r','o','r'},null);
}
protected InferredType inferredGlobal=null;
static final char[] CONSTRUCTOR_ID={'c','o','n','s','t','r','u','c','t','o','r'};
static class Context
{
InferredType currentType;
IFunctionDeclaration currentMethod;
/** The current assignment.*/
IAssignment currentAssignment;
/** the current declaration */
ILocalDeclaration currentLocalDeclaration;
boolean isJsDocClass;
private HashtableOfObject definedMembers;
/*
* Parent context to provide chaining when searching
* for members in scope.
*/
private Context parent = null;
/*
* Root context
*/
Context(){}
/*
* Nested context
*/
Context( Context parent )
{
this.parent = parent;
currentType = parent.currentType;
currentMethod = parent.currentMethod;
this.currentAssignment = parent.currentAssignment;
this.currentLocalDeclaration = parent.currentLocalDeclaration;
this.isJsDocClass=parent.isJsDocClass;
}
public Object getMember( char [] key ){
Object value = null;
if( definedMembers != null ){
value = definedMembers.get( key );
}
//chain lookup
if( value == null && parent != null ){
value = parent.getMember( key );
}
return value;
}
public void addMember( char [] key, Object member ){
if(key == null)
return;
if( definedMembers == null ){
definedMembers = new HashtableOfObject();
}
definedMembers.put( key, member );
}
public void setCurrentType(InferredType type)
{
this.currentType=type;
Context parentContext=this.parent;
while (parentContext!=null && parentContext.currentMethod==this.currentMethod)
{
parentContext.currentType=type;
parentContext=parentContext.parent;
}
}
}
private static boolean REPORT_INFER_TIME = false;
public InferEngine(InferOptions inferOptions)
{
this.inferOptions=inferOptions;
}
public InferEngine()
{
this.inferOptions=new InferOptions();
}
public void initialize()
{
this.contextPtr=-1;
this.currentContext=new Context();
this.passNumber=1;
this.isTopLevelAnonymousFunction=false;
this.anonymousCount=0;
this.inferredGlobal=null;
}
public void setCompilationUnit(CompilationUnitDeclaration scriptFileDeclaration) {
this.compUnit = scriptFileDeclaration;
buildDefinedMembers(scriptFileDeclaration.getStatements(),null);
}
public boolean visit(IFunctionCall functionCall) {
boolean visitChildren=handleFunctionCall(functionCall);
if (visitChildren)
{
if (this.contextPtr==-1 && functionCall.getReceiver() instanceof FunctionExpression)
this.isTopLevelAnonymousFunction=true;
}
return visitChildren;
}
public boolean visit(ILocalDeclaration localDeclaration) {
//add as a member of the current context
currentContext.addMember( localDeclaration.getName(), localDeclaration );
//create a new context for the local declaration
pushContext();
this.currentContext.currentLocalDeclaration = localDeclaration;
if (localDeclaration.getJsDoc()!=null)
{
Javadoc javadoc = (Javadoc)localDeclaration.getJsDoc();
createTypeIfNecessary(javadoc);
InferredAttribute attribute = null;
if (javadoc.memberOf!=null)
{
InferredType type = this.addType(javadoc.memberOf.getFullTypeName(),true);
int nameStart = localDeclaration.sourceStart();
attribute = type.addAttribute(localDeclaration.getName(), localDeclaration, nameStart);
handleAttributeDeclaration(attribute, localDeclaration.getInitialization());
if (localDeclaration.getInitialization()!=null)
attribute.initializationStart=localDeclaration.getInitialization().sourceStart();
attribute.type=type;
}
if (javadoc.returnType!=null)
{
InferredType type = this.addType(changePrimitiveToObject(javadoc.returnType.getFullTypeName()));
localDeclaration.setInferredType(type);
if (attribute!=null)
attribute.type=type;
}
}
// visit the function in case it defines a type
if(localDeclaration.getInitialization() instanceof IFunctionExpression) {
boolean keepVisiting = handleFunctionExpressionLocalDeclaration(localDeclaration);
if(!keepVisiting) {
return false;
}
}
if (localDeclaration.getInferredType()==null && localDeclaration.getInitialization()!=null)
{
if(localDeclaration.getInitialization() instanceof MessageSend) {
handleFunctionCall((IFunctionCall)localDeclaration.getInitialization(), (LocalDeclaration) localDeclaration);
} else {
localDeclaration.setInferredType(getTypeOf(localDeclaration.getInitialization()));
}
}
return true;
}
/**
* @see org.eclipse.wst.jsdt.core.ast.ASTVisitor#endVisit(org.eclipse.wst.jsdt.core.ast.ILocalDeclaration)
*/
public void endVisit(ILocalDeclaration localDeclaration) {
popContext();
}
private void createTypeIfNecessary(Javadoc javadoc) {
if (javadoc.memberOf!=null)
{
char [][]namespace={};
char[][] typeName = javadoc.memberOf.getTypeName();
if (javadoc.namespace!=null)
{
namespace=javadoc.namespace.getTypeName();
}
char [] name=CharOperation.concat(
CharOperation.concatWith(namespace, '.'),
CharOperation.concatWith(typeName, '.'),
'.');
this.currentContext.currentType=addType(name);
if (javadoc.extendsType!=null)
{
char[] superName = CharOperation.concatWith(javadoc.extendsType.getTypeName(),'.');
this.currentContext.currentType.superClass=addType(superName);
}
this.currentContext.isJsDocClass=true;
}
}
public boolean visit(IAssignment assignment) {
pushContext();
this.currentContext.currentAssignment = assignment;
IExpression assignmentExpression=assignment.getExpression();
if (handlePotentialType(assignment))
{
}
else if (assignmentExpression instanceof FunctionExpression)
{
boolean keepVisiting= handleFunctionExpressionAssignment(assignment);
if (!keepVisiting)
return false;
}
else if (assignmentExpression instanceof SingleNameReference && this.currentContext.currentType !=null &&
isThis(assignment.getLeftHandSide()))
{
ISingleNameReference snr=(ISingleNameReference)assignmentExpression;
Object object = this.currentContext.getMember( snr.getToken() );
IFieldReference fieldReference=(IFieldReference)assignment.getLeftHandSide();
char [] memberName = fieldReference.getToken();
InferredMember member = null;
int nameStart = fieldReference.sourceEnd() - memberName.length + 1;
/*
* this.foo = bar //bar is a function
*/
if( object instanceof MethodDeclaration ){
MethodDeclaration method=(MethodDeclaration)object;
member = this.currentContext.currentType.addMethod(memberName, method, nameStart);
}
/*
* this.foo = bar //assume that bar is not a function and create a new attribute in the current type
*/
else{
member = this.currentContext.currentType.addAttribute(memberName, assignment, nameStart);
handleAttributeDeclaration((InferredAttribute) member, assignment.getExpression());
if (((InferredAttribute) member).type == null)
((InferredAttribute)member).type = getTypeOf( assignmentExpression );
}
//setting location
if( member != null ){
member.isStatic = false; //this is a not static member because it is being set on the this
}
}
/*
* foo = {};
*/
else if ( assignmentExpression instanceof IObjectLiteral && assignment.getLeftHandSide() instanceof ISingleNameReference ){
IAbstractVariableDeclaration varDecl = getVariable( assignment.getLeftHandSide() );
if(varDecl == null) {
IAssignment existing = getAssignment(assignment.getLeftHandSide());
if(existing == null) {
//add as a member of the parent context (the current context is for the assignment)
currentContext.parent.addMember( getName(assignment.getLeftHandSide()), assignment );
}
}
if( varDecl != null ){
InferredType type = varDecl.getInferredType();
if( type == null ){
//create an anonymous type based on the ObjectLiteral
type = getTypeOf( assignmentExpression );
varDecl.setInferredType(type);
return true;
}
else
return false; //
} else {
IAssignment assignmentDecl = getAssignment(assignment.getLeftHandSide());
if( assignmentDecl != null ){
InferredType type = assignmentDecl.getInferredType();
if( type == null ){
//create an anonymous type based on the ObjectLiteral
type = getTypeOf( assignmentExpression );
assignmentDecl.setInferredType(type);
return true;
}
else
return false; //
}
}
}
/*
* foo.bar = {};
*
*/
else if ( assignmentExpression instanceof IObjectLiteral && assignment.getLeftHandSide() instanceof FieldReference ){
FieldReference fRef = (FieldReference)assignment.getLeftHandSide();
boolean isKnownName=fRef.receiver.isThis() && isKnownType(fRef.getToken()) &&
(this.inferredGlobal!=null && this.inferredGlobal==this.currentContext.currentType);
if (isKnownName || (this.inferOptions.useAssignments && passNumber == 2 ))
{
InferredType receiverType = getInferredType( fRef.receiver );
if (receiverType==null && this.passNumber==2)
receiverType=getInferredType2(fRef.receiver );
if( receiverType != null ){
//check if there is an attribute already created
InferredAttribute attr = receiverType.findAttribute( fRef.getToken() );
//ignore if the attribute exists and has a type
if( !(attr != null && attr.type != null) ){
int nameStart = (int)(fRef.nameSourcePosition>>>32);
attr = receiverType.addAttribute(fRef.getToken(), assignment, nameStart);
handleAttributeDeclaration(attr, assignment.getExpression());
attr.type = getTypeOf( assignmentExpression );
if (isKnownName && attr.type.isAnonymous)
{
InferredType existingType = compUnit.findInferredType( fRef.getToken() ) ;
if (existingType!=null)
attr.type=existingType;
else
{
compUnit.inferredTypesHash.removeKey(attr.type.name);
attr.type.name=fRef.getToken();
compUnit.inferredTypesHash.put(attr.type.name, attr.type);
}
}
/*
* determine if static
*
* check if the receiver is a type
*/
char [] possibleTypeName = constructTypeName( fRef.receiver );
if( receiverType.allStatic ||
(possibleTypeName != null && compUnit.findInferredType( possibleTypeName ) != null ))
attr.isStatic = true;
else
attr.isStatic = false;
return false; //done with this
}
}
}
}
else if ( assignmentExpression instanceof AllocationExpression &&
((AllocationExpression)assignmentExpression).member instanceof FunctionExpression){
handleFunctionExpressionAssignment((Assignment)assignment);
}
else if ( assignmentExpression instanceof Assignment &&
((Assignment)assignmentExpression).expression instanceof FunctionExpression){
handleFunctionExpressionAssignment((Assignment)assignment);
}
else
{
/*
* foo.bar = ? //? is not {} and not a function
*/
if (this.inferOptions.useAssignments)
{
if( assignment.getLeftHandSide() instanceof FieldReference || assignment.getLeftHandSide() instanceof ArrayReference){
Reference ref = (Reference) assignment.getLeftHandSide();
Expression receiver = null;
char[] attName = null;
int nameStart = 0;
if(ref instanceof FieldReference) {
receiver = ((FieldReference)ref).receiver;
attName = ((FieldReference)ref).token;
nameStart=(int)(((FieldReference)ref).nameSourcePosition>>>32);
} else if(ref instanceof ArrayReference) {
if(((ArrayReference)ref).position instanceof StringLiteral) {
receiver = ((ArrayReference)ref).receiver;
attName = ((StringLiteral)((ArrayReference)ref).position).source();
nameStart = ((StringLiteral)((ArrayReference)ref).position).sourceStart + 1;
}
}
InferredType receiverType = getInferredType( receiver );
if (receiverType==null)
{
IFunctionDeclaration function = getDefinedFunction(receiver);
if (function!=null)
{
char [] typeName = constructTypeName(receiver);
if (typeName!=null)
receiverType=addType(typeName);
}
}
if (receiverType==null && this.passNumber==2)
receiverType=getInferredType2(receiver );
if( receiverType != null && attName != null){
//check if there is an attribute already created
InferredMethod method=null;
InferredAttribute attr = receiverType.findAttribute( attName );
if (attr==null)
method = receiverType.findMethod(attName, null);
//ignore if the attribute exists and has a type
if( (method==null && attr==null) || (method==null && attr != null && attr.type == null) ){
// attr.type =
IFunctionDeclaration definedFunction=null;
InferredType exprType = getTypeOf( assignmentExpression );
if (exprType==null)
definedFunction = getDefinedFunction(assignmentExpression );
if (definedFunction!=null)
{
method = receiverType.addMethod(attName, definedFunction, nameStart);
method.isStatic=receiverType.allStatic;
}
else
{
int nameStart_ = nameStart;
attr = receiverType.addAttribute(attName, assignment, nameStart_);
handleAttributeDeclaration(attr, assignmentExpression);
attr.type=exprType;
/*
* determine if static
*
* check if the receiver is a type
*/
char [] possibleTypeName = constructTypeName( receiver );
if( receiverType.allStatic||
(possibleTypeName != null && compUnit.findInferredType( possibleTypeName ) != null ))
attr.isStatic = true;
else
attr.isStatic = false;
}
return false; //done with this
}
}
}
}
}
return true; // do nothing by default, keep traversing
}
protected InferredType getInferredType2(IExpression fieldReceiver)
{
InferredType receiverType=null;
IAbstractVariableDeclaration var=getVariable(fieldReceiver);
if (var!=null)
{
receiverType=createAnonymousType(var);
}
else
{
if (this.inferredGlobal!=null && fieldReceiver instanceof ISingleNameReference)
{
char []name=((ISingleNameReference)fieldReceiver).getToken();
InferredAttribute attr=this.inferredGlobal.findAttribute(name);
if (attr!=null)
receiverType=attr.type;
}
}
return receiverType;
}
private InferredType createAnonymousType(IAbstractVariableDeclaration var) {
InferredType currentType = var.getInferredType();
if (currentType==null || !currentType.isAnonymous)
{
InferredType type=createAnonymousType(var.getName(), currentType);
var.setInferredType(type);
}
return var.getInferredType();
}
private InferredType createAnonymousType(IAssignment assignment) {
InferredType currentType = assignment.getInferredType();
if (currentType==null || !currentType.isAnonymous)
{
InferredType type=createAnonymousType(getName(assignment.getLeftHandSide()), currentType);
assignment.setInferredType(type);
}
return assignment.getInferredType();
}
protected InferredType createAnonymousType(char[] possibleTypeName, InferredType currentType) {
char []name;
if (this.isKnownType(possibleTypeName))
{
name=possibleTypeName;
}
else
{
char[] cs = String.valueOf(this.anonymousCount++).toCharArray();
name = CharOperation.concat(ANONYMOUS_PREFIX,possibleTypeName,cs);
}
InferredType type = addType(name,true);
type.isAnonymous=true;
if (currentType!=null)
type.superClass=currentType;
return type;
}
/*
* Creates an anonymous type based in the location in the document. This information is used
* to avoid creating duplicates because of the 2-pass nature of this engine.
*/
private InferredType createAnonymousType( IObjectLiteral objLit ) {
if (objLit.getInferredType()!=null)
return objLit.getInferredType();
char []name = createAnonymousTypeName(objLit);
InferredType anonType = addType(name,true);
anonType.isAnonymous=true;
anonType.isObjectLiteral=true;
anonType.superClass = ObjectType;
anonType.sourceStart = objLit.sourceStart();
anonType.sourceEnd = objLit.sourceEnd();
populateType( anonType, objLit , false);
return anonType;
}
/**
* <p>Creates an anonymous type name for the given {@link IASTNode}</p>
*
* @param node create the anonymous type name off the location of this node
* @return an anonymous type name based off the given nodes location
*/
protected static char[] createAnonymousTypeName(IASTNode node) {
char [] loc = (String.valueOf( node.sourceStart() ) + '_' + String.valueOf( node.sourceEnd() )).toCharArray();
return CharOperation.concat( ANONYMOUS_PREFIX, ANONYMOUS_CLASS_ID, loc );
}
/**
* handle the inferrencing for an assigment whose right hand side is a function expression
* @param the assignment AST node
* @return true if handled
*/
protected boolean handleFunctionExpressionAssignment(IAssignment assignment)
{
IFunctionExpression functionExpression=null;
if (assignment.getExpression() instanceof IFunctionExpression)
functionExpression=(IFunctionExpression)assignment.getExpression();
else if (assignment.getExpression() instanceof IAllocationExpression)
functionExpression=(IFunctionExpression)((IAllocationExpression)assignment.getExpression()).getMember();
else if (assignment.getExpression() instanceof IAssignment)
functionExpression=(FunctionExpression)((IAssignment)assignment.getExpression()).getExpression();
MethodDeclaration methodDeclaration = functionExpression.getMethodDeclaration();
char [] possibleTypeName = constructTypeName( assignment.getLeftHandSide() );
InferredType type = null;
if( possibleTypeName != null )
{
type = compUnit.findInferredType( possibleTypeName );
if (type==null && isPossibleClassName(possibleTypeName))
{
type=addType(possibleTypeName,true);
}
if (type==null && methodDeclaration.getJsDoc()!=null && ((Javadoc)methodDeclaration.getJsDoc()).isConstructor)
{
type=addType(possibleTypeName,true);
handleJSDocConstructor(type, methodDeclaration, assignment.sourceStart());
}
}
if (type!=null) // isConstructor
{
if (this.inferOptions.useInitMethod)
{
this.currentContext.currentType=type;
type.isDefinition=true;
int nameStart = assignment.getLeftHandSide().sourceStart();
type.addConstructorMethod(type.name, methodDeclaration, nameStart);
type.updatePositions(nameStart, assignment.getExpression().sourceEnd());
}
}
else // could be method
{
if (assignment.getLeftHandSide() instanceof FieldReference || assignment.getLeftHandSide() instanceof ArrayReference)
{
Reference ref = (Reference) assignment.getLeftHandSide();
Expression receiver = null;
char[] methodName = null;
int nameStart = 0;
if(ref instanceof FieldReference) {
receiver = ((FieldReference)ref).receiver;
methodName = ((FieldReference)ref).token;
nameStart=(int)(((FieldReference)ref).nameSourcePosition>>>32);
} else if(ref instanceof ArrayReference) {
if(((ArrayReference)ref).position instanceof StringLiteral) {
receiver = ((ArrayReference)ref).receiver;
methodName = ((StringLiteral)((ArrayReference)ref).position).source();
nameStart = ((StringLiteral)((ArrayReference)ref).position).sourceStart + 1;
}
}
InferredType receiverType = getInferredType( receiver );
if( receiverType != null && methodName != null){
//check if there is a member method already created
InferredMethod method = receiverType.findMethod( methodName, methodDeclaration );
if( method == null ){
//create member method if it does not exist
method = receiverType.addMethod(methodName, methodDeclaration, nameStart);
receiverType.updatePositions(assignment.sourceStart(), assignment.sourceEnd()); // @GINO: not sure if necessary
receiverType.isDefinition=true;
/*
* determine if static
*
* check if the receiver is a type
*/
char [] possibleInTypeName = constructTypeName( receiver );
if( receiverType.allStatic ||
(possibleInTypeName != null && compUnit.findInferredType( possibleInTypeName ) != null) )
method.isStatic = true;
else
method.isStatic = false;
return true; //keep visiting to get return type
}
else
return false; //no need to visit again
}
else if (this.passNumber==2 && methodName != null) // create anonymous class
{
receiverType = getInferredType2(receiver);
if (receiverType!=null)
{
InferredMethod method = receiverType.addMethod(methodName, methodDeclaration, nameStart);
method.isStatic=receiverType.isAnonymous;
receiverType.updatePositions(assignment.sourceStart(), assignment.sourceEnd());
}
}
}
else if (assignment.getLeftHandSide() instanceof SingleNameReference)
{
if(methodDeclaration.selector == null) {
methodDeclaration.potentialName = ((SingleNameReference)assignment.getLeftHandSide()).token;
}
}
}
return true;
}
/**
* <p>Handle a local declaration who's right hand side is a function.</p>
* <p>Use case:</p><pre>foo.bar.Test = function() { this.num = 42; }</pre>
*
* @param localDeclaration {@link ILocalDeclaration} to attempt to infer a type from
* @return <code>true</code> if keep visiting, <code>false</code> otherwise.
*/
private boolean handleFunctionExpressionLocalDeclaration(ILocalDeclaration localDeclaration) {
boolean keepVisiting = true;
IFunctionExpression functionExpression=null;
IExpression expression = localDeclaration.getInitialization();
if (expression instanceof IFunctionExpression) {
functionExpression=(IFunctionExpression)expression;
} else if (expression instanceof IAllocationExpression) {
functionExpression=(IFunctionExpression)((IAllocationExpression)expression).getMember();
} else if (expression instanceof IAssignment) {
functionExpression=(FunctionExpression)((IAssignment)expression).getExpression();
}
MethodDeclaration methodDeclaration = functionExpression.getMethodDeclaration();
char [] possibleTypeName = localDeclaration.getName();
InferredType type = null;
if( possibleTypeName != null ) {
type = compUnit.findInferredType( possibleTypeName );
if (type == null && isPossibleClassName(possibleTypeName)) {
type = addType(possibleTypeName,true);
}
if (type == null && methodDeclaration.getJsDoc()!= null &&
((Javadoc)methodDeclaration.getJsDoc()).isConstructor) {
type = addType(possibleTypeName,true);
handleJSDocConstructor(type, methodDeclaration, localDeclaration.sourceStart());
}
}
if (type!=null) { // isConstructor
if (this.inferOptions.useInitMethod) {
this.currentContext.currentType=type;
type.isDefinition=true;
int nameStart = localDeclaration.sourceStart();
type.addConstructorMethod(type.name, methodDeclaration, nameStart);
type.updatePositions(nameStart, localDeclaration.getInitialization().sourceEnd());
}
keepVisiting = false;
}
return keepVisiting;
}
/**
* @param assignment
* @return whether a type was not created for this assignment
*/
protected boolean handlePotentialType(IAssignment assignment) {
IExpression lhs = assignment.getLeftHandSide();
if (lhs instanceof FieldReference) {
FieldReference fieldReference = (FieldReference) lhs;
/*
* foo.prototype = ?
*/
if (fieldReference.isPrototype())
{
/*
* When encountering a prototype, we are going to assume that the
* receiver is a type.
*
* If the type had not been inferred, it will be added at this point
*/
InferredType newType = null;
char [] possibleTypeName = constructTypeName( fieldReference.getReceiver() );
if( possibleTypeName != null )
newType = compUnit.findInferredType( possibleTypeName );
else
return true; //no type created
//create the new type if not found
if( newType == null ){
newType = addType( possibleTypeName ,true);
}
newType.isDefinition=true;
// char[] typeName = getTypeName(fieldReference.receiver);
// Object object = currentContext.definedMembers.get(typeName);
//
// if (object instanceof Argument)
// return false;
newType.updatePositions(assignment.sourceStart(), assignment.sourceEnd());
/*
* foo.prototype = new ...
*/
if (assignment.getExpression() instanceof IAllocationExpression)
{
//setting the super type
IAllocationExpression allocationExpression =(IAllocationExpression)assignment.getExpression();
InferredType superType = null;
char [] possibleSuperTypeName = constructTypeName( allocationExpression.getMember() );
if( possibleSuperTypeName != null ){
superType = compUnit.findInferredType( possibleSuperTypeName );
if( superType == null )
superType = addType( possibleSuperTypeName );
//check if it is set already because it might be set by jsdocs
if( newType.superClass == null )
newType.superClass = superType;
}
return true;
}
/*
* foo.prototype = {...}
*/
else if( assignment.getExpression() instanceof IObjectLiteral ){
//rather than creating an anonymous type, is better just to set the members directly
//on newType
populateType( newType, (IObjectLiteral)assignment.getExpression(),false );
//check if it is set already because it might be set by jsdocs
if( newType.superClass == null )
newType.superClass = ObjectType;
return true;
}
}
/*
* foo.prototype.bar = ?
*/
else if ( fieldReference.receiver.isPrototype() )
{
FieldReference prototype = (FieldReference) fieldReference.receiver;
InferredType newType = null;
char[] possibleTypeName = constructTypeName( prototype.receiver );
if( possibleTypeName != null )
newType = compUnit.findInferredType( possibleTypeName );
else
return true; //no type created
//create the new type if not found
if( newType == null ){
newType = addType( possibleTypeName );
}
newType.isDefinition = true;
// char[] typeName = getTypeName(prototype.receiver);
// Object receiverDef = currentContext.definedMembers.get(typeName);
// if (receiverDef instanceof Argument)
// return false;
// InferredType newType = addType(typeName);
// newType.isDefinition=true;
newType.updatePositions(assignment.sourceStart(), assignment.sourceEnd());
//prevent Object literal based anonymous types from being created more than once
if( passNumber == 1 && assignment.getExpression() instanceof IObjectLiteral ){
return false;
}
char[] memberName = fieldReference.token;
int nameStart= (int)(fieldReference.nameSourcePosition >>> 32);
InferredType typeOf = (assignment.getJsDoc() != null && assignment.getJsDoc() instanceof Javadoc && ((Javadoc) assignment.getJsDoc()).returnType != null) ? this.addType(changePrimitiveToObject(((Javadoc) assignment.getJsDoc()).returnType.getFullTypeName())) : getTypeOf(assignment.getExpression());
IFunctionDeclaration methodDecl=null;
if (typeOf==null || typeOf==FunctionType)
methodDecl=getDefinedFunction(assignment.getExpression());
if (methodDecl!=null)
{
InferredMember method = newType.addMethod(memberName, methodDecl, nameStart);
}
// http://bugs.eclipse.org/269053 - constructor property not supported in JSDT
else /*if (!CharOperation.equals(CONSTRUCTOR_ID, memberName))*/
{
InferredAttribute attribute = newType.addAttribute(memberName, assignment, nameStart);
handleAttributeDeclaration(attribute, assignment.getExpression());
attribute.initializationStart=assignment.getExpression().sourceStart();
if (attribute.type==null)
attribute.type=typeOf;
}
return true;
} else if(fieldReference.receiver instanceof IThisReference) {
InferredType newType = null;
IFunctionDeclaration parentMethod = this.currentContext.currentMethod;
IAssignment parentAssignment;
ILocalDeclaration parentLocalDeclaration;
char[] newTypeName = null;
/* if there is a current assignment and LHS is a function and that function
* is the current method then use the RHS as the type name
* else if there is a current local declaration and the LHS is a function and
* that function is the current method then use the RHS as the type name
* else if the parent method has a name use that as the type name
*/
if(this.currentContext.parent != null &&
(parentAssignment = this.currentContext.parent.currentAssignment) != null &&
parentAssignment.getExpression() instanceof IFunctionExpression &&
((IFunctionExpression)parentAssignment.getExpression()).getMethodDeclaration() == parentMethod) {
newTypeName = Util.getTypeName(parentAssignment.getLeftHandSide());
} else if(this.currentContext.parent != null &&
(parentLocalDeclaration = this.currentContext.parent.currentLocalDeclaration) != null &&
parentLocalDeclaration.getInitialization() instanceof IFunctionExpression &&
((IFunctionExpression)parentLocalDeclaration.getInitialization()).getMethodDeclaration() == parentMethod) {
newTypeName = parentLocalDeclaration.getName();
}else if( parentMethod != null && parentMethod.getName() != null ) {
newTypeName = parentMethod.getName();
}
//if calculated new type name, use it to create a new type
if(newTypeName != null) {
newType = compUnit.findInferredType(newTypeName);
//create the new type if not found
if(newType == null) {
newType = addType(newTypeName);
}
} else {
return false; //no type to create
}
newType.isDefinition = true;
newType.updatePositions(assignment.sourceStart(), assignment.sourceEnd());
//prevent Object literal based anonymous types from being created more than once
if( passNumber == 1 && assignment.getExpression() instanceof IObjectLiteral ){
return false;
}
char[] memberName = fieldReference.token;
int nameStart= (int)(fieldReference.nameSourcePosition >>> 32);
InferredType typeOf = getTypeOf(assignment.getExpression());
IFunctionDeclaration methodDecl=null;
if (typeOf==null || typeOf==FunctionType)
methodDecl=getDefinedFunction(assignment.getExpression());
if (methodDecl!=null)
{
InferredMember method = newType.addMethod(memberName, methodDecl, nameStart);
}
// http://bugs.eclipse.org/269053 - constructor property not supported in JSDT
else /*if (!CharOperation.equals(CONSTRUCTOR_ID, memberName))*/
{
InferredAttribute attribute = newType.addAttribute(memberName, assignment, nameStart);
handleAttributeDeclaration(attribute, assignment.getExpression());
attribute.initializationStart=assignment.getExpression().sourceStart();
if (attribute.type==null)
attribute.type=typeOf;
}
return true;
}
}
return false;
}
/**
* Get the function referenced by the expression
*
* @param expression AST node
* @return the function or null
*/
protected IFunctionDeclaration getDefinedFunction(IExpression expression)
{
if (expression instanceof SingleNameReference)
{
Object object = this.currentContext.getMember( ((SingleNameReference)expression).token );
if (object instanceof AbstractMethodDeclaration)
return (MethodDeclaration)object;
} else if (expression instanceof FunctionExpression)
return ((FunctionExpression)expression).methodDeclaration;
else if (expression instanceof FieldReference)
{
FieldReference fieldReference=(FieldReference)expression;
InferredType receiverType = getInferredType( fieldReference.receiver );
if (receiverType==null && passNumber==2)
receiverType=getInferredType2( fieldReference.receiver );
if (receiverType!=null)
{
InferredMethod method = receiverType.findMethod(fieldReference.token, null);
if (method!=null)
return method.getFunctionDeclaration();
}
}
return null;
}
protected InferredType getTypeOf(IExpression expression) {
if (expression instanceof IStringLiteral) {
return StringType;
}
else if (expression instanceof INumberLiteral) {
return NumberType;
}
else if (expression instanceof IAllocationExpression)
{
IAllocationExpression allocationExpression=(IAllocationExpression)expression;
InferredType type = null;
char [] possibleTypeName = constructTypeName( allocationExpression.getMember() );
if( possibleTypeName != null ){
type = compUnit.findInferredType( possibleTypeName );
if( type == null )
type = addType( possibleTypeName );
return type;
}
}
else if (expression instanceof ISingleNameReference)
{
IAbstractVariableDeclaration varDecl = getVariable( expression );
if( varDecl != null )
return varDecl.getInferredType();
if (this.inferredGlobal!=null)
{
InferredAttribute attribute = this.inferredGlobal.findAttribute(((ISingleNameReference)expression).getToken() );
if (attribute!=null)
return attribute.type;
}
}
else if (expression instanceof FieldReference)
{
FieldReference fieldReference=(FieldReference)expression;
if (fieldReference.receiver.isThis() && currentContext.currentType!=null)
{
InferredAttribute attribute = currentContext.currentType.findAttribute(fieldReference.getToken());
if (attribute!=null)
return attribute.type;
}
}
else if (expression instanceof ArrayInitializer)
{
ArrayInitializer arrayInitializer = (ArrayInitializer)expression;
boolean typeSet=false;
InferredType memberType=null;
if (arrayInitializer.expressions!=null)
for (int i = 0; i < arrayInitializer.expressions.length; i++) {
InferredType thisType = getTypeOf(arrayInitializer.expressions[i]);
if (thisType!=null)
{
if (!thisType.equals(memberType))
if (!typeSet)
memberType=thisType;
else
memberType=null;
typeSet=true;
}
}
if (memberType!=null)
{
InferredType type = new InferredType(InferredType.ARRAY_NAME);
type.referenceClass=memberType;
return type;
}
else
return ArrayType;
} else if (expression instanceof ITrueLiteral || expression instanceof IFalseLiteral) {
return BooleanType;
}
else if ( expression instanceof IObjectLiteral ){
//create an annonymous type based on the ObjectLiteral
InferredType type = createAnonymousType( (IObjectLiteral)expression);
//set the start and end
type.sourceStart = expression.sourceStart();
type.sourceEnd = expression.sourceEnd();
return type;
} else if ( expression instanceof IThisReference ){
return this.currentContext.currentType;
}
else if (expression instanceof Assignment)
return getTypeOf(((Assignment)expression).getExpression());
else if (expression instanceof FunctionExpression)
return FunctionType;
else if(expression instanceof UnaryExpression) {
return getTypeOf(((UnaryExpression)expression).expression);
} else if(expression instanceof BinaryExpression) {
BinaryExpression bExpression = (BinaryExpression) expression;
int operator = (bExpression.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
switch(operator) {
case OperatorIds.MULTIPLY :
case OperatorIds.DIVIDE :
case OperatorIds.REMAINDER :
case OperatorIds.MINUS:
case OperatorIds.LEFT_SHIFT:
case OperatorIds.RIGHT_SHIFT:
return NumberType;
case OperatorIds.PLUS:
InferredType leftType = getTypeOf(bExpression.left);
InferredType rightType = getTypeOf(bExpression.right);
if(leftType != null && leftType.equals(StringType))
return StringType;
if(rightType != null && rightType.equals(StringType))
return StringType;
if(leftType == null || rightType == null)
return null;
if(leftType.equals(StringType) || rightType.equals(StringType)) {
return StringType;
} else if(leftType.equals(NumberType) && rightType.equals(NumberType)) {
return NumberType;
}
return null;
case OperatorIds.EQUAL_EQUAL:
case OperatorIds.EQUAL_EQUAL_EQUAL:
case OperatorIds.NOT_EQUAL:
case OperatorIds.NOT_EQUAL_EQUAL:
case OperatorIds.GREATER:
case OperatorIds.GREATER_EQUAL:
case OperatorIds.LESS:
case OperatorIds.LESS_EQUAL:
case OperatorIds.INSTANCEOF:
case OperatorIds.IN:
case OperatorIds.AND_AND:
case OperatorIds.OR_OR:
return BooleanType;
default:
return null;
}
}
return null;
}
protected void populateType(InferredType type, IObjectLiteral objLit, boolean isStatic) {
if (objLit.getInferredType()==null) {
objLit.setInferredType(type);
if (objLit.getFields() != null) {
for (int i = 0; i < objLit.getFields().length; i++) {
IObjectLiteralField field = objLit.getFields()[i];
char[] name = null;
int nameStart = -1;
if (field.getFieldName() instanceof SingleNameReference) {
SingleNameReference singleNameReference = (SingleNameReference) field.getFieldName();
name = singleNameReference.token;
nameStart = singleNameReference.sourceStart;
} else if (field.getFieldName() instanceof IStringLiteral) {
IStringLiteral stringLiteral = (IStringLiteral) field.getFieldName();
name = stringLiteral.source();
nameStart = stringLiteral.sourceStart();
} else
continue; //not supporting this case right now
Javadoc javaDoc = (Javadoc)field.getJsDoc();
InferredType returnType=null;
if (javaDoc!=null)
{
if (javaDoc.memberOf!=null)
{
char[] typeName = javaDoc.memberOf.getFullTypeName();
convertAnonymousTypeToNamed(type,typeName);
type.isDefinition=true;
}
else if (this.currentContext.isJsDocClass && javaDoc.property!=null)
{
if (type.isAnonymous )
{
InferredType previousType = this.currentContext.currentType;
if (previousType!=null)
{
copyAnonymousTypeToNamed(type,previousType);
objLit.setInferredType(type = this.currentContext.currentType = previousType);
}
}
}
if (javaDoc.returnType!=null)
{
returnType=this.addType(changePrimitiveToObject(javaDoc.returnType.getFullTypeName()));
}
}
//need to build the members of the annonymous inferred type
if (field.getInitializer() instanceof IFunctionExpression) {
IFunctionExpression functionExpression = (IFunctionExpression) field.getInitializer();
InferredMember method = type.addMethod(name,
functionExpression.getMethodDeclaration(), nameStart);
method.isStatic=isStatic;
if (javaDoc!=null)
{
functionExpression.getMethodDeclaration().modifiers=javaDoc.modifiers;
}
handleFunctionDeclarationArguments(functionExpression.getMethodDeclaration(),javaDoc);
if (returnType!=null && functionExpression.getMethodDeclaration().getInferredType() == null)
{
functionExpression.getMethodDeclaration().setInferredType(returnType);
}
} else //attribute
{
InferredAttribute attribute = type.findAttribute(name);
if (attribute == null) {
attribute = type.addAttribute(name, field.getInitializer(), nameStart);
handleAttributeDeclaration(attribute, field.getInitializer());
attribute.isStatic=isStatic;
//@GINO: recursion might not be the best idea
if (returnType!=null) {
attribute.type = returnType;
// apply (force) type onto OL initializer
if (field.getInitializer() instanceof ObjectLiteral) {
((ObjectLiteral) field.getInitializer()).setInferredType(returnType);
}
}
else
attribute.type = getTypeOf(field.getInitializer());
}
}
}
}
}
}
public void endVisit(IAssignment assignment) {
popContext();
}
protected boolean handleAttributeDeclaration(InferredAttribute attribute, IExpression initializer) {
return true;
}
protected boolean handleFunctionCall(IFunctionCall messageSend) {
return handleFunctionCall(messageSend, null);
}
protected boolean handleFunctionCall(IFunctionCall messageSend, LocalDeclaration assignmentExpression) {
return true;
}
public void endVisit(IReturnStatement returnStatement) {
// if (currentContext.currentMethod!=null)
// {
// if (returnStatement.getExpression()!=null)
// {
//
// InferredType type = getTypeOf(returnStatement.getExpression());
//
// if (currentContext.currentMethod.inferredType==VoidType)
// currentContext.currentMethod.inferredType=type;
// else if (type==null || !type.equals(currentContext.currentMethod.inferredType))
// currentContext.currentMethod.inferredType=null;
// }
// }
}
public boolean visit(IReturnStatement returnStatement) {
if (currentContext.currentMethod!=null)
{
if (returnStatement.getExpression()!=null)
{
InferredType type = null;
IExpression expression = returnStatement.getExpression();
if (expression instanceof IObjectLiteral)
{
type = createAnonymousType( (ObjectLiteral)expression);
//set the start and end
type.sourceStart = expression.sourceStart();
type.sourceEnd = expression.sourceEnd();
}
else
type=getTypeOf(expression);
if (currentContext.currentMethod.getInferredType()==VoidType) {
currentContext.currentMethod.setInferredType(type);
} else {
/* If the return statement inferred type is null or
* the existing inferred return type and the statement return type are not equal and
* the return type is either not well known or is well known and the return type names are the same
*
* This logic is to cover the scenario where the return type is a known type but is from a
* different instance of the InferEngine
*/
boolean shouldSetToAny = !((MethodDeclaration)currentContext.currentMethod).isInferredJsDocType();
if(type != null && shouldSetToAny) {
//get the name of the current methods inferred return type
String currentMethodInferredType = null;
if( this.currentContext.currentMethod.getInferredType() != null && this.currentContext.currentMethod.getInferredType().name != null) {
currentMethodInferredType = new String(this.currentContext.currentMethod.getInferredType().name);
}
boolean returnTypesEqual = type.equals(currentContext.currentMethod.getInferredType());
boolean returnTypeNamesEqual = (new String(type.name)).equals(currentMethodInferredType);
boolean returnTypeIsWellKnown = WellKnownTypes.containsKey(type.name);
shouldSetToAny = !returnTypesEqual && (!returnTypeIsWellKnown || !(returnTypeIsWellKnown && returnTypeNamesEqual));
}
if(shouldSetToAny) {
currentContext.currentMethod.setInferredType(null);
}
}
}
}
return false;
}
public void endVisit(IFunctionDeclaration methodDeclaration) {
popContext();
}
public boolean visit(IFunctionDeclaration methodDeclaration) {
pushContext();
if (this.isTopLevelAnonymousFunction && this.currentContext.currentType==null)
{
this.currentContext.currentType=addType(InferredType.GLOBAL_NAME,true);
this.inferredGlobal=this.currentContext.currentType;
}
this.isTopLevelAnonymousFunction=false;
char[] methodName = methodDeclaration.getName();
//if declaration didn't have name get name from inferred method if there is one
if(methodName == null && methodDeclaration.getInferredMethod() != null) {
methodName = methodDeclaration.getInferredMethod().name;
}
if (passNumber==1)
{
buildDefinedMembers((ProgramElement[])methodDeclaration.getStatements(),(Argument[])methodDeclaration.getArguments());
if (methodDeclaration.getJsDoc()!=null)
{
InferredMethod method=null;
Javadoc javadoc = (Javadoc)methodDeclaration.getJsDoc();
createTypeIfNecessary(javadoc);
if (javadoc.isConstructor)
{
InferredType type;
if (!this.currentContext.isJsDocClass && methodName!=null)
type = this.addType(methodName);
else
type=this.currentContext.currentType;
if (type!=null)
handleJSDocConstructor(type, methodDeclaration, methodDeclaration.sourceStart());
}
else if (javadoc.memberOf!=null)
{
InferredType type = this.addType(javadoc.memberOf.getFullTypeName(),true);
char [] name=methodName;
int nameStart = methodDeclaration.sourceStart();
if (name!=null)
method=type.addMethod(methodName, methodDeclaration, nameStart);
}
else if (javadoc.methodDef!=null && this.currentContext.isJsDocClass)
{
InferredType type=this.currentContext.currentType;
char[][] methName = javadoc.methodDef.getTypeName();
int nameStart = ((MethodDeclaration)methodDeclaration).sourceStart;
if (methName.length==1)
method=type.addMethod(methName[0], methodDeclaration, nameStart);
else
{
method=type.addMethod(methName[methName.length-1], methodDeclaration, nameStart);
method.isStatic=true;
}
}
if (javadoc.returnType!=null)
{
InferredType type = this.addType(changePrimitiveToObject(javadoc.returnType.getFullTypeName()));
methodDeclaration.setInferredType(type);
((MethodDeclaration)methodDeclaration).bits |= ASTNode.IsInferredJsDocType;
}
}
handleFunctionDeclarationArguments((MethodDeclaration)methodDeclaration,(Javadoc)methodDeclaration.getJsDoc());
}
// check if this is a constructor
if (passNumber==2)
{
if (methodName!=null) {
InferredType type = compUnit
.findInferredType(methodName);
if (type != null) {
this.currentContext.currentType = type;
type.isDefinition = true;
int nameStart = methodDeclaration.sourceStart();
type.addConstructorMethod(methodName, methodDeclaration, nameStart);
}
}
}
this.currentContext.currentMethod=(MethodDeclaration)methodDeclaration;
if (methodDeclaration.getInferredMethod()!=null && methodDeclaration.getInferredMethod().inType!=null)
this.currentContext.currentType=methodDeclaration.getInferredMethod().inType;
if (methodDeclaration.getInferredType()==null)
methodDeclaration.setInferredType(VoidType);
return true;
}
protected void handleJSDocConstructor(InferredType type,IFunctionDeclaration methodDeclaration, int nameStart) {
Javadoc javadoc = (Javadoc)methodDeclaration.getJsDoc();
type.isDefinition=true;
type.addConstructorMethod(type.name, methodDeclaration, nameStart);
if (javadoc.extendsType!=null)
{
InferredType superType=this.addType(javadoc.extendsType.getFullTypeName());
type.superClass=superType;
}
}
protected void handleFunctionDeclarationArguments(IFunctionDeclaration methodDeclaration, IJsDoc jsdoc) {
if (jsdoc==null || !(jsdoc instanceof Javadoc))
return;
Javadoc javadoc = (Javadoc) jsdoc;
IArgument[] arguments = methodDeclaration.getArguments();
if (arguments!=null)
for (int i = 0; i < arguments.length; i++) {
if (arguments[i].getInferredType() != null)
continue;
JavadocSingleNameReference param = javadoc.findParam(arguments[i].getName());
if (param!=null)
{
if (param.types!=null)
{
char []name={};
for (int j = 0; j < param.types.length; j++) {
//char []typeName=param.types[j].getFullTypeName();
//make sure we are using the type version of Boolean, even if the user entered boolean as the JSdoc type.
char []typeName=changePrimitiveToObject(param.types[j].getFullTypeName());
if (j==0)
name=typeName;
else
{
name=CharOperation.append(name, '|');
name=CharOperation.concat(name, typeName);
}
}
InferredType paramType=this.addType(name);
arguments[i].setInferredType(paramType);
}
}
}
}
public boolean visit(
IAllocationExpression allocationExpression) {
InferredType type = null;
char [] possibleTypeName = constructTypeName( allocationExpression.getMember() );
if( possibleTypeName != null ){
type = compUnit.findInferredType( possibleTypeName );
if( type == null )
type = addType( possibleTypeName ); //creating type
}
return true;
}
public void endVisit(IObjectLiteralField field) {
// if (field.getJsDoc()!=null)
// {
// Javadoc javaDoc = (Javadoc)field.getJsDoc();
// InferredType inClass=this.currentContext.currentType;
// char [] name=null;
// int nameStart=-1;
// InferredType returnType=null;
//// boolean isFunction=field.initializer instanceof FunctionExpression;
// if (field.getFieldName() instanceof SingleNameReference)
// {
// SingleNameReference singleNameReference=(SingleNameReference)field.getFieldName();
// name=singleNameReference.token;
// nameStart=singleNameReference.sourceStart;
// }
// if (javaDoc.memberOf!=null)
// {
// char[] typeName = javaDoc.memberOf.getFullTypeName();
// convertAnonymousTypeToNamed(inClass,typeName);
// inClass.isDefinition=true;
// }
// else if (this.currentContext.isJsDocClass && javaDoc.property!=null)
// {
// if (this.currentContext.currentType.isAnonymous && this.currentContext.parent!=null)
// {
// InferredType previousType = this.currentContext.parent.currentType;
// if (previousType!=null)
// {
// copyAnonymousTypeToNamed(inClass,previousType);
// this.currentContext.currentType=previousType;
// }
//
// }
// }
// if (javaDoc.returnType!=null)
// {
// returnType=this.addType(javaDoc.returnType.getFullTypeName());
// }
//
// if (inClass!=null && name!=null)
// {
// if (field.getInitializer() instanceof FunctionExpression) {
// FunctionExpression functionExpression = (FunctionExpression) field.getInitializer();
// InferredMember method = inClass.addMethod(name, functionExpression.methodDeclaration,false);
// method.nameStart=nameStart;
// functionExpression.methodDeclaration.modifiers=javaDoc.modifiers;
// if (returnType!=null)
// {
// functionExpression.methodDeclaration.inferredType=returnType;
// }
//// else
//// method.inferredType=functionExpression.methodDeclaration.inferredType;
// }
// else //attribute
// {
// InferredAttribute attribute = inClass.addAttribute(name, field.getFieldName());
// attribute.nameStart=field.getFieldName().sourceStart();
// if (returnType!=null)
// attribute.type=returnType;
// }
// }
//
// }
// //no jsdoc
// else{
//
// if( field.getInitializer() instanceof ObjectLiteral ){
//
// }
//
//
// }
}
private void copyAnonymousTypeToNamed(InferredType inClass,
InferredType toType) {
if (toType==null)return;
compUnit.inferredTypesHash.removeKey(inClass.name);
if (inClass.methods!=null)
{
toType.methods.addAll(inClass.methods);
// else
// toType.methods=inClass.methods;
}
if (inClass.attributes!=null)
{
for (int i = 0; i < inClass.numberAttributes; i++) {
toType.addAttribute(inClass.attributes[i]);
}
}
}
private void convertAnonymousTypeToNamed(InferredType inClass, char[] typeName) {
if (inClass.isAnonymous)
{
inClass.isAnonymous=false;
compUnit.inferredTypesHash.removeKey(inClass.name);
inClass.name=typeName;
compUnit.inferredTypesHash.put(typeName,inClass);
}
}
protected boolean isMatch(IExpression expr,char[] [] names, int index)
{
char [] matchName=names[index];
if (expr instanceof SingleNameReference) {
SingleNameReference snr = (SingleNameReference) expr;
return CharOperation.equals(snr.token, matchName);
}
else if (expr instanceof FieldReference && names.length>1 && index>0) {
FieldReference fieldReference = (FieldReference) expr;
if (CharOperation.equals(fieldReference.token, matchName))
return isMatch(fieldReference.receiver, names, index-1);
}
return false;
}
protected boolean isFunction(IFunctionCall messageSend,String string) {
String []names=string.split("\\."); //$NON-NLS-1$
char [] functionName=names[names.length-1].toCharArray();
if (!CharOperation.equals(functionName, messageSend.getSelector()))
return false;
char [][]namesChars=new char[names.length][];
for (int i = 0; i < namesChars.length; i++) {
namesChars[i]=names[i].toCharArray();
}
if (names.length>1)
return isMatch(messageSend.getReceiver(), namesChars, namesChars.length-2);
return true;
}
protected boolean isFunction(IFunctionCall messageSend,char [][]names) {
char [] functionName=names[names.length-1];
if (!CharOperation.equals(functionName, messageSend.getSelector()))
return false;
if (names.length>1)
return isMatch(messageSend.getReceiver(), names, names.length-2);
return true;
}
public void doInfer()
{
try {
long time0 = 0;
if (REPORT_INFER_TIME) {
time0 = System.currentTimeMillis();
}
compUnit.traverse(this );
passNumber=2;
compUnit.traverse(this );
for (int i = 0; i < compUnit.numberInferredTypes; i++) {
if (compUnit.inferredTypes[i].sourceStart<0)
compUnit.inferredTypes[i].sourceStart=0;
}
if (REPORT_INFER_TIME) {
long time = System.currentTimeMillis() - time0;
System.err.println(getClass().getName() + " inferred " + new String(compUnit.getFileName()) + " in " + time + "ms");
}
this.compUnit=null;
} catch (RuntimeException e) {
org.eclipse.wst.jsdt.internal.core.util.Util.log(e, "error during type inferencing");
}
}
protected InferredType addType(char[] className) {
return addType(className,false);
}
/**
* Create a new inferred type with the given name
*
* @param className the name of the inferred type
* @param isDefinition true if this unit defines the type
* @return new Inferred type
*/
protected InferredType addType(char[] className, boolean isDefinition) {
InferredType type = compUnit.addType(className, isDefinition, this.inferenceProvider.getID());
return type;
}
protected final void pushContext()
{
Context newContext = new Context( currentContext );
contexts[++contextPtr] = currentContext;
currentContext = newContext;
}
protected final void popContext()
{
currentContext = contexts[contextPtr];
contexts[contextPtr--] = null;
}
protected final boolean isInNamedMethod()
{
return this.currentContext.currentMethod!=null && this.currentContext.currentMethod.getName()!=null;
}
/**
* Finds a Var Declaration on the context from the name represented with the expression
*
* Currently, only SNR are supported
*/
protected IAbstractVariableDeclaration getVariable(IExpression expression)
{
char [] name=null;
if (expression instanceof ISingleNameReference)
name = ((ISingleNameReference) expression).getToken();
else if (expression instanceof IFieldReference)
name = ((IFieldReference) expression).getToken();
if (name!=null)
{
Object var = this.currentContext.getMember( name );
if (var instanceof IAbstractVariableDeclaration)
return (IAbstractVariableDeclaration)var;
}
return null;
}
/**
* Finds a assignment on the context from the name represented with the expression
*
* Currently, only SNR are supported
*/
protected IAssignment getAssignment(IExpression expression)
{
char [] name=null;
if (expression instanceof ISingleNameReference)
name = ((ISingleNameReference) expression).getToken();
else if (expression instanceof IFieldReference)
name = ((IFieldReference) expression).getToken();
if (name!=null)
{
Object assignment = this.currentContext.getMember( name );
if (assignment instanceof IAssignment)
return (IAssignment)assignment;
}
return null;
}
/**
* Finds a Function Declaration on the context from the name represented with the expression
*
* Currently, only SNR are supported
*/
protected IAbstractFunctionDeclaration getFunction(IExpression expression)
{
char [] name=null;
if (expression instanceof ISingleNameReference)
name = ((ISingleNameReference) expression).getToken();
else if (expression instanceof IFieldReference)
name = ((IFieldReference) expression).getToken();
if (name!=null)
{
Object method = this.currentContext.getMember( name );
if (method instanceof IAbstractFunctionDeclaration)
return (IAbstractFunctionDeclaration)method;
}
return null;
}
private void buildDefinedMembers(IProgramElement[] statements, IArgument[] arguments) {
if (arguments!=null)
{
for (int i = 0; i < arguments.length; i++) {
this.currentContext.addMember( arguments[i].getName(), arguments[i] );
}
}
if (statements!=null)
{
for (int i = 0; i < statements.length; i++) {
if (statements[i] instanceof ILocalDeclaration) {
ILocalDeclaration local = (ILocalDeclaration) statements[i];
this.currentContext.addMember( local.getName(), local );
}
else if (statements[i] instanceof IAbstractFunctionDeclaration) {
IAbstractFunctionDeclaration method = (IAbstractFunctionDeclaration) statements[i];
if (method.getName()!=null)
this.currentContext.addMember( method.getName(), method );
}
}
}
}
private static boolean isThis(IExpression expression)
{
if (expression instanceof FieldReference && ((FieldReference)expression).receiver.isThis())
return true;
return false;
}
/*
* This method is used to determined the inferred type of a LHS Expression.
*
* It could return null.
*
* a.b.c
*/
private InferredType getInferredType( Expression expression ){
InferredType type = null;
/*
* this
*/
if( expression instanceof IThisReference ){
if (this.passNumber==2 && this.currentContext.currentType==null)
{
char [] possibleTypeName={'g','l','o','b','a','l'};
if (this.currentContext.currentMethod!=null)
possibleTypeName=this.currentContext.currentMethod.getName();
this.currentContext.setCurrentType(createAnonymousType(possibleTypeName, null));
}
type = this.currentContext.currentType;
}
/*
* foo (could be a Type name or a reference to a variable)
*/
else if( expression instanceof SingleNameReference ){
char [] possibleTypeName = constructTypeName( expression );
if( possibleTypeName != null ){
//search the defined types in the context
type = compUnit.findInferredType( possibleTypeName );
if (type==null)
{
if (WellKnownTypes.containsKey(possibleTypeName))
{
type = addType(possibleTypeName,true);
}
else if (/*this.passNumber==2 && */this.isKnownType(possibleTypeName))
{
type = addType(possibleTypeName,true);
// if (type!=null)
// {
// AbstractVariableDeclaration varDecl = getVariable( (expression) );
//
// if( varDecl != null ){
// varDecl.inferredType=type;
// }
//
// }
}
}
/*
* There is no match for a type with the name, check if the name refers to
* var decl and return its type
*/
if( type == null ){
IAbstractVariableDeclaration varDecl = getVariable( expression );
if( varDecl != null ){
type = varDecl.getInferredType(); //could be null
if (type!=null && !type.isAnonymous) {
if(varDecl.getInitialization() instanceof IAllocationExpression && !type.isFunction()) {
type = createAnonymousType(varDecl);
} else {
InferredType superType = type;
type = addType(varDecl.getName(), true);
type.superClass = superType;
}
type.updatePositions(varDecl.sourceStart(), varDecl.sourceEnd());
}
} else {
IAssignment assignment = getAssignment(expression);
if(assignment != null) {
type = assignment.getInferredType(); //could be null
if (type!=null && !type.isAnonymous) {
if(assignment.getExpression() instanceof IAllocationExpression && !type.isFunction()) {
type = createAnonymousType(assignment);
} else {
InferredType superType = type;
type = addType(getName(assignment.getLeftHandSide()), true);
type.superClass = superType;
}
type.updatePositions(assignment.sourceStart(), assignment.sourceEnd());
}
}
}
}
}
}
/*
* foo.bar.xxx...
*/
else if( expression instanceof FieldReference ){
char[] possibleTypeName = constructTypeName(expression);
if (possibleTypeName != null)
// search the defined types in the context
type = compUnit.findInferredType(possibleTypeName);
if (type==null && isPossibleClassName(possibleTypeName))
{
type = addType(possibleTypeName,true);
}
/*
* Continue the search by trying to resolve further down the name
* because this token of the field reference could be a member of a
* type or instance of a type
*/
if (type == null) {
FieldReference fRef = (FieldReference) expression;
// this
InferredType parentType = getInferredType(fRef.receiver);
if (parentType != null) {
// check the members and return type
InferredAttribute typeAttribute = parentType
.findAttribute(fRef.token);
if (typeAttribute != null) {
type = typeAttribute.type;
if (type != null && !type.isAnonymous) {
if (possibleTypeName==null)
possibleTypeName=typeAttribute.name;
type = createAnonymousType(possibleTypeName, type);
typeAttribute.type = type;
}
}
}
}
}
return type;
}
protected boolean isKnownType(char[] possibleTypeName) {
return false;
}
/*
* For SNR it returns the name
* For FR it construct a Qualified name separated by '.'
*
* If at any point it hits a portion of the Field reference that is
* not supported (such as a function call, a prototype, or this )
*/
protected final char [] constructTypeName( IExpression expression ){
return Util.getTypeName( expression );
}
public boolean visit(IObjectLiteral literal) {
if (this.passNumber==1 && literal.getInferredType()==null)
createAnonymousType((ObjectLiteral)literal);
pushContext();
this.currentContext.currentType=literal.getInferredType();
return true;
}
public void endVisit(IObjectLiteral literal) {
popContext();
}
/**
* Overriden by client who wish to update the infer options
*
* @param options
*/
public void initializeOptions(InferOptions options) {
}
protected boolean isPossibleClassName(char[]name)
{
return false;
}
/**
* Get the Script file this inferrence is being done on
*
* @return
*/
public IScriptFileDeclaration getScriptFileDeclaration()
{
return this.compUnit;
}
public InferredType findDefinedType(char [] className)
{
return compUnit.findInferredType(className);
}
protected char[] changePrimitiveToObject(char[] name) {
//Changes the first character of the name of the primitive types to uppercase. This will allow future reference to the object wrapper instead of the primitive type.
if(CharOperation.equals(name, TypeConstants.BOOLEAN, false)) //$NON-NLS-1$
return BooleanType.getName();
return name;
}
private char[] getName(IExpression expression) {
if (expression instanceof ISingleNameReference)
return ((ISingleNameReference) expression).getToken();
else if (expression instanceof IFieldReference)
return ((IFieldReference) expression).getToken();
return null;
}
}