blob: a5d1c7b47cf11d653f052fa616438b0b130a6cd3 [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.jetbrains.android.dom.inspections;
import com.android.SdkConstants;
import com.android.resources.ResourceFolderType;
import com.android.tools.idea.databinding.DataBindingAnnotationsService;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.XmlRecursiveElementVisitor;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlChildRole;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.xml.XmlElementDescriptor;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.android.dom.AndroidAnyAttributeDescriptor;
import org.jetbrains.android.dom.AndroidAnyTagDescriptor;
import org.jetbrains.android.dom.manifest.ManifestDomFileDescription;
import org.jetbrains.android.dom.xml.AndroidXmlResourcesUtil;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.TagFromClassDescriptor;
import org.jetbrains.android.resourceManagers.ModuleResourceManagers;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
public class AndroidUnknownAttributeInspection extends LocalInspectionTool {
private static volatile Set<ResourceFolderType> ourSupportedResourceTypes;
@Nls
@NotNull
@Override
public String getGroupDisplayName() {
return AndroidBundle.message("android.inspections.group.name");
}
@Nls
@NotNull
@Override
public String getDisplayName() {
return AndroidBundle.message("android.inspections.unknown.attribute.name");
}
@NotNull
@Override
public String getShortName() {
return "AndroidUnknownAttribute";
}
@Override
public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) {
if (!(file instanceof XmlFile)) {
return ProblemDescriptor.EMPTY_ARRAY;
}
AndroidFacet facet = AndroidFacet.getInstance(file);
if (facet == null) {
return ProblemDescriptor.EMPTY_ARRAY;
}
if (isMyFile(facet, (XmlFile)file)) {
// Support attributes defined by @BindingAdapter annotations.
DataBindingAnnotationsService bindingAnnotationsService = DataBindingAnnotationsService.getInstance(facet);
Set<String> bindingAdapterAttributes = bindingAnnotationsService.getBindingAdapterAttributes();
MyVisitor visitor = new MyVisitor(manager, bindingAdapterAttributes, isOnTheFly);
file.accept(visitor);
return visitor.myResult.toArray(ProblemDescriptor.EMPTY_ARRAY);
}
return ProblemDescriptor.EMPTY_ARRAY;
}
static boolean isMyFile(@NotNull AndroidFacet facet, XmlFile file) {
ResourceFolderType resourceType = ModuleResourceManagers.getInstance(facet).getLocalResourceManager().getFileResourceFolderType(file);
if (resourceType != null) {
if (ourSupportedResourceTypes == null) {
ourSupportedResourceTypes = EnumSet.complementOf(EnumSet.of(ResourceFolderType.INTERPOLATOR, ResourceFolderType.VALUES));
}
// Raw resource files should accept any tag values
if (!ourSupportedResourceTypes.contains(resourceType) || ResourceFolderType.RAW == resourceType) {
return false;
}
if (ResourceFolderType.XML == resourceType) {
final XmlTag rootTag = file.getRootTag();
return rootTag != null && AndroidXmlResourcesUtil.isSupportedRootTag(facet, rootTag.getName());
}
return true;
}
return ManifestDomFileDescription.isManifestFile(file, facet);
}
private static class MyVisitor extends XmlRecursiveElementVisitor {
private final InspectionManager myInspectionManager;
private final Set<String> myBindingAdapterAttributes;
private final boolean myOnTheFly;
final List<ProblemDescriptor> myResult = new ArrayList<>();
private MyVisitor(InspectionManager inspectionManager, Set<String> bindingAdapterAttributes, boolean onTheFly) {
myInspectionManager = inspectionManager;
myBindingAdapterAttributes = bindingAdapterAttributes;
myOnTheFly = onTheFly;
}
@Override
public void visitXmlAttribute(XmlAttribute attribute) {
if (!"xmlns".equals(attribute.getNamespacePrefix())) {
String namespace = attribute.getNamespace();
if (SdkConstants.ANDROID_URI.equals(namespace) || namespace.isEmpty()) {
final XmlTag tag = attribute.getParent();
if (tag == null) return;
XmlElementDescriptor descriptor = tag.getDescriptor();
if (descriptor instanceof AndroidAnyTagDescriptor ||
descriptor instanceof TagFromClassDescriptor && ((TagFromClassDescriptor)descriptor).getClazz() == null) {
// Don't register warning for unknown tags tags or for unresolved Views.
return;
}
if (attribute.getDescriptor() instanceof AndroidAnyAttributeDescriptor) {
if (myBindingAdapterAttributes.contains(attribute.getName())) {
// Attribute is defined by @BindingAdapter annotation.
return;
}
final ASTNode node = attribute.getNode();
assert node != null;
ASTNode nameNode = XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(node);
final PsiElement nameElement = nameNode != null ? nameNode.getPsi() : null;
if (nameElement != null) {
myResult.add(myInspectionManager.createProblemDescriptor(nameElement, AndroidBundle
.message("android.inspections.unknown.attribute.message", attribute.getName()), myOnTheFly, LocalQuickFix.EMPTY_ARRAY,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING));
}
}
}
}
}
}
}