| /******************************************************************************* |
| * 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; |
| } |
| |
| } |