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.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiRecursiveElementWalkingVisitor; import com.intellij.psi.xml.XmlFile; import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment; import com.jetbrains.php.lang.psi.PhpFile; import com.jetbrains.php.lang.psi.elements.MethodReference; import com.jetbrains.php.lang.psi.elements.PhpClass; import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil; 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.dic.ContainerService; import fr.adrienbrault.idea.symfony2plugin.dic.container.ServiceInterface; import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver; import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.YAMLTokenTypes; import org.jetbrains.yaml.psi.YAMLFile; import java.util.Map; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class ServiceDeprecatedClassesInspection 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 MyPsiElementVisitor(holder); } private static class ProblemRegistrar { private ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector; public void attachDeprecatedProblem(PsiElement element, String text, ProblemsHolder holder) { PhpClass phpClass = ServiceUtil.getResolvedClassDefinition(element.getProject(), text, getLazyServiceCollector(element.getProject())); if(phpClass == null) { return; } PhpDocComment docComment = phpClass.getDocComment(); if(docComment != null && docComment.getTagElementsByName("@deprecated").length > 0) { holder.registerProblem(element, String.format("Class '%s' is deprecated", phpClass.getName()), ProblemHighlightType.LIKE_DEPRECATED); } } public void attachServiceDeprecatedProblem(@NotNull PsiElement element, @NotNull String serviceName, @NotNull ProblemsHolder holder) { Map<String, ContainerService> services = getLazyServiceCollector(element.getProject()).getCollector().getServices(); if(!services.containsKey(serviceName)) { return; } ServiceInterface serviceDef = services.get(serviceName).getService(); if(serviceDef == null || !serviceDef.isDeprecated()) { return; } holder.registerProblem(element, String.format("Service '%s' is deprecated", serviceName), ProblemHighlightType.LIKE_DEPRECATED); } private ContainerCollectionResolver.LazyServiceCollector getLazyServiceCollector(Project project) { return this.lazyServiceCollector == null ? this.lazyServiceCollector = new ContainerCollectionResolver.LazyServiceCollector(project) : this.lazyServiceCollector; } public void reset() { this.lazyServiceCollector = null; } } private class XmlClassElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor { private final ProblemsHolder holder; private final ProblemRegistrar problemRegistrar; public XmlClassElementWalkingVisitor(ProblemsHolder holder, ProblemRegistrar problemRegistrar) { this.holder = holder; this.problemRegistrar = problemRegistrar; } @Override public void visitElement(PsiElement element) { boolean serviceArgumentAccepted = XmlHelper.getArgumentServiceIdPattern().accepts(element); if(serviceArgumentAccepted || XmlHelper.getServiceIdPattern().accepts(element)) { String text = PsiElementUtils.trimQuote(element.getText()); PsiElement[] psiElements = element.getChildren(); // we need to attach to child because else strike out equal and quote char if(StringUtils.isNotBlank(text) && psiElements.length > 2) { this.problemRegistrar.attachDeprecatedProblem(psiElements[1], text, holder); // check service arguments for "deprecated" defs if(serviceArgumentAccepted) { this.problemRegistrar.attachServiceDeprecatedProblem(psiElements[1], text, holder); } } } super.visitElement(element); } } private class YmlClassElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor { private final ProblemsHolder holder; private final ProblemRegistrar problemRegistrar; public YmlClassElementWalkingVisitor(ProblemsHolder holder, ProblemRegistrar problemRegistrar) { this.holder = holder; this.problemRegistrar = problemRegistrar; } @Override public void visitElement(PsiElement element) { if(YamlElementPatternHelper.getSingleLineScalarKey("class").accepts(element)) { // class: '\Foo' String text = PsiElementUtils.trimQuote(element.getText()); if(StringUtils.isNotBlank(text)) { this.problemRegistrar.attachDeprecatedProblem(element, text, holder); } } else if(element.getNode().getElementType() == YAMLTokenTypes.TEXT) { // @service String text = element.getText(); if(text != null && StringUtils.isNotBlank(text) && text.startsWith("@")) { this.problemRegistrar.attachDeprecatedProblem(element, text.substring(1), holder); this.problemRegistrar.attachServiceDeprecatedProblem(element, text.substring(1), holder); } } super.visitElement(element); } } private class PhpClassWalkingVisitor extends PsiRecursiveElementWalkingVisitor { private final ProblemsHolder holder; private final ProblemRegistrar problemRegistrar; Symfony2InterfacesUtil symfony2InterfacesUtil; public PhpClassWalkingVisitor(ProblemsHolder holder, ProblemRegistrar problemRegistrar) { this.holder = holder; this.problemRegistrar = problemRegistrar; symfony2InterfacesUtil = new Symfony2InterfacesUtil(); } @Override public void visitElement(PsiElement element) { MethodReference methodReference = PsiElementUtils.getMethodReferenceWithFirstStringParameter(element); if (methodReference == null || !symfony2InterfacesUtil.isContainerGetCall(methodReference)) { super.visitElement(element); return; } PsiElement psiElement = element.getParent(); if(!(psiElement instanceof StringLiteralExpression)) { super.visitElement(element); return; } String contents = ((StringLiteralExpression) psiElement).getContents(); if(StringUtils.isNotBlank(contents)) { this.problemRegistrar.attachDeprecatedProblem(element, contents, holder); this.problemRegistrar.attachServiceDeprecatedProblem(element, contents, holder); } super.visitElement(element); } } private class MyPsiElementVisitor extends PsiElementVisitor { private final ProblemsHolder holder; public MyPsiElementVisitor(ProblemsHolder holder) { this.holder = holder; } @Override public void visitFile(PsiFile psiFile) { ProblemRegistrar problemRegistrar = null; if(psiFile instanceof YAMLFile) { psiFile.acceptChildren(new YmlClassElementWalkingVisitor(holder, problemRegistrar = new ProblemRegistrar())); } else if(psiFile instanceof XmlFile) { psiFile.acceptChildren(new XmlClassElementWalkingVisitor(holder, problemRegistrar = new ProblemRegistrar())); } else if(psiFile instanceof PhpFile) { psiFile.acceptChildren(new PhpClassWalkingVisitor(holder, problemRegistrar = new ProblemRegistrar())); } if(problemRegistrar != null) { problemRegistrar.reset(); } super.visitFile(psiFile); } } }