package fr.adrienbrault.idea.symfony2plugin.codeInspection.service; import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiRecursiveElementWalkingVisitor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.jetbrains.php.lang.psi.elements.PhpClass; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.config.xml.XmlHelper; import fr.adrienbrault.idea.symfony2plugin.config.yaml.YamlElementPatternHelper; import fr.adrienbrault.idea.symfony2plugin.form.util.FormUtil; import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil; import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.psi.YAMLCompoundValue; import org.jetbrains.yaml.psi.YAMLFile; import org.jetbrains.yaml.psi.YAMLKeyValue; import org.jetbrains.yaml.psi.YAMLScalar; import java.util.Set; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class TaggedExtendsInterfaceClassInspection extends LocalInspectionTool { @NotNull @Override public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, boolean isOnTheFly) { if(!Symfony2ProjectComponent.isEnabled(holder.getProject())) { return super.buildVisitor(holder, isOnTheFly); } return new PsiElementVisitor() { @Override public void visitFile(PsiFile psiFile) { if(psiFile instanceof YAMLFile) { psiFile.acceptChildren(new YmlClassElementWalkingVisitor(holder, new ContainerCollectionResolver.LazyServiceCollector(holder.getProject()))); } else if(psiFile instanceof XmlFile) { psiFile.acceptChildren(new XmlClassElementWalkingVisitor(holder, new ContainerCollectionResolver.LazyServiceCollector(holder.getProject()))); } } }; } private class XmlClassElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor { private final ProblemsHolder holder; private final ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector; public XmlClassElementWalkingVisitor(ProblemsHolder holder, ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) { this.holder = holder; this.lazyServiceCollector = lazyServiceCollector; } @Override public void visitElement(PsiElement element) { if(XmlHelper.getServiceIdPattern().accepts(element)) { String text = PsiElementUtils.trimQuote(element.getText()); PsiElement[] psiElements = element.getChildren(); // attach problems to string value only if(StringUtils.isNotBlank(text) && psiElements.length > 2) { XmlTag parentOfType = PsiTreeUtil.getParentOfType(element, XmlTag.class); if(parentOfType != null) { registerTaggedProblems(psiElements[1], FormUtil.getTags(parentOfType), text, holder, this.lazyServiceCollector); } } } super.visitElement(element); } } private class YmlClassElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor { private final ProblemsHolder holder; private final ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector; public YmlClassElementWalkingVisitor(ProblemsHolder holder, ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) { this.holder = holder; this.lazyServiceCollector = lazyServiceCollector; } @Override public void visitElement(PsiElement element) { if(YamlElementPatternHelper.getSingleLineScalarKey("class").accepts(element)) { // class: '\Foo' String text = PsiElementUtils.trimQuote(element.getText()); if(StringUtils.isBlank(text)) { super.visitElement(element); return; } PsiElement yamlScalar = element.getParent(); if(!(yamlScalar instanceof YAMLScalar)) { super.visitElement(element); return; } PsiElement classKey = yamlScalar.getParent(); if(classKey instanceof YAMLKeyValue) { PsiElement yamlCompoundValue = classKey.getParent(); if(yamlCompoundValue instanceof YAMLCompoundValue) { PsiElement serviceKeyValue = yamlCompoundValue.getParent(); if(serviceKeyValue instanceof YAMLKeyValue) { Set<String> tags = YamlHelper.collectServiceTags((YAMLKeyValue) serviceKeyValue); if(tags != null && tags.size() > 0) { registerTaggedProblems(element, tags, text, holder, this.lazyServiceCollector); } } } } } super.visitElement(element); } } private void registerTaggedProblems(@NotNull PsiElement source, @NotNull Set<String> tags, @NotNull String serviceClass, @NotNull ProblemsHolder holder, @NotNull ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) { if(tags.size() == 0) { return; } PhpClass phpClass = null; for (String tag : tags) { if(!ServiceUtil.TAG_INTERFACES.containsKey(tag)) { continue; } String expectedClass = ServiceUtil.TAG_INTERFACES.get(tag); if(expectedClass == null) { continue; } // load PhpClass only if we need it, on error exit if(phpClass == null) { phpClass = ServiceUtil.getResolvedClassDefinition(holder.getProject(), serviceClass, lazyServiceCollector); if(phpClass == null) { return; } } // check interfaces if(!PhpElementsUtil.isInstanceOf(phpClass, expectedClass)) { holder.registerProblem(source, String.format("Class needs to implement '%s' for tag '%s'", expectedClass, tag), ProblemHighlightType.WEAK_WARNING); } } } }