blob: addcb96c8dad357f7f67150dc665490596c594ee [file] [log] [blame]
/*
* Copyright 2007 Sascha Weinreuter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.intellij.plugins.relaxNG.inspections;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.XmlElementVisitor;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Query;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomManager;
import org.intellij.plugins.relaxNG.ApplicationLoader;
import org.intellij.plugins.relaxNG.compact.psi.RncDefine;
import org.intellij.plugins.relaxNG.compact.psi.RncElementVisitor;
import org.intellij.plugins.relaxNG.compact.psi.RncGrammar;
import org.intellij.plugins.relaxNG.compact.psi.impl.RncDefineImpl;
import org.intellij.plugins.relaxNG.model.resolve.RelaxIncludeIndex;
import org.intellij.plugins.relaxNG.xml.dom.RngGrammar;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
/**
* Created by IntelliJ IDEA.
* User: sweinreuter
* Date: 26.07.2007
*/
public class UnusedDefineInspection extends BaseInspection {
@Override
public boolean isEnabledByDefault() {
return false;
}
@Override
@Nls
@NotNull
public String getDisplayName() {
return "Unused Define";
}
@Override
@NonNls
@NotNull
public String getShortName() {
return "UnusedDefine";
}
@Override
@NotNull
public RncElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new MyElementVisitor(holder);
}
private static final class MyElementVisitor extends RncElementVisitor {
private final ProblemsHolder myHolder;
private final XmlElementVisitor myXmlVisitor = new XmlElementVisitor() {
@Override
public void visitXmlTag(XmlTag tag) {
MyElementVisitor.this.visitXmlTag(tag);
}
};
public MyElementVisitor(ProblemsHolder holder) {
myHolder = holder;
}
@Override
protected void superVisitElement(PsiElement element) {
element.accept(myXmlVisitor);
}
@Override
public void visitDefine(RncDefine pattern) {
final RncGrammar grammar = PsiTreeUtil.getParentOfType(pattern, RncGrammar.class);
final PsiFile file = pattern.getContainingFile();
if (grammar != null) {
if (processRncUsages(pattern, new LocalSearchScope(grammar))) return;
} else {
if (processRncUsages(pattern, new LocalSearchScope(file))) return;
}
final PsiElementProcessor.CollectElements<XmlFile> collector = new PsiElementProcessor.CollectElements<XmlFile>();
RelaxIncludeIndex.processBackwardDependencies((XmlFile)file, collector);
if (processRncUsages(pattern, new LocalSearchScope(collector.toArray()))) return;
final ASTNode astNode = ((RncDefineImpl)pattern).getNameNode();
myHolder.registerProblem(astNode.getPsi(), "Unreferenced define", ProblemHighlightType.LIKE_UNUSED_SYMBOL, new MyFix<RncDefine>(pattern));
}
private static boolean processRncUsages(PsiElement tag, LocalSearchScope scope) {
final Query<PsiReference> query = ReferencesSearch.search(tag, scope);
for (PsiReference reference : query) {
final PsiElement e = reference.getElement();
final RncDefine t = PsiTreeUtil.getParentOfType(e, RncDefine.class, false);
if (t == null || !PsiTreeUtil.isAncestor(tag, t, true)) {
return true;
}
}
return false;
}
public void visitXmlTag(XmlTag tag) {
final PsiFile file = tag.getContainingFile();
if (file.getFileType() != StdFileTypes.XML) {
return;
}
if (!tag.getLocalName().equals("define")) {
return;
}
if (!tag.getNamespace().equals(ApplicationLoader.RNG_NAMESPACE)) {
return;
}
if (tag.getAttribute("combine") != null) {
return; // ?
}
final XmlAttribute attr = tag.getAttribute("name");
if (attr == null) return;
final XmlAttributeValue value = attr.getValueElement();
if (value == null) return;
final String s = value.getValue();
if (s == null || s.length() == 0) {
return;
}
final PsiElement parent = value.getParent();
if (!(parent instanceof XmlAttribute)) {
return;
}
if (!"name".equals(((XmlAttribute)parent).getName())) {
return;
}
final PsiElement grandParent = parent.getParent();
if (!(grandParent instanceof XmlTag)) {
return;
}
final DomElement element = DomManager.getDomManager(tag.getProject()).getDomElement(tag);
if (element == null) {
return;
}
final RngGrammar rngGrammar = element.getParentOfType(RngGrammar.class, true);
if (rngGrammar != null) {
if (processUsages(tag, value, new LocalSearchScope(rngGrammar.getXmlTag()))) return;
} else {
if (processUsages(tag, value, new LocalSearchScope(file))) return;
}
final PsiElementProcessor.CollectElements<XmlFile> collector = new PsiElementProcessor.CollectElements<XmlFile>();
RelaxIncludeIndex.processBackwardDependencies((XmlFile)file, collector);
if (processUsages(tag, value, new LocalSearchScope(collector.toArray()))) return;
myHolder.registerProblem(value, "Unreferenced define", ProblemHighlightType.LIKE_UNUSED_SYMBOL, new MyFix<XmlTag>(tag));
}
private static boolean processUsages(PsiElement tag, XmlAttributeValue value, LocalSearchScope scope) {
final Query<PsiReference> query = ReferencesSearch.search(tag, scope, true);
for (PsiReference reference : query) {
final PsiElement e = reference.getElement();
if (e != value) {
final XmlTag t = PsiTreeUtil.getParentOfType(e, XmlTag.class);
if (t != null && !PsiTreeUtil.isAncestor(tag, t, true)) {
return true;
}
}
}
return false;
}
private static class MyFix<T extends PsiElement> implements LocalQuickFix {
private final T myTag;
public MyFix(T tag) {
myTag = tag;
}
@Override
@NotNull
public String getName() {
return "Remove Define";
}
@Override
@NotNull
public String getFamilyName() {
return getName();
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
try {
if (myTag.isValid()) {
myTag.delete();
}
} catch (IncorrectOperationException e) {
Logger.getInstance(UnusedDefineInspection.class.getName()).error(e);
}
}
}
}
}