/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.php.internal.typebinding; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org2.eclipse.php.core.compiler.PHPFlags; import org2.eclipse.php.internal.core.ast.nodes.ASTNode; import org2.eclipse.php.internal.core.ast.nodes.Assignment; import org2.eclipse.php.internal.core.ast.nodes.Block; import org2.eclipse.php.internal.core.ast.nodes.CatchClause; import org2.eclipse.php.internal.core.ast.nodes.ClassDeclaration; import org2.eclipse.php.internal.core.ast.nodes.ClassInstanceCreation; import org2.eclipse.php.internal.core.ast.nodes.ClassName; import org2.eclipse.php.internal.core.ast.nodes.Comment; import org2.eclipse.php.internal.core.ast.nodes.ConstantDeclaration; import org2.eclipse.php.internal.core.ast.nodes.Dispatch; import org2.eclipse.php.internal.core.ast.nodes.DoStatement; import org2.eclipse.php.internal.core.ast.nodes.Expression; import org2.eclipse.php.internal.core.ast.nodes.ExpressionStatement; import org2.eclipse.php.internal.core.ast.nodes.FieldAccess; import org2.eclipse.php.internal.core.ast.nodes.FieldsDeclaration; import org2.eclipse.php.internal.core.ast.nodes.ForEachStatement; import org2.eclipse.php.internal.core.ast.nodes.ForStatement; import org2.eclipse.php.internal.core.ast.nodes.FormalParameter; import org2.eclipse.php.internal.core.ast.nodes.FunctionDeclaration; import org2.eclipse.php.internal.core.ast.nodes.FunctionInvocation; import org2.eclipse.php.internal.core.ast.nodes.FunctionName; import org2.eclipse.php.internal.core.ast.nodes.GlobalStatement; import org2.eclipse.php.internal.core.ast.nodes.Identifier; import org2.eclipse.php.internal.core.ast.nodes.IfStatement; import org2.eclipse.php.internal.core.ast.nodes.Include; import org2.eclipse.php.internal.core.ast.nodes.InfixExpression; import org2.eclipse.php.internal.core.ast.nodes.InterfaceDeclaration; import org2.eclipse.php.internal.core.ast.nodes.MethodDeclaration; import org2.eclipse.php.internal.core.ast.nodes.MethodInvocation; import org2.eclipse.php.internal.core.ast.nodes.ParenthesisExpression; import org2.eclipse.php.internal.core.ast.nodes.Program; import org2.eclipse.php.internal.core.ast.nodes.ReturnStatement; import org2.eclipse.php.internal.core.ast.nodes.Scalar; import org2.eclipse.php.internal.core.ast.nodes.StaticConstantAccess; import org2.eclipse.php.internal.core.ast.nodes.StaticDispatch; import org2.eclipse.php.internal.core.ast.nodes.StaticFieldAccess; import org2.eclipse.php.internal.core.ast.nodes.StaticMethodInvocation; import org2.eclipse.php.internal.core.ast.nodes.StaticStatement; import org2.eclipse.php.internal.core.ast.nodes.SwitchStatement; import org2.eclipse.php.internal.core.ast.nodes.TryStatement; import org2.eclipse.php.internal.core.ast.nodes.TypeDeclaration; import org2.eclipse.php.internal.core.ast.nodes.Variable; import org2.eclipse.php.internal.core.ast.nodes.VariableBase; import org2.eclipse.php.internal.core.ast.nodes.WhileStatement; import org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor; import org2.eclipse.php.internal.core.documentModel.phpElementData.IPHPDoc; import com.aptana.core.logging.IdeLog; import com.aptana.editor.php.PHPEditorPlugin; import com.aptana.editor.php.core.IPHPTypeConstants; import com.aptana.editor.php.core.model.ISourceModule; import com.aptana.editor.php.core.model.IType; import com.aptana.editor.php.core.typebinding.IBinding; import com.aptana.editor.php.core.typebinding.IBindingReporter; import com.aptana.editor.php.core.typebinding.ITypeBinding; import com.aptana.editor.php.core.typebinding.TypeBinding; import com.aptana.editor.php.indexer.IElementEntry; import com.aptana.editor.php.indexer.IElementsIndex; import com.aptana.editor.php.indexer.PHPGlobalIndexer; import com.aptana.editor.php.internal.core.typebinding.MethodBinding; import com.aptana.editor.php.internal.core.typebinding.ModuleBinding; import com.aptana.editor.php.internal.indexer.CallPath; import com.aptana.editor.php.internal.indexer.ClassPHPEntryValue; import com.aptana.editor.php.internal.indexer.FunctionPHPEntryValue; import com.aptana.editor.php.internal.indexer.FunctionPathReference; import com.aptana.editor.php.internal.indexer.PHPDocUtils; import com.aptana.editor.php.internal.indexer.PHPTypeProcessor; import com.aptana.editor.php.internal.indexer.StaticPathReference; import com.aptana.editor.php.internal.indexer.VariablePHPEntryValue; import com.aptana.editor.php.internal.indexer.VariablePathReference; import com.aptana.editor.php.internal.model.impl.SourceModule; import com.aptana.editor.php.internal.parser.phpdoc.FunctionDocumentation; import com.aptana.editor.php.internal.parser.phpdoc.TypedDescription; import com.aptana.editor.php.internal.search.PHPSearchEngine; /** * PHP type binding builder. * * @author Denis Denisenko * @author Pavel Petrochenko * @author Shalom Gibly <sgibly@aptana.com> * @see #buildBindings(Program, String) for the main entry point of this class. */ @SuppressWarnings("unused") public class TypeBindingBuilder { /** * This. */ private static final String THIS = "this"; //$NON-NLS-1$ /** * Self. */ private static final String SELF = "self"; //$NON-NLS-1$ /** * Define function name. */ private static final String DEFINE = "define"; //$NON-NLS-1$ /** * Main entry point for building the bindings for a given Program (AST). * * @param program */ public static void buildBindings(Program program) { buildBindings(program, null); } /** * Main entry point for building the bindings for a given Program (AST). * * @param program * @param source */ public static void buildBindings(Program program, String source) { if (program != null) { new TypeBindingBuilder().indexModule(program, source, new IBindingReporter() { public void report(ASTNode node, IBinding binding) { node.setBinding(binding); } }); program.setBindingCompleted(true); } else { IdeLog.logError(PHPEditorPlugin.getDefault(), "Error building the PHP bindings for the AST", //$NON-NLS-1$ new IllegalArgumentException("Cannot build the PHP bindings with a null AST")); //$NON-NLS-1$ } } /** * Variable info. * * @author Denis Denisenko */ private static class VariableInfo { /** * Variable name. */ private String variableName; /** * Variable types. */ private Set<Object> variableTypes; /** * Variable scope. */ private Scope scope; /** * Node start. */ private int nodeStart = 0; private int modifier = 0; /** * VariableInfo constructor. * * @param variableName * - variable name. * @param variableType * - variable type. * @param scope * @param pos */ private VariableInfo(String variableName, Object variableType, Scope scope, int pos) { this.variableName = variableName; variableTypes = new HashSet<Object>(1); if (variableType != null) { variableTypes.add(variableType); } this.scope = scope; nodeStart = pos; } /** * VariableInfo constructor. * * @param variableName * - variable name. * @param variableTypes * - variable types. * @param scope * @param pos */ private VariableInfo(String variableName, Set<Object> variableTypes, Scope scope, int pos) { this.variableName = variableName; this.variableTypes = variableTypes; this.scope = scope; nodeStart = pos; } /** * VariableInfo constructor. * * @param variableName * - variable name. * @param variableTypes * - variable types. * @param scope * - variable scope. * @param pos * - variable declaration position. * @param modifier * - variable modifier. */ private VariableInfo(String variableName, Set<Object> variableTypes, Scope scope, int pos, int modifier) { this.variableName = variableName; this.variableTypes = variableTypes; this.scope = scope; nodeStart = pos; this.modifier = modifier; } /** * Gets variable type. * * @return variable type. */ public Set<Object> getVariableTypes() { if (variableTypes != null) { return variableTypes; } else { return Collections.emptySet(); } } /** * Sets variable type. * * @param variableType * - type to set. */ public void setVariableType(Object variableType) { variableTypes = new HashSet<Object>(1); variableTypes.add(variableType); } /** * Sets variable types. * * @param variableType */ public void setVariableTypes(Collection<Object> variableType) { variableTypes = new HashSet<Object>(); variableTypes.addAll(variableType); } /** * Adds variable type. * * @param variableType * - type to add. */ public void addVariableType(Object variableType) { if (variableTypes == null) { variableTypes = new HashSet<Object>(); } variableTypes.add(variableType); } /** * Gets variable name. * * @return variable name. */ public String getName() { return variableName; } /** * Gets scope. * * @return scope. */ public Scope getScope() { return scope; } /** * {@inheritDoc} */ @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append(variableName); buffer.append(" types:"); //$NON-NLS-1$ buffer.append(variableTypes.toString()); return buffer.toString(); } /** * {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((variableName == null) ? 0 : variableName.hashCode()); return result; } /** * Gets node start. * * @return node start. */ public int getNodeStart() { return nodeStart; } // /** // * Sets node start. // * @param nodeStart - node start to set. // */ // public void setNodeStart(int nodeStart) // { // this.nodeStart = nodeStart; // } /** * Gets node modifier. * * @return modifier */ public int getModifier() { return modifier; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final VariableInfo other = (VariableInfo) obj; if (variableName == null) { if (other.variableName != null) return false; } else if (!variableName.equals(other.variableName)) return false; return true; } } /** * Variables scope. * * @author Denis Denisenko */ private static class Scope { /** * Scope root. */ private ASTNode root; /** * Root entry. */ private IElementEntry entry; /** * Scope variables. */ private Map<String, VariableInfo> variables; /** * Scope parent. */ private Scope parent; /** * Set of variables imported from global scope. */ private Set<String> globalImports = new HashSet<String>(); /** * Scope constructor. * * @param root * - scope root. * @param parent * - scope parent. */ private Scope(ASTNode root, Scope parent) { this.root = root; this.parent = parent; this.entry = null; variables = new HashMap<String, VariableInfo>(1); } /** * Scope constructor. * * @param root * - scope root. * @param parent * - scope parent. * @param entry * - root entry if exist. null is acceptable. */ private Scope(ASTNode root, Scope parent, IElementEntry entry) { this.root = root; this.parent = parent; this.entry = entry; variables = new HashMap<String, VariableInfo>(1); } /** * Gets scope global imports. * * @return scope golobal imports. */ public Set<String> getGlobalImports() { return globalImports; } /** * Adds variables to import from global scope. * * @param imports * - set of imports. */ public void addGlobalImports(Set<String> imports) { globalImports.addAll(imports); } /** * Adds variable to import from global scope. * * @param importVariable * - name of variable to import. */ public void addGlobalImport(String importVariable) { globalImports.add(importVariable); } /** * Gets whether this scope is global. * * @return true if global, false otherwise. */ public boolean isGlobalScope() { return root instanceof Program; } /** * Adds variable to the scope. If variable has a type specified and this or parent scopes do contain such a * variable, current type would be added to the list of variable types. * * @param variable * - variable info. */ public void addVariable(VariableInfo variable) { VariableInfo existingVariable = getVariable(variable.getName()); if (existingVariable != null) { if (variable.getVariableTypes() != null && !variable.getVariableTypes().isEmpty()) { if (this.equals(existingVariable.getScope())) { existingVariable.setVariableTypes(variable.getVariableTypes()); } else { for (Object type : variable.getVariableTypes()) { existingVariable.addVariableType(type); } } } } else { variables.put(variable.getName(), variable); } } /** * Gets variable by name from current scope or nearest of the parent scopes. * * @param name * - variable name. * @return variable info. */ public VariableInfo getVariable(String name) { return getVariable(name, new HashSet<String>()); } /** * Gets variable by name from current scope or nearest of the parent scopes. * * @param name * - variable name. * @param importsFromGlobal * - set of variables that might be imported from global scope. * @return variable info. */ public VariableInfo getVariable(String name, Set<String> importsFromGlobal) { if (globalImports != null && !globalImports.isEmpty()) { importsFromGlobal.addAll(globalImports); } VariableInfo info = variables.get(name); if (info != null) { return info; } if (parent != null) { if (parent.isGlobalScope()) { if (importsFromGlobal.contains(name) || !(root instanceof FunctionDeclaration || root instanceof ClassDeclaration)) { return parent.getVariable(name, importsFromGlobal); } } else { return parent.getVariable(name, importsFromGlobal); } /* * if (!parent.isGlobalScope() || importsFromGlobal.contains(name)) { return parent.getVariable(name, * importsFromGlobal); } */ } return null; } /** * Gets root element entry. * * @return rooot element entry. */ public IElementEntry getEntry() { return entry; } /** * Gets scope root. * * @return scope root node. */ public ASTNode getRoot() { return root; } /** * Gets scope parent scope or null if parent not exist. * * @return scope parent. */ public Scope getParent() { return parent; } /** * Gets unmodifiable variables set including variables from the upper scope being aware of global statement. * * @return unmodifiable variables set. */ public Set<VariableInfo> getVariables() { return getVariables(new HashSet<String>()); } /** * Gets variables or nearest of the parent scopes. * * @param importsFromGlobal * - set of variables that might be imported from global scope. * @return variables. */ private Set<VariableInfo> getVariables(Set<String> importsFromGlobal) { if (globalImports != null && !globalImports.isEmpty()) { importsFromGlobal.addAll(globalImports); } Set<VariableInfo> result = new HashSet<VariableInfo>(); for (VariableInfo variable : variables.values()) { result.add(variable); } if (parent != null) { if (!parent.isGlobalScope()) { result.addAll(parent.getVariables(importsFromGlobal)); } else { if (automaticallyDeriveGlobalVariables()) { result.addAll(parent.getVariables(new HashSet<String>())); } else { for (String var : importsFromGlobal) { VariableInfo varInfo = parent.getVariable(var, importsFromGlobal); if (varInfo != null) { result.add(varInfo); } } } } } return result; } /** * Checks whether this node must automatically derive variables from the global scope. * * @return true if this node must automatically derive variables from the global scope, false otherwise. */ private boolean automaticallyDeriveGlobalVariables() { if (!parent.isGlobalScope()) { return false; } if (this.getRoot() instanceof TypeDeclaration || this.getRoot() instanceof FunctionDeclaration || this.getRoot() instanceof MethodDeclaration) { return false; } return true; } } /** * Class scope information. * * @author Denis Denisenko */ private static class ClassScopeInfo { /** * Class fields. */ private Map<String, IElementEntry> fields = new HashMap<String, IElementEntry>(); private String name; /** * ClassScopeInfo constructor. * * @param classEntry * - class entry. */ private ClassScopeInfo() { } private ClassScopeInfo(String name) { this.name = name; } public String getName() { return name; } /** * Gets class fields. * * @return class fields. */ public Collection<IElementEntry> getFields() { return fields.values(); } /** * Checks whether class has a field with a name specified. * * @param fieldName * - field name to check. * @return true if has field, false otherwise. */ public boolean hasField(String fieldName) { return fields.containsKey(fieldName); } /** * Gets field by name. * * @param fieldName * - field name to get. * @return field entry or null. */ public IElementEntry getField(String fieldName) { return fields.get(fieldName); } /** * Adds new field. * * @param fieldName * - field name. * @param field * - field entry. */ public void setField(String fieldName, IElementEntry field) { fields.put(fieldName, field); } /** * Adds field types. * * @param fieldName * - field name. * @param types * - types to add. * @return true if types are added, false otherwise. */ public boolean addFieldTypes(String fieldName, Set<Object> types) { IElementEntry fieldEntry = fields.get(fieldName); if (fieldEntry == null) { return false; } Object entryValue = fieldEntry.getValue(); if (!(entryValue instanceof VariablePHPEntryValue)) { return false; } VariablePHPEntryValue value = (VariablePHPEntryValue) entryValue; for (Object type : types) { value.addType(type); } return true; } } /** * AST visitor. * * @author Denis Denisenko */ private class PHPASTVisitor extends AbstractVisitor { /** * Reporter. */ private IBindingReporter reporter; private Map<String, ITypeBinding> binding = new HashMap<String, ITypeBinding>(); /** * Module. */ private ISourceModule module; /** * Current class. */ private ClassScopeInfo currentClass; /** * Current function. */ private IElementEntry currentFunction; /** * Variable scopes. */ private Stack<Scope> scopes = new Stack<Scope>(); private String currentNamespace = ""; //$NON-NLS-1$ // /** // * Backuped variable scopes. // */ // private Stack<Scope> backupedScopes = new Stack<Scope>(); // // /** // * Scopes of the previous node. // */ // private Stack<Scope> previousNodeScopes = new Stack<Scope>(); /** * Whether the local stack was reported (required for local mode). */ boolean localStackReported = false; private Map<String, Set<Object>> localParametersMap; /** * PHPASTVisitor constructor. * * @param reporter2 * - reporter to use. * @param program * - current module. */ private PHPASTVisitor(IBindingReporter reporter2, Program program) { this.reporter = reporter2; this.module = program.getSourceModule(); } /** * {@inheritDoc} */ @Override public boolean visit(ClassDeclaration classDeclaration) { // int category = IPHPIndexConstants.CLASS_CATEGORY; List<Identifier> interfaces = classDeclaration.interfaces(); List<String> interfaceNames = new ArrayList<String>(interfaces.size()); for (int i = 0; i < interfaces.size(); i++) { interfaceNames.add(interfaces.get(i).getName()); Identifier identifier = interfaces.get(i); identifier.setBinding(resolveTypeBinding(interfaces.get(i).getName())); } Expression superClassIdentifier = classDeclaration.getSuperClass(); String superClassName = null; if (superClassIdentifier != null && (superClassIdentifier.getType() == ASTNode.NAMESPACE_NAME || superClassIdentifier.getType() == ASTNode.IDENTIFIER)) { superClassName = ((Identifier) superClassIdentifier).getName(); superClassIdentifier.setBinding(resolveTypeBinding(superClassName)); } classDeclaration.setBinding(resolveTypeBinding(classDeclaration.getName().getName())); ClassPHPEntryValue value = new ClassPHPEntryValue(classDeclaration.getModifier(), superClassName, interfaceNames, currentNamespace); value.setStartOffset(classDeclaration.getStart()); value.setEndOffset(classDeclaration.getEnd()); // FIXME IElementEntry currentClassEntry = // reporter.reportEntry(category, // classDeclaration.getName().getName(),value, module); currentClass = new ClassScopeInfo(classDeclaration.getName().getName()); return true; } private IBinding resolveTypeBinding(String superClassName) { // TODO HANDLE IT BETTER IType[] convertClasses = PHPSearchEngine.getInstance().findTypes(superClassName, null); if (convertClasses != null && convertClasses.length > 0) { return new TypeBinding(convertClasses[0]); } return null; } /** * {@inheritDoc} */ @Override public boolean visit(InterfaceDeclaration interfaceDeclaration) { List<Identifier> interfaces = interfaceDeclaration.interfaces(); List<String> interfaceNames = new ArrayList<String>(interfaces.size()); for (Identifier interfaceName : interfaces) { interfaceNames.add(interfaceName.getName()); } ClassPHPEntryValue value = new ClassPHPEntryValue(PHPFlags.AccInterface, null, interfaceNames, currentNamespace); value.setStartOffset(interfaceDeclaration.getStart()); value.setEndOffset(interfaceDeclaration.getEnd()); // IElementEntry currentClassEntry = reporter.reportEntry(category, // interfaceDeclaration.getName().getName(), // value, module); // FIXME // currentClass = new ClassScopeInfo(currentClassEntry); return true; } @Override public boolean visit(StaticConstantAccess classConstantAccess) { visitStaticDispatch(classConstantAccess); return super.visit(classConstantAccess); } private void visitStaticDispatch(StaticDispatch classConstantAccess) { Expression className = classConstantAccess.getClassName(); if (className != null && (className.getType() == ASTNode.NAMESPACE_NAME || className.getType() == ASTNode.IDENTIFIER)) { IBinding resolveTypeBinding = resolveTypeBinding(((Identifier) className).getName()); reporter.report(classConstantAccess, resolveTypeBinding); reporter.report(classConstantAccess.getClassName(), resolveTypeBinding); } } @Override public boolean visit(StaticMethodInvocation staticMethodInvocation) { visitStaticDispatch(staticMethodInvocation); return super.visit(staticMethodInvocation); } /** * {@inheritDoc} */ @Override public boolean visit(FunctionInvocation functionInvocation) { FunctionName funcName = functionInvocation.getFunctionName(); if (funcName == null) { return true; } Expression functionName = funcName.getName(); if (functionName != null) { if (functionName.getType() == ASTNode.IDENTIFIER) { if (!DEFINE.equals(((Identifier) functionName).getName())) { return true; } } if (functionName.getType() == ASTNode.VARIABLE) { Variable vr = (Variable) functionName; Expression name = vr.getName(); if (name != null && name.getType() == ASTNode.IDENTIFIER) { if (!DEFINE.equals(((Identifier) name).getName())) { return true; } } } } List<Expression> parameters = functionInvocation.parameters(); if (parameters.size() < 2) { return true; } if (!(parameters.get(0).getType() == ASTNode.SCALAR)) { return true; } if (Scalar.TYPE_STRING != ((Scalar) parameters.get(0)).getScalarType()) { return true; } // getting define name String defineName = ((Scalar) parameters.get(0)).getStringValue(); if (defineName.startsWith("\"")) //$NON-NLS-1$ { defineName = defineName.substring(1); } if (defineName.endsWith("\"")) //$NON-NLS-1$ { defineName = defineName.substring(0, defineName.length() - 1); } if (defineName.startsWith("\'")) //$NON-NLS-1$; { defineName = defineName.substring(1); } if (defineName.endsWith("\'")) //$NON-NLS-1$ { defineName = defineName.substring(0, defineName.length() - 1); } Set<Object> defineTypes = countExpressionTypes(parameters.get(1)); if (defineTypes == null) { return true; } VariableInfo info = new VariableInfo(defineName, defineTypes, getCurrentScope(), functionInvocation.getStart(), PHPFlags.AccStatic); getCurrentScope().addVariable(info); // VariablePHPEntryValue entryValue = new VariablePHPEntryValue(0, // false, false, true, defineTypes, // FIXME functionInvocation.getStart()); // reporter.reportEntry(IPHPIndexConstants.CONST_CATEGORY, // defineName, entryValue, module); return true; } @Override public boolean visit(FunctionDeclaration functionDeclaration) { // methods are handled in other type. if (functionDeclaration.getParent() != null && functionDeclaration.getParent() instanceof MethodDeclaration) { return true; } Comment comment = findFunctionPHPDocComment(functionDeclaration.getStart()); // getting function name Identifier functionNameIdentifier = functionDeclaration.getFunctionName(); if (functionNameIdentifier == null) { return true; } // String functionName = functionNameIdentifier.getName(); // getting function parameters List<FormalParameter> parameters = functionDeclaration.formalParameters(); int[] parameterPositions = (parameters == null || parameters.size() == 0) ? null : new int[parameters .size()]; localParametersMap = null; if (parameters != null && parameters.size() > 0) { localParametersMap = new LinkedHashMap<String, Set<Object>>(parameters.size()); } ArrayList<Boolean> mandatoryParams = new ArrayList<Boolean>(); if (parameters != null) { int parCount = 0; for (FormalParameter parameter : parameters) { Identifier nameIdentifier = parameter.getParameterNameIdentifier(); if (nameIdentifier == null) { continue; } String parameterName = nameIdentifier.getName(); parameterPositions[parCount] = parameter.getStart(); String parameterType = null; Expression parameterTypeIdentifier = parameter.getParameterType(); if (parameterTypeIdentifier != null && (parameterTypeIdentifier.getType() == ASTNode.NAMESPACE_NAME || parameterTypeIdentifier .getType() == ASTNode.IDENTIFIER)) { parameterType = ((Identifier) parameterTypeIdentifier).getName(); } Set<Object> types = null; if (parameterType != null) { types = new HashSet<Object>(1); types.add(parameterType); } localParametersMap.put(parameterName, types); mandatoryParams.add(parameter.getDefaultValue() == null || parameter.isMandatory()); parCount++; } } // parsing PHP doc and adding types from it to the parameters String[] returnTypes = null; if (comment != null) { returnTypes = applyFunctionComment(comment, localParametersMap); } boolean[] mandatories = new boolean[mandatoryParams.size()]; for (int j = 0; j < mandatoryParams.size(); j++) { mandatories[j] = mandatoryParams.get(j); } FunctionPHPEntryValue entryValue = new FunctionPHPEntryValue(0, false, localParametersMap, parameterPositions, mandatories, functionDeclaration.getStart(), currentNamespace); if (returnTypes != null) { Set<Object> returnTypesSet = new HashSet<Object>(); for (String returnType : returnTypes) { returnTypesSet.add(returnType); } entryValue.setReturnTypes(returnTypesSet); } // String entryPath = ""; //$NON-NLS-1$ // if (currentClass != null) // { // entryPath = currentClass.getClassEntry().getEntryPath() + // IElementsIndex.DELIMITER; // } // entryPath += functionName; // FIXME // currentFunction = // reporter.reportEntry(IPHPIndexConstants.FUNCTION_CATEGORY, // entryPath, entryValue, module); return true; } /** * {@inheritDoc} */ @Override public boolean visit(MethodDeclaration methodDeclaration) { Comment comment = findFunctionPHPDocComment(methodDeclaration.getStart()); FunctionDeclaration functionDeclaration = methodDeclaration.getFunction(); if (functionDeclaration == null) { return true; } // getting function name Identifier functionNameIdentifier = functionDeclaration.getFunctionName(); if (functionNameIdentifier == null) { return true; } // String functionName = functionNameIdentifier.getName(); // getting function parameters List<FormalParameter> parameters = functionDeclaration.formalParameters(); int[] parameterPositions = (parameters == null || parameters.size() == 0) ? null : new int[parameters .size()]; Map<String, Set<Object>> parametersMap = null; if (parameters.size() > 0) { parametersMap = new LinkedHashMap<String, Set<Object>>(parameters.size()); } ArrayList<Boolean> mandatoryParams = new ArrayList<Boolean>(); if (parameters != null) { int parCount = 0; for (FormalParameter parameter : parameters) { Identifier nameIdentifier = parameter.getParameterNameIdentifier(); if (nameIdentifier == null) { continue; } String parameterName = nameIdentifier.getName(); parameterPositions[parCount] = parameter.getStart(); String parameterType = null; Expression parameterTypeIdentifier = parameter.getParameterType(); if (parameterTypeIdentifier != null && (parameterTypeIdentifier.getType() == ASTNode.NAMESPACE_NAME || parameterTypeIdentifier .getType() == ASTNode.IDENTIFIER)) { parameterType = ((Identifier) parameterTypeIdentifier).getName(); } Set<Object> types = null; if (parameterType != null) { types = new HashSet<Object>(1); types.add(parameterType); } parametersMap.put(parameterName, types); mandatoryParams.add(parameter.getDefaultValue() == null || parameter.isMandatory()); parCount++; } } // parsing PHP doc and adding types from it to the parameters String[] returnTypes = null; if (comment != null) { returnTypes = applyFunctionComment(comment, parametersMap); } int modifier = methodDeclaration.getModifier(); // if access modifier is unspecified, making it public. if (!PHPFlags.isPublic(modifier) && !PHPFlags.isProtected(modifier) && !PHPFlags.isPrivate(modifier)) { modifier |= PHPFlags.AccPublic; } boolean[] mandatories = new boolean[mandatoryParams.size()]; for (int j = 0; j < mandatoryParams.size(); j++) { mandatories[j] = mandatoryParams.get(j); } FunctionPHPEntryValue entryValue = new FunctionPHPEntryValue(modifier, true, parametersMap, parameterPositions, mandatories, methodDeclaration.getStart(), currentNamespace); if (returnTypes != null) { Set<Object> returnTypesSet = new HashSet<Object>(); for (String returnType : returnTypes) { returnTypesSet.add(returnType); } entryValue.setReturnTypes(returnTypesSet); } //String entryPath = ""; //$NON-NLS-1$ // if (currentClass != null) // { // entryPath = currentClass.getClassEntry().getEntryPath() + // IElementsIndex.DELIMITER; // } // entryPath += functionName; reporter.report(methodDeclaration, new MethodBinding((currentClass != null) ? currentClass.getName() : "", //$NON-NLS-1$ methodDeclaration.getFunction().getFunctionName().getName(), modifier, module)); // FIXME currentFunction = // reporter.reportEntry(IPHPIndexConstants.FUNCTION_CATEGORY, // entryPath, entryValue, module); // backuping old scopes // backupedScopes = scopes; // scopes = new Stack<Scope>(); return true; } /** * {@inheritDoc} */ @Override public boolean visit(Variable variable) { ASTNode parent = variable.getParent(); // handling variables assigned to some expression if (parent instanceof Assignment) { Assignment assignment = (Assignment) parent; if (variable.equals(assignment.getLeftHandSide())) { boolean staticDeclaration = parent.getParent() instanceof StaticStatement; return handleAssigment(variable, assignment, staticDeclaration); } } // handling simple variable definitions else if (parent instanceof ExpressionStatement) { String variableName = getVariableName(variable); if (variableName == null || !variable.isDollared()) { return true; } VariableInfo variableInfo = new VariableInfo(variableName, null, getCurrentScope(), variable.getStart()); getCurrentScope().addVariable(variableInfo); } // handling infix expressions, variable might take part in else if (parent instanceof InfixExpression) { String variableName = getVariableName(variable); if (variableName == null) { return true; } InfixExpression expr = (InfixExpression) parent; Set<Object> types = countInfixExpressionTypes(expr); VariableInfo variableInfo = new VariableInfo(variableName, types, getCurrentScope(), variable.getStart()); getCurrentScope().addVariable(variableInfo); } return true; } /** * {@inheritDoc} */ @Override public boolean visit(StaticFieldAccess fieldAccess) { visitStaticDispatch(fieldAccess); if (currentClass == null) { return true; } if (fieldAccess.getParent() == null || !(fieldAccess.getParent() instanceof Assignment)) { return true; } Expression className = fieldAccess.getClassName(); if (className == null || (className.getType() != ASTNode.IDENTIFIER && className.getType() != ASTNode.NAMESPACE_NAME) || !SELF.equals(((Identifier) className).getName())) { return true; } String fieldName = getVariableName(fieldAccess.getField()); if (fieldName == null) { return true; } Expression value = ((Assignment) fieldAccess.getParent()).getRightHandSide(); Set<Object> valueTypes = countExpressionTypes(value); if (valueTypes != null && valueTypes.size() > 0) { if (currentClass.hasField(fieldName)) { currentClass.addFieldTypes(fieldName, valueTypes); } else { reportField(PHPFlags.AccPublic | PHPFlags.AccStatic, fieldName, valueTypes, fieldAccess.getStart()); } } return true; } /** * {@inheritDoc} */ @Override public boolean visit(ConstantDeclaration constantDeclaration) { if (currentClass == null) { return true; } List<Identifier> variableNames = constantDeclaration.names(); List<Expression> values = constantDeclaration.initializers(); for (int i = 0; i < variableNames.size(); i++) { String variableName = variableNames.get(i).getName(); Expression value = values.get(i); Set<Object> valueTypes = countExpressionTypes(value); if (valueTypes != null && valueTypes.size() > 0) { if (currentClass.hasField(variableName)) { currentClass.addFieldTypes(variableName, valueTypes); } else { reportField(PHPFlags.AccPublic | PHPFlags.AccStatic | PHPFlags.AccFinal, variableName, valueTypes, constantDeclaration.getStart()); } } } return true; } @Override public boolean visit(MethodInvocation methodInvocation) { if (methodInvocation == null) { return true; } VariableBase leftSide = methodInvocation.getDispatcher(); if (!(leftSide instanceof Variable)) { return true; } VariableBase rightSide = methodInvocation.getMember(); if (rightSide != null) { ITypeBinding bnd = (ITypeBinding) resolveVariableTypeBinding(getVariableName((Variable) leftSide)); reporter.report(leftSide, bnd); return true; } return true; } /** * {@inheritDoc} */ @Override public boolean visit(FieldAccess fieldAccess) { if (fieldAccess == null) { return false; } VariableBase rightSide = fieldAccess.getMember(); if (!(rightSide instanceof Variable)) { return true; } VariableBase leftSide = fieldAccess.getDispatcher(); if (!(leftSide instanceof Variable)) { return true; } String fieldName = getVariableName((Variable) rightSide); if (fieldName != null) { ITypeBinding bnd = (ITypeBinding) resolveVariableTypeBinding(getVariableName((Variable) leftSide)); reporter.report(leftSide, bnd); return true; } if (currentClass == null) { return true; } if (fieldAccess.getParent() == null || !(fieldAccess.getParent() instanceof Assignment)) { return true; } if (fieldAccess != ((Assignment) fieldAccess.getParent()).getLeftHandSide()) { return true; } String variableName = getVariableName((Variable) leftSide); if (variableName == null || !THIS.equals(variableName)) { return true; } Expression value = ((Assignment) fieldAccess.getParent()).getRightHandSide(); Set<Object> valueTypes = countExpressionTypes(value); if (valueTypes != null && valueTypes.size() > 0) { if (currentClass.hasField(fieldName)) { currentClass.addFieldTypes(fieldName, valueTypes); } else { reportField(PHPFlags.AccPublic, fieldName, valueTypes, fieldAccess.getStart()); } } return true; } private ITypeBinding resolveVariableTypeBinding(String fieldName) { Scope currentScope = getCurrentScope(); VariableInfo variable = currentScope.getVariable(fieldName); if (variable != null) { Set<Object> variableTypes = variable.getVariableTypes(); Set<String> ts = null; Set<String> set = resolvedTypes.get(variableTypes); if (set != null) { ts = set; } else { ts = PHPTypeProcessor.processTypes(variableTypes, PHPGlobalIndexer.getInstance().getIndex()); resolvedTypes.put(variableTypes, ts); } if (ts.size() == 1) { String tn = ts.iterator().next(); if (!binding.containsKey(tn)) { ITypeBinding typeBinding = (ITypeBinding) resolveTypeBinding(tn); binding.put(tn, typeBinding); return typeBinding; } ITypeBinding typeBinding = this.binding.get(tn); return typeBinding; } } return null; } /** * {@inheritDoc} */ @Override public boolean visit(FieldsDeclaration fieldsDeclaration) { Variable[] variables = fieldsDeclaration.getVariableNames(); Expression[] initialValues = fieldsDeclaration.getInitialValues(); int modifier = fieldsDeclaration.getModifier(); // if access modifier is unspecified, making it public. if (!PHPFlags.isPublic(modifier) && !PHPFlags.isProtected(modifier) && !PHPFlags.isPrivate(modifier)) { modifier |= PHPFlags.AccPublic; } for (int i = 0; i < variables.length; i++) { Variable fieldVariable = variables[i]; Expression initialValue = initialValues[i]; String fieldName = getVariableName(fieldVariable); if (fieldName == null) { continue; } Set<Object> fieldTypes = null; if (initialValue != null) { fieldTypes = countExpressionTypes(initialValue); } reportField(modifier, fieldName, fieldTypes, fieldVariable.getStart()); } return true; } /** * {@inheritDoc} */ @Override public boolean visit(GlobalStatement globalStatement) { List<Variable> variables = globalStatement.variables(); Set<String> imports = new HashSet<String>(); for (Variable variable : variables) { String varName = getVariableName(variable); if (varName != null) { imports.add(varName); getGlobalScope().addVariable( new VariableInfo(varName, null, getGlobalScope(), globalStatement.getStart())); } } getCurrentScope().addGlobalImports(imports); return true; } /** * {@inheritDoc} */ @Override public boolean visit(ReturnStatement returnStatement) { // ignoring out of function returns if (currentFunction == null) { return true; } Expression expression = returnStatement.getExpression(); Set<Object> types = countExpressionTypes(expression); if (!(currentFunction.getValue() instanceof FunctionPHPEntryValue)) { return true; } FunctionPHPEntryValue value = (FunctionPHPEntryValue) currentFunction.getValue(); if (types != null) { Set<Object> currentTypes = value.getReturnTypes(); if (currentTypes == null || currentTypes.size() == 0) { currentTypes = new HashSet<Object>(); } currentTypes.addAll(types); value.setReturnTypes(currentTypes); } return true; } /** * {@inheritDoc} */ @Override public void endVisit(ClassDeclaration classDeclaration) { currentClass = null; } /** * {@inheritDoc} */ @Override public void endVisit(FunctionDeclaration functionDeclaration) { currentFunction = null; localParametersMap = null; } /** * {@inheritDoc} */ @Override public void endVisit(MethodDeclaration methodDeclaration) { // restoring backuped scopes // scopes = backupedScopes; currentFunction = null; localParametersMap = null; } /** * {@inheritDoc} */ @Override public void endVisit(Program program) { endVisitScopeNode(program); } /** * {@inheritDoc} */ @Override public boolean visit(Program program) { startVisitScopeNode(program); return true; } /** * {@inheritDoc} */ @Override public boolean visit(Block block) { startVisitScopeNode(block.getParent()); addBlockVariables(block); return true; } /** * {@inheritDoc} */ @Override public boolean visit(CatchClause catchClause) { return true; } /** * {@inheritDoc} */ @Override public boolean visit(DoStatement doStatement) { return true; } /** * {@inheritDoc} */ @Override public boolean visit(ForEachStatement forEachStatement) { return true; } /** * {@inheritDoc} */ @Override public boolean visit(ForStatement forStatement) { startVisitScopeNode(forStatement); return true; } /** * {@inheritDoc} */ @Override public boolean visit(IfStatement ifStatement) { return true; } /** * {@inheritDoc} */ @Override public boolean visit(SwitchStatement switchStatement) { return true; } /** * {@inheritDoc} */ @Override public boolean visit(TryStatement tryStatement) { return true; } /** * {@inheritDoc} */ @Override public boolean visit(WhileStatement whileStatement) { return true; } /** * {@inheritDoc} */ @Override public boolean visit(Include include) { Expression expr = include.getExpression(); if (expr != null && (expr instanceof ParenthesisExpression || expr instanceof Scalar)) { Expression subExpr = null; if (expr instanceof Scalar) { subExpr = expr; } else { subExpr = ((ParenthesisExpression) expr).getExpression(); } if (subExpr == null) { return true; } if (subExpr instanceof Scalar) { String includePath = ((Scalar) subExpr).getStringValue(); if (includePath == null || includePath.length() == 0) { return true; } int pathStartOffset = subExpr.getStart(); if (includePath.startsWith("\"") || includePath.startsWith("'")) //$NON-NLS-1$ //$NON-NLS-2$ { includePath = includePath.substring(1); pathStartOffset++; } if (includePath.endsWith("\"") || includePath.endsWith("'")) //$NON-NLS-1$ //$NON-NLS-2$ { includePath = includePath.substring(0, includePath.length() - 1); } // int pdtIncludeType = include.getIncludeType(); // int includeType = -1; // switch (pdtIncludeType) { // case Include.IT_INCLUDE: // includeType = IncludePHPEntryValue.INCLUDE_TYPE; // break; // case Include.IT_INCLUDE_ONCE: // includeType = IncludePHPEntryValue.INCLUDE_ONCE_TYPE; // break; // case Include.IT_REQUIRE: // includeType = IncludePHPEntryValue.REQUIRE_TYPE; // break; // case Include.IT_REQUIRE_ONCE: // includeType = IncludePHPEntryValue.REQUIRE_ONCE_TYPE; // break; // } if (module != null) { ISourceModule module2 = ((SourceModule) module).getModule(includePath); if (module2 != null) { reporter.report(include, new ModuleBinding(module2)); } } //reporter.reportEntry(IPHPIndexConstants.IMPORT_CATEGORY, "", value, module); //$NON-NLS-1$ } } return true; } // /// /** * {@inheritDoc} */ @Override public void endVisit(Block block) { endVisitScopeNode(block); } /** * {@inheritDoc} */ @Override public void endVisit(CatchClause catchClause) { } /** * {@inheritDoc} */ @Override public void endVisit(DoStatement doStatement) { } /** * {@inheritDoc} */ @Override public void endVisit(ForEachStatement forEachStatement) { } /** * {@inheritDoc} */ @Override public void endVisit(ForStatement forStatement) { endVisitScopeNode(forStatement); } /** * {@inheritDoc} */ @Override public void endVisit(IfStatement ifStatement) { } /** * {@inheritDoc} */ @Override public void endVisit(SwitchStatement switchStatement) { } /** * {@inheritDoc} */ @Override public void endVisit(TryStatement tryStatement) { } /** * {@inheritDoc} */ @Override public void endVisit(WhileStatement whileStatement) { } /** * Handles variable assignment. * * @param variable * - variable. * @param assigment * - assignment node. * @return */ private boolean handleAssigment(Variable variable, Assignment assigment, boolean staticDeclaration) { Expression value = assigment.getRightHandSide(); String variableName = getVariableName(variable); if (variableName == null) { return true; } Set<Object> rightSideTypes = countExpressionTypes(value); VariableInfo variableInfo = new VariableInfo(variableName, rightSideTypes, getCurrentScope(), variable.getStart(), staticDeclaration ? PHPFlags.AccStatic : 0); getCurrentScope().addVariable(variableInfo); return true; } /** * Gets variable entry path. * * @param variable * - variable. * @return entry path. */ String getVariableEntryPath(Variable variable) { String entryPath = ""; //$NON-NLS-1$ if (currentFunction != null) { entryPath = currentFunction.getEntryPath() + IElementsIndex.DELIMITER; } // else if (currentClass != null) // { // entryPath = currentClass.getClassEntry().getEntryPath() + // IElementsIndex.DELIMITER; // } String varName = getVariableName(variable); if (varName == null) { return null; } entryPath += varName; return entryPath; } /** * Gets variable name. * * @param variable * - variable. * @return variable name. */ private String getVariableName(Expression variable) { if (variable != null) { if (variable.getType() == ASTNode.VARIABLE) { // this will get the name from the Variable as an identifier variable = ((Variable) variable).getName(); } if (variable.getType() == ASTNode.NAMESPACE_NAME || variable.getType() == ASTNode.IDENTIFIER) { return ((Identifier) variable).getName(); } } return null; } /** * Handles the start of visiting the node that provides variables scope. * * @param node * - node. */ private void startVisitScopeNode(ASTNode node) { Scope parent = null; if (!scopes.isEmpty()) { parent = scopes.peek(); } IElementEntry currentEntry = null; if (node instanceof FunctionDeclaration) { currentEntry = currentFunction; } // else if (node instanceof ClassDeclaration) // { // currentEntry = currentClass.getClassEntry(); // } Scope scope = new Scope(node, parent, currentEntry); if (localParametersMap != null) { for (String s : localParametersMap.keySet()) { scope.addVariable(new VariableInfo(s, localParametersMap.get(s), scope, 0)); } } scopes.push(scope); } /** * Handles the end of visiting the node that provides variables scope. * * @param node * - node. */ private void endVisitScopeNode(ASTNode node) { if (!scopes.isEmpty()) { // if local mode is on and we did not report the local stack // yet, // backuping the stack. if (!globalMode && !localStackReported) { if (node.getEnd() > currentOffset) { reportStack(scopes); localStackReported = true; } } Scope scope = scopes.pop(); reportGlobalScopeVariables(scope); } } /** * Gets current scope. Should never be null. * * @return current scope. */ private Scope getCurrentScope() { return scopes.peek(); } /** * Gets current scope. Should never be null. * * @return current scope. */ private Scope getGlobalScope() { return scopes.get(0); } /** * Reports global scope variables. * * @param scope * - scope, which variables to report. */ private void reportGlobalScopeVariables(Scope scope) { // if (globalMode) // { // if (scope.getRoot() instanceof Program) // { // for (VariableInfo info : scope.getVariables()) // { // VariablePHPEntryValue entryValue = new VariablePHPEntryValue(0, false, false, false, // info.getVariableTypes(), info.getNodeStart()); // String entryPath = info.getName(); // FIXME // reporter.reportEntry(IPHPIndexConstants.VAR_CATEGORY, entryPath, entryValue, module); // } // } // } } /** * Count variables types by name and scope. * * @param variableName * - variable name. * @param currentScope * - current scope. * @return */ private Set<Object> countVariableTypes(String variableName, Scope currentScope) { Set<Object> result = new HashSet<Object>(); // checking for variable with this name. VariableInfo variable = currentScope.getVariable(variableName); if (variable != null) { result.addAll(variable.getVariableTypes()); } // checking for function or method parameter with this name IElementEntry entry = findFunctionOrMethodParent(currentScope); if (entry != null) { FunctionPHPEntryValue entryValue = (FunctionPHPEntryValue) entry.getValue(); Map<String, Set<Object>> parameters = entryValue.getParameters(); if (parameters != null) { Set<Object> types = parameters.get(variableName); if (types != null) { result.addAll(types); } } } return result; } /** * Finds function or method parent scope and returns its element entry. * * @param scope * - current scope. * @return entry or null if not found. */ private IElementEntry findFunctionOrMethodParent(Scope scope) { Scope currentScope = scope; while (currentScope != null) { IElementEntry entry = currentScope.getEntry(); if (entry != null) { if (entry.getValue() instanceof FunctionPHPEntryValue) { return entry; } } currentScope = currentScope.getParent(); } return null; } /** * Counts infix expression types. * * @param expr * - expression. * @return expression type or null if undefined */ private Set<Object> countInfixExpressionTypes(InfixExpression expr) { int operator = expr.getOperator(); String type = null; switch (operator) { case InfixExpression.OP_CONCAT: type = IPHPTypeConstants.STRING_TYPE; break; case InfixExpression.OP_IS_IDENTICAL: case InfixExpression.OP_IS_NOT_IDENTICAL: case InfixExpression.OP_IS_EQUAL: case InfixExpression.OP_IS_NOT_EQUAL: case InfixExpression.OP_RGREATER: case InfixExpression.OP_IS_SMALLER_OR_EQUAL: case InfixExpression.OP_LGREATER: case InfixExpression.OP_IS_GREATER_OR_EQUAL: case InfixExpression.OP_BOOL_OR: type = IPHPTypeConstants.BOOLEAN_TYPE; break; case InfixExpression.OP_STRING_OR: case InfixExpression.OP_STRING_AND: case InfixExpression.OP_STRING_XOR: type = IPHPTypeConstants.STRING_TYPE; break; case InfixExpression.OP_PLUS: case InfixExpression.OP_MINUS: case InfixExpression.OP_MUL: case InfixExpression.OP_DIV: case InfixExpression.OP_MOD: type = IPHPTypeConstants.REAL_TYPE; break; case InfixExpression.OP_SL: case InfixExpression.OP_SR: type = IPHPTypeConstants.INTEGER_TYPE; break; } Set<Object> result = new HashSet<Object>(1); result.add(type); return result; } /** * Converts static access to the plain path. In example ClassName::$b would be [ClassName,b] path. * * @param dispatch * - dispatch. * @return path or null indicating parsing failure */ private CallPath getPathByStaticDispatch(StaticDispatch dispatch) { CallPath result = new CallPath(); // counting first path entry Expression classNameIdentifier = dispatch.getClassName(); // FIXME: Shalom - We might need a better handle here for namespaces String className = null; if (classNameIdentifier == null || (classNameIdentifier.getType() != ASTNode.IDENTIFIER && classNameIdentifier.getType() != ASTNode.NAMESPACE_NAME)) { IdeLog.logError(PHPEditorPlugin.getDefault(), "Expected an identifier or namespace-name", new Exception("Missing identifier")); //$NON-NLS-1$ //$NON-NLS-2$ return null; } className = ((Identifier) classNameIdentifier).getName(); result.setClassEntry(className); // counting second entry ASTNode member = dispatch.getMember(); // getting member name and type String memberName = null; boolean field = true; if (member instanceof Variable) { Variable currentField = (Variable) member; memberName = getVariableName(currentField); if (memberName == null) { return null; } field = true; } else if (member instanceof FunctionInvocation) { FunctionInvocation funcInvocation = (FunctionInvocation) member; memberName = getFunctionNameByInvocation(funcInvocation); if (memberName == null) { return null; } field = false; } else { return null; } // inserting member to the path if (field) { result.addVariableEntry(memberName); } else { result.addMethodEntry(memberName); } return result; } /** * Converts field access to the plain path. In example $a->$b->$method() would be [a,b,method] path. * * @param dispatch * - dispatch. * @return path or null indicating parsing failure */ private CallPath getPathByDispatch(Dispatch dispatch) { CallPath result = new CallPath(); Dispatch currentDispatch = dispatch; while (currentDispatch != null) { VariableBase currentDispatcher = currentDispatch.getDispatcher(); // getting member name and type String memberName = null; boolean field = true; VariableBase member = currentDispatch.getMember(); if (member instanceof Variable) { Variable currentField = (Variable) member; memberName = getVariableName(currentField); if (memberName == null) { return null; } field = true; } else if (member instanceof FunctionInvocation) { FunctionInvocation funcInvocation = (FunctionInvocation) member; memberName = getFunctionNameByInvocation(funcInvocation); if (memberName == null) { return null; } field = false; } else { return null; } // inserting member to the path if (field) { result.insertVariableEntry(memberName); } else { result.insertMethodEntry(memberName); } // if left side is still the dispatch, continue to parse it if (currentDispatcher instanceof Dispatch) { currentDispatch = (Dispatch) currentDispatcher; } else if (currentDispatcher instanceof StaticDispatch) { // getting the path of the static dispatch CallPath staticDispatchCallPath = getPathByStaticDispatch((StaticDispatch) currentDispatcher); if (staticDispatchCallPath == null) { return null; } // adding current path to the static dispatch type staticDispatchCallPath.addPath(result); return staticDispatchCallPath; } else if (currentDispatcher instanceof Variable) { // if left side is variable, inserting it and stopping // parsing String dispatecherName = getVariableName((Variable) currentDispatcher); if (dispatecherName == null) { return null; } result.insertVariableEntry(dispatecherName); break; } else if (currentDispatcher instanceof FunctionInvocation) { // if left side is method, inserting it and stopping parsing String dispatecherName = getFunctionNameByInvocation((FunctionInvocation) currentDispatcher); if (dispatecherName == null) { return null; } result.insertMethodEntry(dispatecherName); break; } else { // unrecognized construction return null; } } return result; } /** * Counts the all possible types of expression. * * @param expression * - expression to count. * @return types set or null if expression types connot be counted. */ private Set<Object> countExpressionTypes(Expression expression) { Set<Object> result = null; // handling scalar values if (expression instanceof Scalar) { Scalar scalar = (Scalar) expression; result = countScalarTypes(scalar); } // handling variable values else if (expression instanceof Variable) { Variable rightSideVar = (Variable) expression; String rightSideVarName = getVariableName(rightSideVar); if (rightSideVarName == null) { return null; } result = countVariableTypes(rightSideVarName, getCurrentScope()); } // handling instance creation else if (expression instanceof ClassInstanceCreation) { ClassInstanceCreation creation = (ClassInstanceCreation) expression; result = countInstanceCreationTypes(creation); } // handling call paths else if (expression instanceof FieldAccess || expression instanceof MethodInvocation) { Dispatch dispatch = (Dispatch) expression; result = countDispatchTypes(dispatch); } else if (expression instanceof StaticDispatch) { StaticDispatch staticDispatch = (StaticDispatch) expression; result = countStaticDispatchTypes(staticDispatch); } // handling function invocation else if (expression instanceof FunctionInvocation) { FunctionInvocation invocation = (FunctionInvocation) expression; result = countFunctionInvocationTypes(invocation); } // handling infix expressions else if (expression instanceof InfixExpression) { InfixExpression infix = (InfixExpression) expression; result = countInfixExpressionTypes(infix); } return result; } /** * Counts function invocation types. * * @param invocation * - invocation. * @return set of types or null if cannot count. */ private Set<Object> countFunctionInvocationTypes(FunctionInvocation invocation) { String functionName = getFunctionNameByInvocation(invocation); if (functionName == null) { return null; } FunctionPathReference reference = new FunctionPathReference(functionName, null); Set<Object> result = new HashSet<Object>(1); result.add(reference); return result; } /** * Counts scalar expression types. * * @param scalar * - scalar. * @return set of types or null if cannot count. */ private Set<Object> countScalarTypes(Scalar scalar) { Set<Object> result; int intType = scalar.getScalarType(); String type = typeToString(intType); result = new HashSet<Object>(1); result.add(type); return result; } /** * Counts class instance creation expression types. * * @param creation * - instance creation. * @return set of types or null if cannot count. */ private Set<Object> countInstanceCreationTypes(ClassInstanceCreation creation) { ClassName className = creation.getClassName(); if (className == null) { return null; } Expression classNameExpr = className.getName(); String clName = null; if (classNameExpr != null && (classNameExpr.getType() == ASTNode.NAMESPACE_NAME || classNameExpr.getType() == ASTNode.IDENTIFIER)) { clName = ((Identifier) classNameExpr).getName(); } if (clName == null) { return null; } Set<Object> result = new HashSet<Object>(1); if (SELF.equals(clName)) { if (currentClass != null) { // result.add(currentClass.getClassEntry().getEntryPath()); return result; } return null; } result.add(clName); return result; } /** * Counts dispatch expression types. * * @param dispatch * - dispatch. * @return types set or null if cannot count. */ private Set<Object> countStaticDispatchTypes(StaticDispatch dispatch) { Set<Object> result = null; CallPath path = getPathByStaticDispatch(dispatch); if (path == null || path.getSize() < 2) { return null; } CallPath remainingPath = path.subPath(1); List<CallPath.Entry> pathEntries = path.getEntries(); CallPath.Entry dispatchEntry = pathEntries.get(0); // handling "self::" if (dispatchEntry instanceof CallPath.ClassEntry && SELF.equals(dispatchEntry.getName())) { if (currentClass != null) { Set<Object> dispatcherTypes = new HashSet<Object>(1); // dispatcherTypes.add(ElementsIndexingUtils. // getFirstNameInPath(currentClass.getClassEntry() // .getEntryPath())); StaticPathReference reference = new StaticPathReference(dispatcherTypes, remainingPath); result = new HashSet<Object>(1); result.add(reference); } else { return null; } } // handling "ClassName::" else if (dispatchEntry instanceof CallPath.ClassEntry) { Set<Object> dispatcherTypes = new HashSet<Object>(1); dispatcherTypes.add(dispatchEntry.getName()); StaticPathReference reference = new StaticPathReference(dispatcherTypes, remainingPath); result = new HashSet<Object>(1); result.add(reference); } else { return null; } return result; } /** * Counts dispatch expression types. * * @param dispatch * - dispatch. * @return types set or null if cannot count. */ private Set<Object> countDispatchTypes(Dispatch dispatch) { Set<Object> result = null; CallPath path = getPathByDispatch(dispatch); if (path == null || path.getSize() < 2) { return null; } CallPath remainingPath = path.subPath(1); List<CallPath.Entry> pathEntries = path.getEntries(); CallPath.Entry dispatchEntry = pathEntries.get(0); // handling "this->" if (dispatchEntry instanceof CallPath.VariableEntry && THIS.equals(dispatchEntry.getName())) { if (currentClass != null) { Set<Object> dispatcherTypes = new HashSet<Object>(1); // dispatcherTypes.add(ElementsIndexingUtils. // getFirstNameInPath(currentClass.getClassEntry() // .getEntryPath())); VariablePathReference reference = new VariablePathReference(dispatcherTypes, remainingPath); result = new HashSet<Object>(1); result.add(reference); } else { return null; } } // handling "self::" else if (dispatchEntry instanceof CallPath.ClassEntry && SELF.equals(dispatchEntry.getName())) { if (currentClass != null) { Set<Object> dispatcherTypes = new HashSet<Object>(1); // dispatcherTypes.add(ElementsIndexingUtils. // getFirstNameInPath(currentClass.getClassEntry() // .getEntryPath())); StaticPathReference reference = new StaticPathReference(dispatcherTypes, remainingPath); result = new HashSet<Object>(1); result.add(reference); } else { return null; } } // handling "ClassName::" else if (dispatchEntry instanceof CallPath.ClassEntry) { Set<Object> dispatcherTypes = new HashSet<Object>(1); dispatcherTypes.add(dispatchEntry.getName()); StaticPathReference reference = new StaticPathReference(dispatcherTypes, remainingPath); result = new HashSet<Object>(1); result.add(reference); } // handling "$var->" else if (dispatchEntry instanceof CallPath.VariableEntry) { Set<Object> dispatcherTypes = countVariableTypes(dispatchEntry.getName(), getCurrentScope()); if (dispatcherTypes == null) { return null; } VariablePathReference reference = new VariablePathReference(dispatcherTypes, remainingPath); result = new HashSet<Object>(1); result.add(reference); } // handling "method()->" else if (dispatchEntry instanceof CallPath.MethodEntry) { String methodEntryPath = ((CallPath.MethodEntry) dispatchEntry).getName(); FunctionPathReference reference = new FunctionPathReference(methodEntryPath, remainingPath); result = new HashSet<Object>(1); result.add(reference); } else { return null; } return result; } /** * Gets function name by function invocation. * * @param invocation * - function invocation. * @return function name or null if not recognized. */ private String getFunctionNameByInvocation(FunctionInvocation invocation) { FunctionName funcName = invocation.getFunctionName(); if (funcName == null) { return null; } Expression funcNameExpression = funcName.getName(); return getVariableName(funcNameExpression); } /** * Reports the stack. Needed for local mode. * * @param stack * - stack to report. */ private void reportStack(List<Scope> stack) { if (stack.isEmpty()) { return; } // int currentScopeDepth = 0; // collecting global imports for (int i = 0; i < stack.size(); i++) { Scope currentScope = stack.get(i); Set<String> currentGlobalImports = currentScope.getGlobalImports(); if (currentGlobalImports != null) { overallGlobalImports.addAll(currentGlobalImports); } } // searching for a first scope that start earlier then current // offset Scope currentScope = null; for (int i = stack.size() - 1; i >= 0; i--) { // currentScopeDepth = i; currentScope = stack.get(i); ASTNode root = currentScope.getRoot(); if (root == null) { continue; } if (root.getStart() < currentOffset) { break; } } // nothing to report if (currentScope == null) { return; } isReportedStackGlobal = currentScope.isGlobalScope(); // String scopePath = getScopePath(stack, i); Set<VariableInfo> variables = currentScope.getVariables(); // boolean localVariables = !currentScope.isGlobalScope(); // reporting variables for (VariableInfo varInfo : variables) { if (varInfo.getNodeStart() > currentOffset) { continue; } // String entryPath = varInfo.getName(); // VariablePHPEntryValue value = new VariablePHPEntryValue(varInfo // .getModifier(), false, localVariables, false, varInfo // .getVariableTypes(), varInfo.getNodeStart()); // FIXME reporter.reportEntry(IPHPIndexConstants.VAR_CATEGORY, // entryPath, value, module); } // reporting function/method parameters if needed, going up in the // stack searching for the function // for (int i = stack.size() - 1; i >= 0; i--) // { // Scope scope = stack.get(i); // if (scope.getEntry() != null) // { // IElementEntry entry = scope.getEntry(); // if (entry.getCategory() == IPHPIndexConstants.FUNCTION_CATEGORY // && entry.getValue() instanceof FunctionPHPEntryValue) { // FunctionPHPEntryValue val = (FunctionPHPEntryValue) entry // .getValue(); // Map<String, Set<Object>> parameters = val // .getParameters(); // if (parameters != null && !parameters.isEmpty()) { // for (Map.Entry<String, Set<Object>> parEntry : parameters // .entrySet()) { // //String entryPath = parEntry.getKey(); // // VariablePHPEntryValue value = new VariablePHPEntryValue( // // 0, true, false, false, parEntry // // .getValue(), val // // .getStartOffset()); // // FIXME // // reporter.reportEntry(IPHPIndexConstants. // // VAR_CATEGORY, entryPath, value, module); // } // } // } // } // } } /** * Gets scope entries path (including the ending delimiter). * * @param stack * - scopes stack. * @param pos * - position of the scope in stack. * @param clazz * - class active for the scope. * @return scope entries path */ String getScopePath(List<Scope> stack, int pos) { StringBuffer result = new StringBuffer(); for (int i = pos; i >= 0; i--) { Scope currentScope = stack.get(i); ASTNode root = currentScope.getRoot(); if (root instanceof FunctionDeclaration) { Identifier funcNameIdentifier = ((FunctionDeclaration) root).getFunctionName(); if (funcNameIdentifier == null) { continue; } String functionName = funcNameIdentifier.getName(); if (functionName == null) { continue; } result.insert(0, functionName + IElementsIndex.DELIMITER); } else if (root instanceof MethodDeclaration) { Identifier funcNameIdentifier = ((MethodDeclaration) root).getFunction().getFunctionName(); if (funcNameIdentifier == null) { continue; } String functionName = funcNameIdentifier.getName(); if (functionName == null) { continue; } result.insert(0, functionName + IElementsIndex.DELIMITER); } else if (root instanceof ClassDeclaration) { Identifier classNameIdentifier = ((ClassDeclaration) root).getName(); if (classNameIdentifier == null) { continue; } String className = classNameIdentifier.getName(); if (className == null) { continue; } result.insert(0, className + IElementsIndex.DELIMITER); } } return result.toString(); } /** * Parses comment and adds parameter types if available. * * @param comment * @param parametersMap * @return possible return types. */ private String[] applyFunctionComment(Comment comment, Map<String, Set<Object>> parametersMap) { try { FunctionDocumentation doc = PHPDocUtils.getFunctionDocumentation((IPHPDoc) comment); if (doc == null) { return null; } TypedDescription[] params = doc.getParams(); if (params != null && params.length != 0) { for (TypedDescription param : params) { String paramName = param.getName(); if (paramName == null || paramName.length() == 0) { continue; } if (paramName.startsWith("$")) //$NON-NLS-1$ { paramName = paramName.substring(1); } String[] types = param.getTypes(); Set<Object> toSetParams = (parametersMap != null) ? parametersMap.get(paramName) : Collections .emptySet(); if (toSetParams == null) { toSetParams = new HashSet<Object>(); parametersMap.put(paramName, toSetParams); } if (types != null) { for (String type : types) { toSetParams.add(type); } } } } TypedDescription returnDescr = doc.getReturn(); if (returnDescr == null) { return null; } return returnDescr.getTypes(); } catch (Throwable th) { IdeLog.logWarning(PHPEditorPlugin.getDefault(), "PHP Type-Binding builder - Error while applying a comment to a parameter type (applyComment)", //$NON-NLS-1$ th, PHPEditorPlugin.DEBUG_SCOPE); } return null; } /** * Reports field and adds it to the current class fields list. * * @param modifier * - modifier. * @param fieldName * - field name. * @param fieldTypes * - field types. * @param pos * - position. * @return reported entry or null. */ private IElementEntry reportField(int modifier, String fieldName, Set<Object> fieldTypes, int pos) { // VariablePHPEntryValue entryValue = new VariablePHPEntryValue( // modifier, false, false, true, fieldTypes, pos); // // if (currentClass != null) { // // String entryPath = // // currentClass.getClassEntry().getEntryPath() + // // IElementsIndex.DELIMITER; // // // entryPath += fieldName; // // // IElementEntry result = // // reporter.reportEntry(IPHPIndexConstants.VAR_CATEGORY, // // entryPath, entryValue, // // FIXME module); // // currentClass.setField(fieldName, result); // // return result; // } return null; } /** * Adds block variables for try, for and other non-function expressions that can define local block variables. * * @param block * - block. */ private void addBlockVariables(Block block) { ASTNode blockParent = block.getParent(); if (blockParent instanceof CatchClause) { addCatchClauseVariables((CatchClause) blockParent); } else if (blockParent instanceof ForStatement) { addForVariables((ForStatement) blockParent); } else if (blockParent instanceof ForEachStatement) { addForEachVariables((ForEachStatement) blockParent); } } /** * Adds "for each" local variables. * * @param foreachStatement * - "for each" statement. */ private void addForEachVariables(ForEachStatement foreachStatement) { Expression key = foreachStatement.getKey(); Expression value = foreachStatement.getValue(); if (key != null && key instanceof Variable) { String varName = getVariableName((Variable) key); if (varName != null) { VariableInfo info = new VariableInfo(varName, null, getCurrentScope(), key.getStart()); getCurrentScope().addVariable(info); } } if (value != null && value instanceof Variable) { String varName = getVariableName((Variable) value); if (varName != null) { VariableInfo info = new VariableInfo(varName, null, getCurrentScope(), value.getStart()); getCurrentScope().addVariable(info); } } } /** * Adds "for" local variables. * * @param forStatement * - "for" statement. */ private void addForVariables(ForStatement forStatement) { List<Expression> initializations = forStatement.initializers(); if (initializations == null || initializations.size() == 0) { return; } for (Expression initialization : initializations) { if (initialization instanceof Assignment) { Assignment assigment = (Assignment) initialization; VariableBase var = assigment.getLeftHandSide(); if (var instanceof Variable) { String varName = getVariableName((Variable) var); if (varName == null) { continue; } Set<Object> types = countExpressionTypes(assigment.getRightHandSide()); if (types != null && types.size() != 0) { VariableInfo info = new VariableInfo(varName, types, getCurrentScope(), var.getStart()); getCurrentScope().addVariable(info); } } } } } /** * Adds catch clause local variables. * * @param clause * - catch clause. */ private void addCatchClauseVariables(CatchClause clause) { Variable var = clause.getVariable(); String varName = getVariableName(var); if (varName != null) { VariableInfo info = new VariableInfo(varName, "Exception", getCurrentScope(), var.getStart()); //$NON-NLS-1$ getCurrentScope().addVariable(info); } } } /** * types cache; */ private Map<Set<Object>, Set<String>> resolvedTypes = new HashMap<Set<Object>, Set<String>>(); /** * Mode. */ private boolean globalMode = true; /** * Current offset. */ private int currentOffset = 0; /** * Comments. */ private List<Comment> comments; /** * Source */ private String source; /** * Overall global imports in the reported stack. */ private Set<String> overallGlobalImports = new HashSet<String>(); /** * Whether reported stack is global one. */ private boolean isReportedStackGlobal = true; /** * PDTPHPModuleIndexer constructor. Creates indexer in global mode. */ public TypeBindingBuilder() { } /** * PDTPHPModuleIndexer constructor. Indexer might be created in one of two modes: global mode and local mode. In * global mode indexer reports includes, classes, functions, methods, fields and only global variables. In local * mode indexer reports includes, classes, functions, methods, fields, global variables and local variables that are * visible from the offset specified. * * @param globalMode * - true if global mode is on, false if local mode is on. * @param currentOffset * - offset used for local mode counting. */ public TypeBindingBuilder(boolean globalMode, int currentOffset) { this.globalMode = globalMode; this.currentOffset = currentOffset; } /** * {@inheritDoc} */ public synchronized void indexModule(Program program, String source, IBindingReporter reporter) { try { // grab the comments from the AST this.comments = program.comments(); this.source = source; // indexing PHPASTVisitor visitor = new PHPASTVisitor(reporter, program); program.accept(visitor); } catch (Throwable th) { IdeLog.logError(PHPEditorPlugin.getDefault(), "Unexpected exception while indexing module " + program.toString(), th); //$NON-NLS-1$ } } /** * Gets global imports got while parsing in a local mode in the scopes stack. * * @return global imports. */ public Set<String> getGlobalImports() { return overallGlobalImports; } /** * Gets whether reported scope is global. * * @return whether reported scope is global. */ public boolean isReportedScopeGlobal() { return isReportedStackGlobal; } /** * Gets whether reported scope is under class or function/method. * * @return true if whether reported scope is under class or function/method, false otherwise. */ public boolean isReportedScopeUnderClassOrFunction() { return isReportedStackGlobal; } /** * Converts encoded type to its string representation. * * @param intType * - integer-encoded type. * @return string representation or null if not found. */ private static String typeToString(int intType) { switch (intType) { case Scalar.TYPE_INT: return IPHPTypeConstants.INTEGER_TYPE; case Scalar.TYPE_REAL: return IPHPTypeConstants.REAL_TYPE; case Scalar.TYPE_STRING: return IPHPTypeConstants.STRING_TYPE; case Scalar.TYPE_SYSTEM: return IPHPTypeConstants.SYSTEM_TYPE; case Scalar.TYPE_UNKNOWN: return IPHPTypeConstants.UNKNOWN_TYPE; } return null; } /** * Finds function or method PHPDoc comment above. * * @param offset * - offset to start search from. * @return comment contents or null. */ private Comment findFunctionPHPDocComment(int offset) { return findComment(offset, Comment.TYPE_PHPDOC); } /** * @param offset * @param type * - The comment type * @return */ private Comment findComment(int offset, int type) { if (comments == null || comments.isEmpty()) { return null; } Comment nearestComment = null; for (Comment comment : comments) { if (comment.getStart() > offset) { break; } nearestComment = comment; } if (nearestComment == null) { return null; } if (nearestComment.getCommentType() != type) { return null; } if (source != null) { // checking if we have anything but whitespaces between comment end and // offset for (int i = nearestComment.getEnd() + 1; i < offset - 1; i++) { char ch = source.charAt(i); if (!Character.isWhitespace(ch)) { return null; } } // return _contents.substring(nearestComment.getStart(), nearestComment.getEnd()); return nearestComment; } else { return null; } } }