package org.pdtextensions.semanticanalysis.validation.validator; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.dltk.ast.ASTListNode; import org.eclipse.dltk.ast.references.TypeReference; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.index2.search.ISearchEngine.MatchRule; import org.eclipse.dltk.core.search.IDLTKSearchScope; import org.eclipse.dltk.core.search.SearchEngine; import org.eclipse.php.core.compiler.ast.nodes.ClassDeclaration; import org.eclipse.php.core.compiler.ast.nodes.ClassInstanceCreation; import org.eclipse.php.core.compiler.ast.nodes.FormalParameter; import org.eclipse.php.core.compiler.ast.nodes.FullyQualifiedReference; import org.eclipse.php.core.compiler.ast.nodes.InterfaceDeclaration; import org.eclipse.php.core.compiler.ast.nodes.NamespaceDeclaration; import org.eclipse.php.core.compiler.ast.nodes.NamespaceReference; import org.eclipse.php.core.compiler.ast.nodes.PHPMethodDeclaration; import org.eclipse.php.core.compiler.ast.nodes.StaticConstantAccess; import org.eclipse.php.core.compiler.ast.nodes.StaticFieldAccess; import org.eclipse.php.core.compiler.ast.nodes.StaticMethodInvocation; import org.eclipse.php.core.compiler.ast.nodes.UsePart; import org.eclipse.php.core.compiler.ast.nodes.UseStatement; import org.eclipse.php.internal.core.model.PHPModelAccess; import org.eclipse.php.internal.core.typeinference.PHPModelUtils; import org.eclipse.php.internal.core.typeinference.PHPSimpleTypes; import org.pdtextensions.core.util.PDTModelUtils; import org.pdtextensions.internal.semanticanalysis.validation.PEXProblemIdentifier; import org.pdtextensions.semanticanalysis.PEXAnalysisPlugin; import org.pdtextensions.semanticanalysis.validation.AbstractValidator; import org.pdtextensions.semanticanalysis.validation.IValidatorContext; /** * Checks a PHP sourcemodule for unresolved type references. * * @author Robert Gruendler <r.gruendler@gmail.com> * @author Dawid zulus Pakula <zulus@w3des.net> */ @SuppressWarnings("restriction") public class UsageValidator extends AbstractValidator { final private static String BACK_SLASH = "\\"; //$NON-NLS-1$ final private static String MESSAGE_CANNOT_RESOLVE_TYPE = "The type %s cannot be resolved"; // final private static String MESSAGE_CANNOT_RESOLVE_CALL = // "The call %s cannot be resolved."; final private static String MESSAGE_CANNOT_RESOLVE_USE = "The type %s cannot be resolved or namespace is empty"; final private static String MESSAGE_DUPLATE_USE = "%s is a duplicate"; final public static String ID = "org.pdtextensions.semanticanalysis.validator.usageValidator"; //$NON-NLS-1$ private class PartInfo { public UsePart part; //public UseStatement container; public String fullName; public PartInfo(UseStatement container, UsePart part) { this.part = part; //this.container = container; StringBuilder sb = new StringBuilder(part.getNamespace().getFullyQualifiedName()); if (container.getNamespace() != null) { sb.insert(0, BACK_SLASH); sb.insert(0, container.getNamespace().getFullyQualifiedName()); } if (sb.charAt(0) != '\\') { sb.insert(0, BACK_SLASH); } fullName = sb.toString(); } public String getUseName() { return part.getAlias() != null ? part.getAlias().getName() : part.getNamespace().getName(); } } Map<PartInfo, Boolean> parts; Map<String, Boolean> found; Map<String, Boolean> namespaces; String projectName; IType namespace = null; public UsageValidator() { super(); parts = new HashMap<PartInfo, Boolean>(); found = new HashMap<String, Boolean>(); namespaces = new HashMap<String, Boolean>(); } @Override public void validate(IValidatorContext context) throws Exception { namespace = PHPModelUtils.getCurrentNamespace( context.getSourceModule(), 0); super.validate(context); parts.clear(); if (projectName == null || projectName.equals(context.getProject().getElementName())) { namespaces.clear(); found.clear(); projectName = context.getProject().getElementName(); } } @Override public boolean visit(NamespaceDeclaration s) throws Exception { parts = new HashMap<PartInfo, Boolean>(); namespace = PHPModelUtils.getCurrentNamespace( context.getSourceModule(), s.getNameEnd() + 2); return super.visit(s); } @Override public boolean endvisit(NamespaceDeclaration s) throws Exception { boolean res = super.endvisit(s); namespace = null; return res; } /** * Adds the usestatement to the internal list of statements and checks if it * can be resolved in the project. * * @param s */ public boolean visit(UseStatement s) { for (UsePart part : s.getParts()) { if (part.getNamespace() == null) { continue; } PartInfo partInfo = new PartInfo(s, part); for (PartInfo existsPart : parts.keySet()) { if (existsPart.fullName.equals(partInfo.fullName)) { context.registerProblem(PEXProblemIdentifier.DUPLICATE, String.format( MESSAGE_DUPLATE_USE, part.getNamespace() .getFullyQualifiedName()), part .getNamespace().sourceStart(), part .getNamespace().sourceEnd()); continue; } } boolean isRes = isResolved(partInfo.fullName); parts.put(partInfo, isRes); // add problem if namespace is empty if (!isRes) { IDLTKSearchScope searchScope = SearchEngine .createSearchScope(context.getProject()); IType[] types = PHPModelAccess.getDefault().findTypes( partInfo.fullName.substring(1) + BACK_SLASH, MatchRule.PREFIX, 0, 0, //$NON-NLS-1$ searchScope, new NullProgressMonitor()); if (types.length == 0) { context.registerProblem(PEXProblemIdentifier.UNRESOVABLE, String.format(MESSAGE_CANNOT_RESOLVE_USE, partInfo.fullName.substring(1)), part.getNamespace().sourceStart(), part.getNamespace().sourceEnd()); } } } return true; } @Override public boolean visit(PHPMethodDeclaration s) throws Exception { if (s.getReturnType() instanceof FullyQualifiedReference) { FullyQualifiedReference fqr = (FullyQualifiedReference) s .getReturnType(); if (!isResolved(fqr)) { context.registerProblem(PEXProblemIdentifier.USAGE_RELATED, String.format(MESSAGE_CANNOT_RESOLVE_TYPE, fqr.getName()), fqr.sourceStart(), fqr.sourceEnd()); } } return super.visit(s); } /** * Checks if a FormalParameter can be resolved. * * @param s */ public boolean visit(FormalParameter s) { if (s.getParameterType() == null) { return true; } if (s.getParameterType() instanceof TypeReference && !(s.getParameterType() instanceof FullyQualifiedReference)) { return true; } if (s.getParameterType() instanceof FullyQualifiedReference) { FullyQualifiedReference fqr = (FullyQualifiedReference) s .getParameterType(); if (!isResolved(fqr)) { context.registerProblem(PEXProblemIdentifier.USAGE_RELATED, String.format(MESSAGE_CANNOT_RESOLVE_TYPE, s .getParameterType().getName()), s.getParameterType().sourceStart(), s.getParameterType().sourceEnd()); } } return true; } /** * Check if ClassInstanceCreations can be resolved. * * @param s */ public boolean visit(ClassInstanceCreation s) { if (s.getClassName() instanceof FullyQualifiedReference) { markIfNotExists((FullyQualifiedReference) s.getClassName()); } return true; } @Override public boolean visit(ClassDeclaration s) throws Exception { Collection<TypeReference> interfaceList = s.getInterfaceList(); if (interfaceList == null) { return super.visit(s); } for (TypeReference fqr : s.getInterfaceList()) { markIfNotExists(fqr); } markIfNotExists(s.getSuperClass()); return super.visit(s); } @Override public boolean visit(InterfaceDeclaration s) throws Exception { ASTListNode superClasses = s.getSuperClasses(); if (superClasses != null) { for (Object ob : superClasses.getChilds()) { markIfNotExists(ob); } } return super.visit(s); } @Override public boolean visit(StaticConstantAccess s) throws Exception { if (s.getDispatcher() instanceof FullyQualifiedReference) { FullyQualifiedReference fqr = (FullyQualifiedReference) s .getDispatcher(); if (!"self".equals(fqr.getName()) //$NON-NLS-1$ && !"static".equals(fqr.getName())) { //$NON-NLS-1$ markIfNotExists(fqr); } } return true; } @Override public boolean visit(StaticFieldAccess s) throws Exception { if (s.getDispatcher() instanceof FullyQualifiedReference) { FullyQualifiedReference fqr = (FullyQualifiedReference) s .getDispatcher(); if ("self".equals(fqr.getName()) || "static".equals(fqr.getName())) { //$NON-NLS-1$ //$NON-NLS-2$ return true; } markIfNotExists(fqr); } return true; } @Override public boolean visit(StaticMethodInvocation s) throws Exception { if (s.getReceiver() instanceof FullyQualifiedReference) { FullyQualifiedReference fqr = (FullyQualifiedReference) s .getReceiver(); String fqn = fqr.getFullyQualifiedName(); if ("parent".equals(fqn) || "self".equals(fqn) || "static".equals(fqn)) { return true; } markIfNotExists(fqr); } return false; } /** * Warn if resolve * * @param fqr * @return */ private boolean isResolved(FullyQualifiedReference fqr) { // ignore builtin if (PDTModelUtils.isBuiltinType(fqr.getFullyQualifiedName())) { return true; } if (PHPSimpleTypes.isHintable(fqr.getFullyQualifiedName(), context.getPHPVersion())) { return true; } // check this file if (fqr.getFullyQualifiedName().startsWith(BACK_SLASH)) { return isResolved(fqr.getFullyQualifiedName()); } String check; String sFullName = fqr.getFullyQualifiedName(); for (Entry<PartInfo, Boolean> entry : parts.entrySet()) { String useName = entry.getKey().getUseName(); if (!sFullName.contains(BACK_SLASH) && !sFullName.equals(useName)) { continue; } else if (sFullName.contains(BACK_SLASH) && !sFullName.startsWith(useName + BACK_SLASH)) { continue; } StringBuilder builder = new StringBuilder(entry.getKey().fullName); if (sFullName.contains(BACK_SLASH)) { builder.append(sFullName.substring(useName.length())); } check = builder.toString(); if (isResolved(check)) { return true; } } if (namespace != null) { check = BACK_SLASH + namespace.getFullyQualifiedName() + BACK_SLASH + sFullName; } else { check = BACK_SLASH + sFullName; } if (isResolved(check)) { return true; } return false; } /** * Found item by absolute address * * @param fullyQualifiedReference * @return */ private boolean isResolved(String fullyQualifiedReference) { if (!fullyQualifiedReference.startsWith(BACK_SLASH)) { return false; } final String searchString = fullyQualifiedReference.substring(1); try { for (IType t : context.getSourceModule().getAllTypes()) { if (t.getFullyQualifiedName(BACK_SLASH).equals(searchString)) { return true; } } } catch (ModelException e) { PEXAnalysisPlugin.error(e); } if (found.containsKey(searchString)) { return found.get(searchString); } if (namespaces.containsKey(searchString)) { return namespaces.get(searchString); } if (PDTModelUtils.findTypes(context.getProject(), searchString).length > 0) { found.put(searchString, true); return true; } IDLTKSearchScope scope = SearchEngine.createSearchScope(context.getProject()); if (PHPModelAccess.getDefault().findNamespaces(null, searchString, MatchRule.EXACT, 0, 0, scope, null).length > 0) { namespaces.put(searchString, true); return true; } if (PHPModelAccess.getDefault().findNamespaces(null, searchString + NamespaceReference.NAMESPACE_SEPARATOR, MatchRule.PREFIX, 0, 0, scope, null).length > 0) { namespaces.put(searchString, true); return true; } found.put(searchString, false); return false; } private void markIfNotExists(Object ref) { if (ref == null) { return; } else if (ref instanceof FullyQualifiedReference){ markIfNotExists((FullyQualifiedReference) ref); } } private void markIfNotExists(FullyQualifiedReference fqr){ if (!isResolved(fqr)) { context.registerProblem( PEXProblemIdentifier.USAGE_RELATED, String.format(MESSAGE_CANNOT_RESOLVE_TYPE, fqr.getFullyQualifiedName()), fqr.sourceStart(), fqr.sourceEnd() ); } } }