blob: d68fb318468db5feacb57717f8dd16a7f76b72a5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 xored software, Inc.
*
* 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:
* xored software, Inc. - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.internal.javascript.ti;
import static org.eclipse.dltk.javascript.typeinfo.ITypeNames.BOOLEAN;
import static org.eclipse.dltk.javascript.typeinfo.ITypeNames.NUMBER;
import static org.eclipse.dltk.javascript.typeinfo.ITypeNames.OBJECT;
import static org.eclipse.dltk.javascript.typeinfo.ITypeNames.STRING;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.compiler.problem.IProblemCategory;
import org.eclipse.dltk.compiler.problem.IProblemIdentifier;
import org.eclipse.dltk.internal.javascript.validation.JavaScriptValidations;
import org.eclipse.dltk.internal.javascript.validation.ValidationMessages;
import org.eclipse.dltk.javascript.ast.ArrayInitializer;
import org.eclipse.dltk.javascript.ast.AsteriskExpression;
import org.eclipse.dltk.javascript.ast.BinaryOperation;
import org.eclipse.dltk.javascript.ast.BooleanLiteral;
import org.eclipse.dltk.javascript.ast.BreakStatement;
import org.eclipse.dltk.javascript.ast.CallExpression;
import org.eclipse.dltk.javascript.ast.CaseClause;
import org.eclipse.dltk.javascript.ast.CatchClause;
import org.eclipse.dltk.javascript.ast.CommaExpression;
import org.eclipse.dltk.javascript.ast.ConditionalOperator;
import org.eclipse.dltk.javascript.ast.ConstStatement;
import org.eclipse.dltk.javascript.ast.ContinueStatement;
import org.eclipse.dltk.javascript.ast.DecimalLiteral;
import org.eclipse.dltk.javascript.ast.DefaultXmlNamespaceStatement;
import org.eclipse.dltk.javascript.ast.DoWhileStatement;
import org.eclipse.dltk.javascript.ast.EmptyExpression;
import org.eclipse.dltk.javascript.ast.EmptyStatement;
import org.eclipse.dltk.javascript.ast.Expression;
import org.eclipse.dltk.javascript.ast.ForEachInStatement;
import org.eclipse.dltk.javascript.ast.ForInStatement;
import org.eclipse.dltk.javascript.ast.ForStatement;
import org.eclipse.dltk.javascript.ast.FunctionStatement;
import org.eclipse.dltk.javascript.ast.GetAllChildrenExpression;
import org.eclipse.dltk.javascript.ast.GetArrayItemExpression;
import org.eclipse.dltk.javascript.ast.GetLocalNameExpression;
import org.eclipse.dltk.javascript.ast.Identifier;
import org.eclipse.dltk.javascript.ast.IfStatement;
import org.eclipse.dltk.javascript.ast.JSDeclaration;
import org.eclipse.dltk.javascript.ast.JSScope;
import org.eclipse.dltk.javascript.ast.LabelledStatement;
import org.eclipse.dltk.javascript.ast.NewExpression;
import org.eclipse.dltk.javascript.ast.NullExpression;
import org.eclipse.dltk.javascript.ast.ObjectInitializer;
import org.eclipse.dltk.javascript.ast.ObjectInitializerPart;
import org.eclipse.dltk.javascript.ast.ParenthesizedExpression;
import org.eclipse.dltk.javascript.ast.PropertyExpression;
import org.eclipse.dltk.javascript.ast.PropertyInitializer;
import org.eclipse.dltk.javascript.ast.RegExpLiteral;
import org.eclipse.dltk.javascript.ast.ReturnStatement;
import org.eclipse.dltk.javascript.ast.Script;
import org.eclipse.dltk.javascript.ast.Statement;
import org.eclipse.dltk.javascript.ast.StatementBlock;
import org.eclipse.dltk.javascript.ast.StringLiteral;
import org.eclipse.dltk.javascript.ast.SwitchComponent;
import org.eclipse.dltk.javascript.ast.SwitchStatement;
import org.eclipse.dltk.javascript.ast.ThisExpression;
import org.eclipse.dltk.javascript.ast.ThrowStatement;
import org.eclipse.dltk.javascript.ast.TryStatement;
import org.eclipse.dltk.javascript.ast.UnaryOperation;
import org.eclipse.dltk.javascript.ast.VariableDeclaration;
import org.eclipse.dltk.javascript.ast.VariableStatement;
import org.eclipse.dltk.javascript.ast.VoidExpression;
import org.eclipse.dltk.javascript.ast.WhileStatement;
import org.eclipse.dltk.javascript.ast.WithStatement;
import org.eclipse.dltk.javascript.ast.XmlAttributeIdentifier;
import org.eclipse.dltk.javascript.ast.XmlExpressionFragment;
import org.eclipse.dltk.javascript.ast.XmlFragment;
import org.eclipse.dltk.javascript.ast.XmlLiteral;
import org.eclipse.dltk.javascript.ast.XmlTextFragment;
import org.eclipse.dltk.javascript.ast.YieldOperator;
import org.eclipse.dltk.javascript.core.JavaScriptProblems;
import org.eclipse.dltk.javascript.parser.JSParser;
import org.eclipse.dltk.javascript.parser.PropertyExpressionUtils;
import org.eclipse.dltk.javascript.typeinference.IAssignProtection;
import org.eclipse.dltk.javascript.typeinference.IValueCollection;
import org.eclipse.dltk.javascript.typeinference.IValueReference;
import org.eclipse.dltk.javascript.typeinference.ReferenceKind;
import org.eclipse.dltk.javascript.typeinference.ReferenceLocation;
import org.eclipse.dltk.javascript.typeinfo.IMemberEvaluator;
import org.eclipse.dltk.javascript.typeinfo.IModelBuilder;
import org.eclipse.dltk.javascript.typeinfo.IModelBuilder.IParameter;
import org.eclipse.dltk.javascript.typeinfo.IModelBuilder.IVariable;
import org.eclipse.dltk.javascript.typeinfo.IModelBuilderExtension;
import org.eclipse.dltk.javascript.typeinfo.IRAnyType;
import org.eclipse.dltk.javascript.typeinfo.IRArrayType;
import org.eclipse.dltk.javascript.typeinfo.IRClassType;
import org.eclipse.dltk.javascript.typeinfo.IRMapType;
import org.eclipse.dltk.javascript.typeinfo.IRNoneType;
import org.eclipse.dltk.javascript.typeinfo.IRSimpleType;
import org.eclipse.dltk.javascript.typeinfo.IRType;
import org.eclipse.dltk.javascript.typeinfo.IRVariable;
import org.eclipse.dltk.javascript.typeinfo.ITypeNames;
import org.eclipse.dltk.javascript.typeinfo.ITypeSystem;
import org.eclipse.dltk.javascript.typeinfo.JSTypeSet;
import org.eclipse.dltk.javascript.typeinfo.RModelBuilder;
import org.eclipse.dltk.javascript.typeinfo.ReferenceSource;
import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager;
import org.eclipse.dltk.javascript.typeinfo.TypeMode;
import org.eclipse.dltk.javascript.typeinfo.TypeUtil;
import org.eclipse.dltk.javascript.typeinfo.model.ArrayType;
import org.eclipse.dltk.javascript.typeinfo.model.FunctionType;
import org.eclipse.dltk.javascript.typeinfo.model.GenericMethod;
import org.eclipse.dltk.javascript.typeinfo.model.JSType;
import org.eclipse.dltk.javascript.typeinfo.model.MapType;
import org.eclipse.dltk.javascript.typeinfo.model.Method;
import org.eclipse.dltk.javascript.typeinfo.model.Parameter;
import org.eclipse.dltk.javascript.typeinfo.model.ParameterizedType;
import org.eclipse.dltk.javascript.typeinfo.model.SimpleType;
import org.eclipse.dltk.javascript.typeinfo.model.Type;
import org.eclipse.dltk.javascript.typeinfo.model.TypeInfoModelFactory;
import org.eclipse.dltk.javascript.typeinfo.model.TypeKind;
import org.eclipse.dltk.javascript.typeinfo.model.TypeVariable;
import org.eclipse.dltk.javascript.typeinfo.model.TypeVariableClassType;
import org.eclipse.dltk.javascript.typeinfo.model.TypeVariableReference;
import org.eclipse.dltk.javascript.typeinfo.model.UnionType;
import org.eclipse.dltk.javascript.typeinfo.model.util.TypeInfoModelSwitch;
import org.eclipse.emf.ecore.EObject;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public class TypeInferencerVisitor extends TypeInferencerVisitorBase {
public TypeInferencerVisitor(ITypeInferenceContext context) {
super(context);
}
private final Stack<Branching> branchings = new Stack<Branching>();
private class Branching {
public void end() {
branchings.remove(this);
}
}
protected Branching branching() {
final Branching branching = new Branching();
branchings.add(branching);
return branching;
}
public ReferenceSource getSource() {
final ReferenceSource source = context.getSource();
return source != null ? source : ReferenceSource.UNKNOWN;
}
protected void assign(IValueReference dest, IValueReference src) {
IRType destType = JavaScriptValidations.typeOf(dest);
if (destType != null && isXML(destType)) {
IRType srcType = JavaScriptValidations.typeOf(src);
if (srcType != null && !isXML(srcType))
return;
}
if (branchings.isEmpty()) {
dest.setValue(src);
} else {
dest.addValue(src, false);
}
}
private static boolean isXML(IRType srcType) {
return (srcType instanceof IRSimpleType)
&& (ITypeNames.XML.equals(srcType.getName()) || ITypeNames.XMLLIST
.equals(srcType.getName()));
}
@Override
public IValueReference visitArrayInitializer(ArrayInitializer node) {
final JSTypeSet types = JSTypeSet.create();
for (ASTNode astNode : node.getItems()) {
if (astNode instanceof StringLiteral) {
types.add(JSTypeSet.ref(STRING));
} else if (astNode instanceof DecimalLiteral) {
types.add(JSTypeSet.ref(NUMBER));
} else if (astNode instanceof BooleanLiteral) {
types.add(JSTypeSet.ref(BOOLEAN));
} else if (astNode instanceof NullExpression
|| astNode instanceof EmptyExpression) {
// ignore
} else {
final IValueReference child = visit(astNode);
if (child != null && child.exists()) {
types.addAll(JavaScriptValidations.getTypes(child));
}
// TODO (alex) else add(Obect) ?
}
}
if (types.size() == 1) {
return context.getFactory().create(peekContext(),
JSTypeSet.arrayOf(types.getFirst()));
} else {
// TODO (alex) if not empty then evaluate common base type.
return context.getFactory().createArray(peekContext());
}
}
@Override
public IValueReference visitAsteriskExpression(AsteriskExpression node) {
return context.getFactory().createXMLList(peekContext());
}
@Override
public IValueReference visitBinaryOperation(BinaryOperation node) {
final IValueReference left = visit(node.getLeftExpression());
final int op = node.getOperation();
if (JSParser.ASSIGN == op) {
if (left != null) {
for (IModelBuilder modelBuilder : context.getModelBuilders()) {
if (modelBuilder instanceof IModelBuilderExtension) {
((IModelBuilderExtension) modelBuilder)
.processAssignment(node.getLeftExpression(),
left);
}
}
}
if (left != null && left.exists()) {
left.setAttribute(IReferenceAttributes.RESOLVING, Boolean.TRUE);
final IValueReference r;
try {
r = visit(node.getRightExpression());
} finally {
left.setAttribute(IReferenceAttributes.RESOLVING, null);
}
return visitAssign(left, r, node);
} else {
return visitAssign(left, visit(node.getRightExpression()), node);
}
}
final IValueReference right = visit(node.getRightExpression());
if (left == null && right instanceof ConstantValue) {
return right;
} else if (op == JSParser.LAND) {
return coalesce(right, left);
} else if (op == JSParser.GT || op == JSParser.GTE || op == JSParser.LT
|| op == JSParser.LTE || op == JSParser.NSAME
|| op == JSParser.SAME || op == JSParser.NEQ
|| op == JSParser.EQ) {
return context.getFactory().createBoolean(peekContext());
} else if (isNumber(left) && isNumber(right)) {
return context.getFactory().createNumber(peekContext());
} else if (op == JSParser.ADD) {
if (isString(left) || isString(right)) {
return context.getFactory().createString(peekContext());
}
return left;
} else if (JSParser.INSTANCEOF == op) {
return context.getFactory().createBoolean(peekContext());
} else if (JSParser.LOR == op) {
final JSTypeSet typeSet = JSTypeSet.create();
if (left != null) {
typeSet.addAll(left.getDeclaredTypes());
typeSet.addAll(left.getTypes());
}
if (right != null) {
typeSet.addAll(right.getDeclaredTypes());
typeSet.addAll(right.getTypes());
}
return new ConstantValue(typeSet);
} else {
// TODO handle other operations
return null;
}
}
private static IValueReference coalesce(IValueReference v1,
IValueReference v2) {
return v1 != null ? v1 : v2;
}
private boolean isNumber(IValueReference ref) {
if (ref != null) {
final IRType numType = JSTypeSet.ref(NUMBER);
if (ref.getTypes().contains(numType))
return true;
if (numType.equals(ref.getDeclaredType()))
return true;
}
return false;
}
private boolean isString(IValueReference ref) {
if (ref != null) {
final IRType strType = JSTypeSet.ref(STRING);
if (ref.getTypes().contains(strType))
return true;
if (strType.equals(ref.getDeclaredType()))
return true;
}
return false;
}
protected IValueReference visitAssign(IValueReference left,
IValueReference right, BinaryOperation node) {
if (left != null) {
if (node.getLeftExpression() instanceof PropertyExpression) {
final PropertyExpression property = (PropertyExpression) node
.getLeftExpression();
if (property.getObject() instanceof ThisExpression
&& property.getProperty() instanceof Identifier
&& !left.exists()) {
if (isFunctionDeclaration(property))
left.setKind(ReferenceKind.FUNCTION);
else
left.setKind(ReferenceKind.FIELD);
left.setLocation(ReferenceLocation.create(getSource(),
property.sourceStart(), property.sourceEnd(),
property.getProperty().sourceStart(), property
.getProperty().sourceEnd()));
}
}
if (IValueReference.ARRAY_OP.equals(left.getName())
&& node.getLeftExpression() instanceof GetArrayItemExpression) {
GetArrayItemExpression arrayItemExpression = (GetArrayItemExpression) node
.getLeftExpression();
IValueReference namedChild = extractNamedChild(
left.getParent(), arrayItemExpression.getIndex());
if (namedChild != null) {
assign(namedChild, right);
} else {
assign(left, right);
}
} else {
if (!hasUnknowParentFunctionCall(left))
assign(left, right);
}
}
return right;
}
private boolean hasUnknowParentFunctionCall(IValueReference reference) {
IValueReference parent = reference.getParent();
while (parent != null) {
if (parent.getName().equals(IValueReference.FUNCTION_OP)
&& !parent.exists())
return true;
parent = parent.getParent();
}
return false;
}
@Override
public IValueReference visitBooleanLiteral(BooleanLiteral node) {
return context.getFactory().createBoolean(peekContext());
}
@Override
public IValueReference visitBreakStatement(BreakStatement node) {
return null;
}
@Override
public IValueReference visitCallExpression(CallExpression node) {
final IValueReference reference = visit(node.getExpression());
final List<ASTNode> args = node.getArguments();
final IValueReference[] arguments = new IValueReference[args.size()];
for (int i = 0; i < args.size(); ++i) {
arguments[i] = visit(args.get(i));
}
if (reference != null) {
final List<Method> methods = JavaScriptValidations.extractElements(
reference, Method.class);
if (methods != null && methods.size() == 1) {
if (methods.get(0) instanceof GenericMethod) {
final GenericMethod method = (GenericMethod) methods.get(0);
final JSTypeSet type = evaluateGenericCall(method,
arguments);
if (type != null) {
return new ConstantValue(type);
}
} else {
final ITypeSystem typeSystem = getTypeSystemOf(reference);
final IRType type = JSTypeSet.normalize(typeSystem, methods
.get(0).getType());
if (type != null) {
return new ConstantValue(type);
} else {
return null;
}
}
}
return reference.getChild(IValueReference.FUNCTION_OP);
} else {
return null;
}
}
private ITypeSystem getTypeSystemOf(IValueReference reference) {
final Object value = reference
.getAttribute(IReferenceAttributes.TYPE_SYSTEM);
if (value != null) {
return (ITypeSystem) value;
}
return getContext();
}
protected JSTypeSet evaluateGenericCall(GenericMethod method,
IValueReference[] arguments) {
final JSTypeSet[] argTypes = new JSTypeSet[arguments.length];
for (int i = 0; i < arguments.length; ++i) {
argTypes[i] = arguments[i].getDeclaredTypes();
if (argTypes[i].isEmpty()) {
argTypes[i] = arguments[i].getTypes();
}
}
// TODO (alex) can be pre-evaluated in GenericParameter objects.
final boolean genericParams[] = new boolean[method.getParameters()
.size()];
for (int i = 0; i < method.getParameters().size(); ++i) {
genericParams[i] = isGenericType(method.getParameters().get(i)
.getType());
}
final Map<TypeVariable, JSTypeSet> captures = new HashMap<TypeVariable, JSTypeSet>();
for (TypeVariable variable : method.getTypeParameters()) {
captures.put(variable, JSTypeSet.create());
}
for (int i = 0; i < argTypes.length; ++i) {
final int index = i < method.getParameters().size() ? i : method
.getParameters().size() - 1;
final Parameter parameter = method.getParameters().get(index);
if (genericParams[index]) {
final Capture capture = capture(parameter.getType(),
argTypes[i]);
if (capture != null) {
final JSTypeSet captured = captures.get(capture.variable);
if (captured != null) {
captured.addAll(capture.types);
}
}
} else {
// TODO (alex) check parameter compatibility
}
}
if (method.getType() != null) {
return evaluateReturnType(method.getType(), captures);
} else {
return null;
}
}
private JSTypeSet evaluateReturnType(JSType type,
Map<TypeVariable, JSTypeSet> captures) {
if (type instanceof TypeVariableReference) {
final TypeVariable variable = ((TypeVariableReference) type)
.getVariable();
return normalizeCapture(captures.get(variable));
} else if (type instanceof ArrayType) {
final JSType itemType = ((ArrayType) type).getItemType();
return JSTypeSet.singleton(JSTypeSet.arrayOf(evaluateReturnType(
itemType, captures).toRType()));
} else if (type instanceof ParameterizedType) {
List<IRType> params = new ArrayList<IRType>();
final ParameterizedType parameterized = (ParameterizedType) type;
for (JSType param : parameterized.getActualTypeArguments()) {
params.add(evaluateReturnType(param, captures).toRType());
}
return JSTypeSet.singleton(JSTypeSet.ref(getContext().parameterize(
parameterized.getTarget(), params)));
} else if (type instanceof SimpleType) {
return JSTypeSet.create(JSTypeSet.normalize(getContext(), type));
} else {
return JSTypeSet.emptySet();
}
}
private JSTypeSet normalizeCapture(JSTypeSet types) {
return types;
}
private static class Capture {
final TypeVariable variable;
final JSTypeSet types;
public Capture(TypeVariable variable, JSTypeSet types) {
this.variable = variable;
this.types = types;
}
}
private Capture capture(JSType paramType, JSTypeSet argTypes) {
if (paramType instanceof TypeVariableReference) {
return new Capture(
((TypeVariableReference) paramType).getVariable(), argTypes);
} else if (paramType instanceof TypeVariableClassType) {
final JSTypeSet result = JSTypeSet.create();
for (IRType type : argTypes) {
if (type instanceof IRClassType) {
result.add(((IRClassType) type).toItemType());
}
}
return new Capture(
((TypeVariableClassType) paramType).getVariable(), result);
} else {
// TODO alex other type expressions
return null;
}
}
private static final TypeInfoModelSwitch<Boolean> GENERIC_TYPE_EXPRESSION = new TypeInfoModelSwitch<Boolean>() {
@Override
public Boolean doSwitch(EObject theEObject) {
return theEObject != null ? super.doSwitch(theEObject) : null;
}
@Override
public Boolean caseJSType(JSType object) {
return Boolean.FALSE;
}
@Override
public Boolean caseTypeVariableReference(TypeVariableReference object) {
return Boolean.TRUE;
}
public Boolean caseTypeVariableClassType(TypeVariableClassType object) {
return Boolean.TRUE;
}
@Override
public Boolean caseArrayType(ArrayType object) {
return doSwitch(object.getItemType());
}
@Override
public Boolean caseMapType(MapType object) {
final Boolean result = doSwitch(object.getKeyType());
if (result == Boolean.TRUE) {
return result;
}
return doSwitch(object.getValueType());
}
@Override
public Boolean caseUnionType(UnionType object) {
for (JSType type : object.getTargets()) {
final Boolean result = doSwitch(type);
if (result == Boolean.TRUE) {
return result;
}
}
return Boolean.FALSE;
}
@Override
public Boolean caseFunctionType(FunctionType object) {
for (Parameter parameter : object.getParameters()) {
final Boolean result = doSwitch(parameter.getType());
if (result == Boolean.TRUE) {
return result;
}
}
return doSwitch(object.getReturnType());
}
};
private boolean isGenericType(JSType type) {
if (type != null) {
final Boolean result = GENERIC_TYPE_EXPRESSION.doSwitch(type);
return result != null && result.booleanValue();
}
return false;
}
@Override
public IValueReference visitCommaExpression(CommaExpression node) {
return visit(node.getItems());
}
@Override
public IValueReference visitConditionalOperator(ConditionalOperator node) {
visit(node.getCondition());
return merge(visit(node.getTrueValue()), visit(node.getFalseValue()));
}
protected static final IAssignProtection PROTECT_CONST = new IAssignProtection() {
public IProblemIdentifier problemId() {
return JavaScriptProblems.REASSIGNMENT_OF_CONSTANT;
}
public String problemMessage() {
return ValidationMessages.ReassignmentOfConstant;
}
};
@Override
public IValueReference visitConstDeclaration(ConstStatement node) {
final IValueCollection context = peekContext();
for (VariableDeclaration declaration : node.getVariables()) {
IValueReference constant = createVariable(context, declaration);
if (constant != null)
constant.setAttribute(IAssignProtection.ATTRIBUTE,
PROTECT_CONST);
}
return null;
}
protected IValueReference createVariable(IValueCollection context,
VariableDeclaration declaration) {
final Identifier identifier = declaration.getIdentifier();
final String varName = identifier.getName();
final IValueReference reference = context.createChild(varName);
final JSVariable variable = new JSVariable();
variable.setName(declaration.getVariableName());
if (declaration.getParent() instanceof VariableStatement) {
for (IModelBuilder extension : this.context.getModelBuilders()) {
extension.processVariable(declaration, variable, reporter,
getTypeChecker());
}
}
reference.setAttribute(IReferenceAttributes.VARIABLE, variable);
reference.setKind(inFunction() ? ReferenceKind.LOCAL
: ReferenceKind.GLOBAL);
reference.setLocation(ReferenceLocation.create(getSource(),
declaration.sourceStart(), declaration.sourceEnd(),
identifier.sourceStart(), identifier.sourceEnd()));
initializeVariable(reference, declaration, variable);
// declared type setting must be done after the initialize else the
// IMemberEvaluator.valueOf() call will be reverted for types that do
// return a collection
final IRVariable rvar = RModelBuilder.create(getContext(), variable);
reference.setAttribute(IReferenceAttributes.R_VARIABLE, rvar);
if (rvar.getType() != null) {
setIRType(reference, rvar.getType(), true);
}
return reference;
}
protected void initializeVariable(final IValueReference reference,
VariableDeclaration declaration, IVariable variable) {
if (declaration.getInitializer() != null) {
final IValueReference assignment;
reference
.setAttribute(IReferenceAttributes.RESOLVING, Boolean.TRUE);
try {
assignment = visit(declaration.getInitializer());
} finally {
reference.setAttribute(IReferenceAttributes.RESOLVING, null);
}
if (assignment != null) {
assign(reference, assignment);
if (assignment.getKind() == ReferenceKind.FUNCTION
&& reference.getAttribute(IReferenceAttributes.METHOD) != null)
reference.setKind(ReferenceKind.FUNCTION);
}
}
}
@Override
public IValueReference visitContinueStatement(ContinueStatement node) {
return null;
}
@Override
public IValueReference visitDecimalLiteral(DecimalLiteral node) {
return context.getFactory().createNumber(peekContext());
}
@Override
public IValueReference visitDefaultXmlNamespace(
DefaultXmlNamespaceStatement node) {
visit(node.getValue());
return null;
}
@Override
public IValueReference visitDoWhileStatement(DoWhileStatement node) {
visit(node.getCondition());
visit(node.getBody());
return null;
}
@Override
public IValueReference visitEmptyExpression(EmptyExpression node) {
return null;
}
@Override
public IValueReference visitEmptyStatement(EmptyStatement node) {
return null;
}
@Override
public IValueReference visitForEachInStatement(ForEachInStatement node) {
IValueReference itemReference = visit(node.getItem());
IValueReference iteratorReference = visit(node.getIterator());
JSTypeSet typeSet = JavaScriptValidations.getTypes(iteratorReference);
if (!typeSet.isEmpty()) {
IRType type = null;
// try to get the best type, just take the first one, and if the the
// latter just have Any or None types skip those.
for (IRType irType : typeSet) {
if (type == null) {
type = irType;
} else if (irType instanceof IRArrayType) {
IRType itemType = ((IRArrayType) irType).getItemType();
if (!(itemType instanceof IRNoneType || itemType instanceof IRAnyType)) {
type = irType;
}
} else if (irType instanceof IRMapType) {
IRType itemType = ((IRMapType) irType).getValueType();
if (!(itemType instanceof IRNoneType || itemType instanceof IRAnyType)) {
type = irType;
}
}
}
if (type instanceof IRArrayType
&& JavaScriptValidations.typeOf(itemReference) == null) {
final IRType itemType = ((IRArrayType) type).getItemType();
setIRType(itemReference, itemType, true);
} else if (type instanceof IRMapType
&& JavaScriptValidations.typeOf(itemReference) == null) {
final IRType itemType = ((IRMapType) type).getValueType();
setIRType(itemReference, itemType, true);
} else if (ITypeNames.XMLLIST.equals(type.getName())) {
itemReference.setDeclaredType(JSTypeSet.ref(context
.getType(ITypeNames.XML)));
}
}
visit(node.getBody());
return null;
}
@Override
public IValueReference visitForInStatement(ForInStatement node) {
final IValueReference item = visit(node.getItem());
if (item != null) {
assign(item, context.getFactory().createString(peekContext()));
}
visit(node.getIterator());
visit(node.getBody());
return null;
}
@Override
public IValueReference visitForStatement(ForStatement node) {
if (node.getInitial() != null)
visit(node.getInitial());
if (node.getCondition() != null)
visit(node.getCondition());
if (node.getStep() != null)
visit(node.getStep());
if (node.getBody() != null)
visit(node.getBody());
return null;
}
@Override
public IValueReference visitFunctionStatement(FunctionStatement node) {
final JSMethod method = generateJSMethod(node);
final IValueCollection function = new FunctionValueCollection(
peekContext(), method.getName(), node.isInlineBlock());
for (IParameter parameter : method.getParameters()) {
final IValueReference refArg = function.createChild(parameter
.getName());
refArg.setKind(ReferenceKind.ARGUMENT);
setTypeImpl(refArg, parameter.getType());
refArg.setLocation(parameter.getLocation());
}
final Identifier methodName = node.getName();
final IValueReference result;
if (isChildFunction(node)) {
result = peekContext().createChild(method.getName());
} else {
result = new AnonymousValue();
}
result.setLocation(method.getLocation());
result.setKind(ReferenceKind.FUNCTION);
result.setDeclaredType(JSTypeSet.ref(context
.getType(ITypeNames.FUNCTION)));
result.setAttribute(IReferenceAttributes.METHOD, method);
result.setAttribute(IReferenceAttributes.R_METHOD,
RModelBuilder.create(getContext(), method));
result.setAttribute(IReferenceAttributes.FUNCTION_SCOPE, function);
result.setAttribute(IReferenceAttributes.RESOLVING, Boolean.TRUE);
enterContext(function);
Set<IProblemIdentifier> suppressed = null;
try {
if (reporter != null && !method.getSuppressedWarnings().isEmpty()) {
suppressed = new HashSet<IProblemIdentifier>();
for (IProblemCategory category : method.getSuppressedWarnings()) {
suppressed.addAll(category.contents());
}
reporter.pushSuppressWarnings(suppressed);
}
visitFunctionBody(node);
} finally {
if (reporter != null && suppressed != null) {
reporter.popSuppressWarnings();
}
leaveContext();
result.setAttribute(IReferenceAttributes.RESOLVING, null);
}
final IValueReference returnValue = result
.getChild(IValueReference.FUNCTION_OP);
returnValue.addValue(function.getReturnValue(), true);
setTypeImpl(returnValue, method.getType());
return result;
}
/**
* @param node
* @param methodName
* @return
*/
protected static boolean isChildFunction(FunctionStatement node) {
return node.getName() != null
&& !(node.getParent() instanceof BinaryOperation)
&& !(node.getParent() instanceof VariableDeclaration)
&& !(node.getParent() instanceof PropertyInitializer)
&& !(node.getParent() instanceof NewExpression);
}
/**
* @param node
* @return
*/
protected JSMethod generateJSMethod(FunctionStatement node) {
final JSMethod method = new JSMethod(node, getSource());
for (IModelBuilder extension : context.getModelBuilders()) {
extension.processMethod(node, method, reporter, getTypeChecker());
}
return method;
}
public void visitFunctionBody(FunctionStatement node) {
handleDeclarations(node);
visit(node.getBody());
}
public void setType(IValueReference value, JSType type, boolean lazyEnabled) {
setTypeImpl(value, type, lazyEnabled);
}
private void setTypeImpl(IValueReference value, JSType type) {
setTypeImpl(value, type, true);
}
private void setTypeImpl(IValueReference value, JSType type,
boolean lazyEnabled) {
if (type == null) {
return;
}
final IRType rt = JSTypeSet.normalize(getContext(), type);
setIRType(value, rt, lazyEnabled);
}
/**
* @param value
* @param type
* @param lazyEnabled
* @param rt
*/
private void setIRType(IValueReference value, final IRType rt,
boolean lazyEnabled) {
if (rt instanceof IRSimpleType) {
final Type t = ((IRSimpleType) rt).getTarget();
if (t.getKind() != TypeKind.UNKNOWN) {
value.setDeclaredType(rt);
if (value instanceof IValueProvider) {
for (IMemberEvaluator evaluator : TypeInfoManager
.getMemberEvaluators()) {
final IValueCollection collection = evaluator.valueOf(
context, t);
if (collection != null) {
if (collection instanceof IValueProvider) {
((IValueProvider) value).getValue().addValue(
((IValueProvider) collection)
.getValue());
}
}
}
}
} else if (lazyEnabled) {
value.addValue(new LazyTypeReference(context, t.getName(),
peekContext()), false);
}
} else {
value.setDeclaredType(rt);
}
}
@Override
public IValueReference visitGetAllChildrenExpression(
GetAllChildrenExpression node) {
return context.getFactory().createXMLList(peekContext());
}
@Override
public IValueReference visitGetArrayItemExpression(
GetArrayItemExpression node) {
final IValueReference array = visit(node.getArray());
visit(node.getIndex());
if (array != null) {
// always just create the ARRAY_OP child (for code completion)
IValueReference child = array.getChild(IValueReference.ARRAY_OP);
IRType arrayType = null;
if (array.getDeclaredType() != null) {
arrayType = TypeUtil.extractArrayItemType(array
.getDeclaredType());
} else {
JSTypeSet types = array.getTypes();
if (types.size() > 0)
arrayType = TypeUtil.extractArrayItemType(types.getFirst());
}
if (arrayType != null && child.getDeclaredType() == null) {
setIRType(child, arrayType, true);
}
if (node.getIndex() instanceof StringLiteral) {
IValueReference namedChild = extractNamedChild(array,
node.getIndex());
if (namedChild.exists()) {
child = namedChild;
if (arrayType != null && child.getDeclaredType() == null) {
child.setDeclaredType(arrayType);
}
}
}
return child;
}
return null;
}
@Override
public IValueReference visitGetLocalNameExpression(
GetLocalNameExpression node) {
return null;
}
@Override
public IValueReference visitIdentifier(Identifier node) {
return peekContext().getChild(node.getName());
}
private Boolean evaluateCondition(Expression condition) {
if (condition instanceof BooleanLiteral) {
return Boolean.valueOf(((BooleanLiteral) condition).getText());
} else {
return null;
}
}
@Override
public IValueReference visitIfStatement(IfStatement node) {
visit(node.getCondition());
visitIfStatements(node);
return null;
}
protected void visitIfStatements(IfStatement node) {
final List<Statement> statements = new ArrayList<Statement>(2);
Statement onlyBranch = null;
final Boolean condition = evaluateCondition(node.getCondition());
if ((condition == null || condition.booleanValue())
&& node.getThenStatement() != null) {
statements.add(node.getThenStatement());
if (condition != null && condition.booleanValue()) {
onlyBranch = node.getThenStatement();
}
}
if ((condition == null || !condition.booleanValue())
&& node.getElseStatement() != null) {
statements.add(node.getElseStatement());
if (condition != null && !condition.booleanValue()) {
onlyBranch = node.getElseStatement();
}
}
if (!statements.isEmpty()) {
if (statements.size() == 1) {
if (statements.get(0) == onlyBranch) {
visit(statements.get(0));
} else {
final Branching branching = branching();
visit(statements.get(0));
branching.end();
}
} else {
final Branching branching = branching();
final List<NestedValueCollection> collections = new ArrayList<NestedValueCollection>(
statements.size());
for (Statement statement : statements) {
final NestedValueCollection nestedCollection = new NestedValueCollection(
peekContext());
enterContext(nestedCollection);
visit(statement);
leaveContext();
collections.add(nestedCollection);
}
NestedValueCollection.mergeTo(peekContext(), collections);
branching.end();
}
}
}
@Override
public IValueReference visitLabelledStatement(LabelledStatement node) {
if (node.getStatement() != null)
visit(node.getStatement());
return null;
}
protected static class AnonymousNewValue extends AnonymousValue {
@Override
public IValueReference getChild(String name) {
if (name.equals(IValueReference.FUNCTION_OP))
return this;
return super.getChild(name);
}
}
public static class VisitNewResult {
IValueReference typeValue;
IValueReference[] arguments;
IValueReference value;
public IValueReference getValue() {
return value;
}
public IValueReference getTypeValue() {
return typeValue;
}
public IValueReference[] getArguments() {
return arguments;
}
}
protected VisitNewResult visitNew(NewExpression node) {
final VisitNewResult result = new VisitNewResult();
Expression objectClass = node.getObjectClass();
if (objectClass instanceof CallExpression) {
final CallExpression call = (CallExpression) objectClass;
result.arguments = new IValueReference[call.getArguments().size()];
int index = 0;
for (ASTNode argument : call.getArguments()) {
result.arguments[index++] = visit(argument);
}
objectClass = call.getExpression();
} else {
result.arguments = new IValueReference[0];
}
result.typeValue = visit(objectClass);
if (result.typeValue != null) {
if (result.typeValue.getKind() == ReferenceKind.FUNCTION) {
Object fs = result.typeValue
.getAttribute(IReferenceAttributes.FUNCTION_SCOPE);
if (fs instanceof IValueCollection
&& ((IValueCollection) fs).getThis() != null) {
result.value = new AnonymousNewValue();
result.value.setValue(((IValueCollection) fs).getThis());
result.value.setKind(ReferenceKind.TYPE);
String className = PropertyExpressionUtils
.getPath(objectClass);
if (className != null) {
Type type = TypeInfoModelFactory.eINSTANCE.createType();
type.setSuperType(context.getKnownType(OBJECT, null));
type.setKind(TypeKind.JAVASCRIPT);
type.setName(className);
result.value.setDeclaredType(JSTypeSet.ref(type));
} else {
result.value.setDeclaredType(JSTypeSet.ref(OBJECT));
}
}
} else if (result.typeValue.exists()) {
for (IRType type : result.typeValue.getDeclaredTypes()) {
if (type instanceof IRClassType) {
result.value = new AnonymousNewValue();
result.value.setKind(ReferenceKind.TYPE);
result.value.setDeclaredType(((IRClassType) type)
.toItemType());
return result;
}
}
for (IRType type : result.typeValue.getTypes()) {
if (type instanceof IRClassType) {
result.value = new AnonymousNewValue();
result.value.setKind(ReferenceKind.TYPE);
result.value.setDeclaredType(((IRClassType) type)
.toItemType());
return result;
}
}
}
}
if (result.value == null) {
final String className = PropertyExpressionUtils
.getPath(objectClass);
IValueCollection contextValueCollection = peekContext();
if (className != null) {
Type knownType = context.getKnownType(className, TypeMode.CODE);
if (knownType != null) {
result.value = new AnonymousNewValue();
result.value.setValue(context.getFactory().create(
contextValueCollection, JSTypeSet.ref(knownType)));
result.value.setKind(ReferenceKind.TYPE);
} else {
result.value = new LazyTypeReference(context, className,
contextValueCollection);
}
} else {
result.value = new AnonymousNewValue();
result.value.setValue(context.getFactory().createObject(
contextValueCollection));
}
}
return result;
}
@Override
public IValueReference visitNewExpression(NewExpression node) {
return visitNew(node).getValue();
}
@Override
public IValueReference visitNullExpression(NullExpression node) {
return null;
}
@Override
public IValueReference visitObjectInitializer(ObjectInitializer node) {
final IValueReference result = new AnonymousValue();
result.setDeclaredType(JSTypeSet.ref(OBJECT));
for (ObjectInitializerPart part : node.getInitializers()) {
if (part instanceof PropertyInitializer) {
final PropertyInitializer pi = (PropertyInitializer) part;
final IValueReference child = extractNamedChild(result,
pi.getName());
final IValueReference value = visit(pi.getValue());
if (child != null) {
child.setValue(value);
child.setLocation(ReferenceLocation.create(getSource(), pi
.getName().sourceStart(), pi.getName().sourceEnd()));
if (child.getKind() == ReferenceKind.UNKNOWN) {
child.setKind(ReferenceKind.FIELD);
}
}
} else {
// TODO handle get/set methods
}
}
return result;
}
@Override
public IValueReference visitParenthesizedExpression(
ParenthesizedExpression node) {
return visit(node.getExpression());
}
@Override
public IValueReference visitPropertyExpression(PropertyExpression node) {
final IValueReference object = visit(node.getObject());
return extractNamedChild(object, node.getProperty());
}
protected IValueReference extractNamedChild(IValueReference parent,
Expression name) {
if (parent != null) {
final String nameStr;
if (name instanceof Identifier) {
nameStr = ((Identifier) name).getName();
IRType parentType = JavaScriptValidations.typeOf(parent);
if (parentType != null && isXML(parentType)) {
IValueReference child = parent.getChild(nameStr);
if (child != null && child.getDeclaredType() == null) {
child.setDeclaredType(JSTypeSet.ref(ITypeNames.XML));
return child;
}
}
} else if (name instanceof StringLiteral) {
nameStr = ((StringLiteral) name).getValue();
} else if (name instanceof XmlAttributeIdentifier) {
if (((XmlAttributeIdentifier) name).getExpression() instanceof AsteriskExpression) {
return visitAsteriskExpression((AsteriskExpression) ((XmlAttributeIdentifier) name)
.getExpression());
} else {
nameStr = ((XmlAttributeIdentifier) name)
.getAttributeName();
IValueReference child = parent.getChild(nameStr);
if (child != null && child.getDeclaredType() == null) {
child.setDeclaredType(JSTypeSet.ref(ITypeNames.XML));
return child;
}
}
} else if (name instanceof AsteriskExpression) {
return visitAsteriskExpression((AsteriskExpression) name);
} else if (name instanceof ParenthesizedExpression) {
visitParenthesizedExpression((ParenthesizedExpression) name);
return parent;
} else {
return null;
}
return parent.getChild(nameStr);
}
return null;
}
@Override
public IValueReference visitRegExpLiteral(RegExpLiteral node) {
return context.getFactory().createRegExp(peekContext());
}
@Override
public IValueReference visitReturnStatement(ReturnStatement node) {
if (node.getValue() != null) {
final IValueReference value = visit(node.getValue());
if (value != null) {
final IValueReference returnValue = peekContext()
.getReturnValue();
if (returnValue != null) {
returnValue.addValue(value,
!(value instanceof LazyTypeReference));
}
}
return value;
}
return null;
}
@Override
public IValueReference visitScript(Script node) {
handleDeclarations(node);
return visit(node.getStatements());
}
private void handleDeclarations(JSScope scope) {
for (JSDeclaration declaration : scope.getDeclarations()) {
if (declaration instanceof FunctionStatement) {
} else if (declaration instanceof VariableDeclaration) {
}
}
}
@Override
public IValueReference visitStatementBlock(StatementBlock node) {
for (Statement statement : node.getStatements()) {
visit(statement);
}
return null;
}
@Override
public IValueReference visitStringLiteral(StringLiteral node) {
return context.getFactory().createString(peekContext());
}
@Override
public IValueReference visitSwitchStatement(SwitchStatement node) {
if (node.getCondition() != null)
visit(node.getCondition());
for (SwitchComponent component : node.getCaseClauses()) {
if (component instanceof CaseClause) {
visit(((CaseClause) component).getCondition());
}
visit(component.getStatements());
}
return null;
}
@Override
public IValueReference visitThisExpression(ThisExpression node) {
return peekContext().getThis();
}
@Override
public IValueReference visitThrowStatement(ThrowStatement node) {
if (node.getException() != null)
visit(node.getException());
return null;
}
@Override
public IValueReference visitTryStatement(TryStatement node) {
visit(node.getBody());
for (CatchClause catchClause : node.getCatches()) {
final NestedValueCollection collection = new NestedValueCollection(
peekContext());
IValueReference exception = collection.createChild(catchClause
.getException().getName());
exception.setDeclaredType(JSTypeSet.ref(ITypeNames.ERROR));
enterContext(collection);
if (catchClause.getStatement() != null) {
visit(catchClause.getStatement());
}
leaveContext();
}
if (node.getFinally() != null) {
visit(node.getFinally().getStatement());
}
return null;
}
@Override
public IValueReference visitUnaryOperation(UnaryOperation node) {
if (node.getOperation() == JSParser.NOT) {
visit(node.getExpression());
return context.getFactory().createBoolean(peekContext());
} else if (node.getOperation() == JSParser.DELETE) {
final IValueReference value = visit(node.getExpression());
if (value != null) {
value.delete();
}
return context.getFactory().createBoolean(peekContext());
} else if (node.getOperation() == JSParser.TYPEOF) {
visit(node.getExpression());
return context.getFactory().createString(peekContext());
} else if (node.getOperation() == JSParser.VOID) {
visit(node.getExpression());
return null;
} else {
return visit(node.getExpression());
}
}
@Override
public IValueReference visitVariableStatement(VariableStatement node) {
final IValueCollection collection = peekContext();
IValueReference result = null;
for (VariableDeclaration declaration : node.getVariables()) {
result = createVariable(collection, declaration);
}
return result;
}
@Override
public IValueReference visitVoidExpression(VoidExpression node) {
visit(node.getExpression());
return null;
}
@Override
public IValueReference visitWhileStatement(WhileStatement node) {
if (node.getCondition() != null)
visit(node.getCondition());
if (node.getBody() != null)
visit(node.getBody());
return null;
}
@Override
public IValueReference visitWithStatement(WithStatement node) {
final IValueReference with = visit(node.getExpression());
if (with != null) {
final WithValueCollection withCollection = new WithValueCollection(
peekContext(), with);
enterContext(withCollection);
visit(node.getStatement());
leaveContext();
} else {
visit(node.getStatement());
}
return null;
}
private static final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
.newInstance();
private DocumentBuilder docBuilder;
/**
* @return
* @throws ParserConfigurationException
*/
private DocumentBuilder getDocumentBuilder()
throws ParserConfigurationException {
if (docBuilder == null)
docBuilder = docBuilderFactory.newDocumentBuilder();
return docBuilder;
}
@Override
public IValueReference visitXmlLiteral(XmlLiteral node) {
IValueReference xmlValueReference = context.getFactory().createXML(
peekContext());
if (xmlValueReference instanceof IValueProvider) {
IRType xmlType = JSTypeSet.ref(context.getKnownType(ITypeNames.XML,
null));
IValue xmlValue = ((IValueProvider) xmlValueReference).getValue();
List<XmlFragment> fragments = node.getFragments();
StringBuilder xml = new StringBuilder();
for (XmlFragment xmlFragment : fragments) {
if (xmlFragment instanceof XmlTextFragment) {
String xmlText = ((XmlTextFragment) xmlFragment).getXml();
if (xmlText.equals("<></>"))
continue;
if (xmlText.startsWith("<>") && xmlText.endsWith("</>")) {
xmlText = "<xml>"
+ xmlText.substring(2, xmlText.length() - 3)
+ "</xml>";
}
xml.append(xmlText);
} else if (xmlFragment instanceof XmlExpressionFragment) {
Expression expression = ((XmlExpressionFragment) xmlFragment)
.getExpression();
visit(expression);
if (xml.charAt(xml.length() - 1) == '<'
|| xml.subSequence(xml.length() - 2, xml.length())
.equals("</")) {
if (expression instanceof Identifier) {
xml.append(((Identifier) expression).getName());
} else {
xml.setLength(0);
break;
}
} else
xml.append("\"\" ");
}
}
if (xml.length() > 0) {
try {
DocumentBuilder docBuilder = getDocumentBuilder();
Document doc = docBuilder.parse(new InputSource(
new StringReader(xml.toString())));
NodeList nl = doc.getChildNodes();
if (nl.getLength() == 1) {
Node item = nl.item(0);
NamedNodeMap attributes = item.getAttributes();
for (int a = 0; a < attributes.getLength(); a++) {
Node attribute = attributes.item(a);
xmlValue.createChild("@" + attribute.getNodeName(),
0);
}
createXmlChilds(xmlType, xmlValue, item.getChildNodes());
} else {
System.err.println("root should be 1 child?? " + xml);
}
} catch (Exception e) {
}
}
}
return xmlValueReference;
}
/**
* @param xmlType
* @param xmlValue
* @param nl
*/
private void createXmlChilds(IRType xmlType, IValue xmlValue, NodeList nl) {
for (int i = 0; i < nl.getLength(); i++) {
Node item = nl.item(i);
if (item.getNodeType() == Node.TEXT_NODE) {
String value = item.getNodeValue();
if (value == null || "".equals(value.trim())) {
continue;
}
}
IValue nodeValue = xmlValue.createChild(item.getNodeName(), 0);
nodeValue.setDeclaredType(xmlType);
NamedNodeMap attributes = item.getAttributes();
if (attributes != null) {
for (int a = 0; a < attributes.getLength(); a++) {
Node attribute = attributes.item(a);
nodeValue.createChild("@" + attribute.getNodeName(), 0);
}
}
createXmlChilds(xmlType, nodeValue, item.getChildNodes());
}
}
@Override
public IValueReference visitXmlPropertyIdentifier(
XmlAttributeIdentifier node) {
return context.getFactory().createXML(peekContext());
}
@Override
public IValueReference visitYieldOperator(YieldOperator node) {
final IValueReference value = visit(node.getExpression());
if (value != null) {
final IValueReference reference = peekContext().getReturnValue();
if (reference != null) {
reference.addValue(value, true);
}
}
return null;
}
public static boolean isFunctionDeclaration(Expression expression) {
PropertyExpression pe = null;
if (expression instanceof PropertyExpression)
pe = (PropertyExpression) expression;
else if (expression.getParent() instanceof PropertyExpression)
pe = (PropertyExpression) expression.getParent();
if (pe != null && pe.getObject() instanceof ThisExpression
&& pe.getParent() instanceof BinaryOperation) {
return ((BinaryOperation) pe.getParent()).getRightExpression() instanceof FunctionStatement;
}
return false;
}
}