All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.lint.checks.DetectMissingPrefix Maven / Gradle / Ivy

There is a newer version: 25.3.0
Show newest version
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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 com.android.tools.lint.checks;

import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
import static com.android.SdkConstants.ANDROID_SUPPORT_PKG_PREFIX;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_CLASS;
import static com.android.SdkConstants.ATTR_CORE_APP;
import static com.android.SdkConstants.ATTR_LAYOUT;
import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.ATTR_PACKAGE;
import static com.android.SdkConstants.ATTR_STYLE;
import static com.android.SdkConstants.TOOLS_URI;
import static com.android.SdkConstants.VIEW_TAG;
import static com.android.resources.ResourceFolderType.ANIM;
import static com.android.resources.ResourceFolderType.ANIMATOR;
import static com.android.resources.ResourceFolderType.COLOR;
import static com.android.resources.ResourceFolderType.DRAWABLE;
import static com.android.resources.ResourceFolderType.INTERPOLATOR;
import static com.android.resources.ResourceFolderType.LAYOUT;
import static com.android.resources.ResourceFolderType.MENU;

import com.android.annotations.NonNull;
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LayoutDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import com.android.tools.lint.detector.api.XmlContext;

import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * Detects layout attributes on builtin Android widgets that do not specify
 * a prefix but probably should.
 */
public class DetectMissingPrefix extends LayoutDetector {

    /** Attributes missing the android: prefix */
    @SuppressWarnings("unchecked")
    public static final Issue MISSING_NAMESPACE = Issue.create(
            "MissingPrefix", //$NON-NLS-1$
            "Missing Android XML namespace",
            "Most Android views have attributes in the Android namespace. When referencing " +
            "these attributes you *must* include the namespace prefix, or your attribute will " +
            "be interpreted by `aapt` as just a custom attribute.\n" +
            "\n" +
            "Similarly, in manifest files, nearly all attributes should be in the `android:` " +
            "namespace.",

            Category.CORRECTNESS,
            6,
            Severity.ERROR,
            new Implementation(
                    DetectMissingPrefix.class,
                    Scope.MANIFEST_AND_RESOURCE_SCOPE,
                    Scope.MANIFEST_SCOPE, Scope.RESOURCE_FILE_SCOPE));

    private static final Set NO_PREFIX_ATTRS = new HashSet();
    static {
        NO_PREFIX_ATTRS.add(ATTR_CLASS);
        NO_PREFIX_ATTRS.add(ATTR_STYLE);
        NO_PREFIX_ATTRS.add(ATTR_LAYOUT);
        NO_PREFIX_ATTRS.add(ATTR_PACKAGE);
        NO_PREFIX_ATTRS.add(ATTR_CORE_APP);
    }

    /** Constructs a new {@link DetectMissingPrefix} */
    public DetectMissingPrefix() {
    }

    @Override
    public boolean appliesTo(@NonNull ResourceFolderType folderType) {
        return folderType == LAYOUT
                || folderType == MENU
                || folderType == DRAWABLE
                || folderType == ANIM
                || folderType == ANIMATOR
                || folderType == COLOR
                || folderType == INTERPOLATOR;
    }

    @NonNull
    @Override
    public Speed getSpeed() {
        return Speed.FAST;
    }

    @Override
    public Collection getApplicableAttributes() {
        return ALL;
    }

    @Override
    public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
        String uri = attribute.getNamespaceURI();
        if (uri == null || uri.isEmpty()) {
            String name = attribute.getName();
            if (name == null) {
                return;
            }
            if (NO_PREFIX_ATTRS.contains(name)) {
                return;
            }

            Element element = attribute.getOwnerElement();
            if (isCustomView(element) && context.getResourceFolderType() != null) {
                return;
            }

            if (name.indexOf(':') != -1) {
                // Don't flag warnings for attributes that already have a different
                // namespace! This doesn't usually happen when lint is run from the
                // command line, since (with the exception of xmlns: declaration attributes)
                // an attribute shouldn't have a prefix *and* have no namespace, but
                // when lint is run in the IDE (with a more fault-tolerant XML parser)
                // this can happen, and we don't want to flag erroneous/misleading lint
                // errors in this case.
                return;
            }

            context.report(MISSING_NAMESPACE, attribute,
                    context.getLocation(attribute),
                    "Attribute is missing the Android namespace prefix");
        } else if (!ANDROID_URI.equals(uri)
                && !TOOLS_URI.equals(uri)
                && context.getResourceFolderType() == ResourceFolderType.LAYOUT
                && !isCustomView(attribute.getOwnerElement())
                && !attribute.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
                // TODO: Consider not enforcing that the parent is a custom view
                // too, though in that case we should filter out views that are
                // layout params for the custom view parent:
                // ....&& !attribute.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
                && attribute.getOwnerElement().getParentNode().getNodeType() == Node.ELEMENT_NODE
                && !isCustomView((Element) attribute.getOwnerElement().getParentNode())) {
            context.report(MISSING_NAMESPACE, attribute,
                    context.getLocation(attribute),
                    String.format("Unexpected namespace prefix \"%1$s\" found for tag `%2$s`",
                            attribute.getPrefix(), attribute.getOwnerElement().getTagName()));
        }
    }

    private static boolean isCustomView(Element element) {
        // If this is a custom view, the usage of custom attributes can be legitimate
        String tag = element.getTagName();
        if (tag.equals(VIEW_TAG)) {
            // 
            return true;
        }

        return tag.indexOf('.') != -1 && (!tag.startsWith(ANDROID_PKG_PREFIX)
                || tag.startsWith(ANDROID_SUPPORT_PKG_PREFIX));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy