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

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

The newest version!
/*
 * Copyright (C) 2012 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.tools.lint.detector.api.CharSequences.indexOf;
import static com.android.tools.lint.detector.api.CharSequences.regionMatches;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector.JavaPsiScanner;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.XmlContext;
import com.intellij.psi.PsiElement;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Looks for issues in Java comments
 */
public class CommentDetector extends ResourceXmlDetector implements JavaPsiScanner {
    private static final String STOPSHIP_COMMENT = "STOPSHIP";

    private static final Implementation IMPLEMENTATION = new Implementation(
            CommentDetector.class,
            Scope.JAVA_AND_RESOURCE_FILES,
            Scope.JAVA_FILE_SCOPE,
            Scope.RESOURCE_FILE_SCOPE);

    /** Looks for hidden code */
    public static final Issue EASTER_EGG = Issue.create(
            "EasterEgg",
            "Code contains easter egg",
            "An \"easter egg\" is code deliberately hidden in the code, both from potential " +
            "users and even from other developers. This lint check looks for code which " +
            "looks like it may be hidden from sight.",
            Category.SECURITY,
            6,
            Severity.WARNING,
            IMPLEMENTATION)
            .setEnabledByDefault(false);

    /** Looks for special comment markers intended to stop shipping the code */
    public static final Issue STOP_SHIP = Issue.create(
            "StopShip",
            "Code contains `STOPSHIP` marker",

            "Using the comment `// STOPSHIP` can be used to flag code that is incomplete but " +
            "checked in. This comment marker can be used to indicate that the code should not " +
            "be shipped until the issue is addressed, and lint will look for these.",
            Category.CORRECTNESS,
            10,
            Severity.WARNING,
            IMPLEMENTATION)
            .setEnabledByDefault(false);

    private static final String ESCAPE_STRING = "\\u002a\\u002f";


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

    @Override
    public List> getApplicablePsiTypes() {
        return null;
    }

    @Override
    public void afterCheckFile(@NonNull Context context) {
        if (context instanceof JavaContext) {
            checkJava((JavaContext) context);
        }
    }

    private static void checkJava(@NonNull JavaContext context) {
        // Lombok does not generate comment nodes for block and line comments, only for
        // javadoc comments!
        CharSequence source = context.getContents();
        if (source == null) {
            return;
        }
        // Process the Java source such that we pass tokens to it

        for (int i = 0, n = source.length() - 1; i < n; i++) {
            char c = source.charAt(i);
            if (c == '\\') {
                i += 1;
            } else if (c == '/') {
                char next = source.charAt(i + 1);
                if (next == '/') {
                    // Line comment
                    int start = i + 2;
                    int end = indexOf(source, '\n', start);
                    if (end == -1) {
                        end = n;
                    }
                    checkComment(context, null, null, source, 0, start, end);
                } else if (next == '*') {
                    // Block comment
                    int start = i + 2;
                    int end = indexOf(source, "*/", start);
                    if (end == -1) {
                        end = n;
                    }
                    checkComment(context, null, null, source, 0, start, end);
                }
            }
        }
    }

    @Override
    public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
        checkXml(context, document);
    }

    private static void checkXml(@NonNull XmlContext context, Node node) {
        if (node.getNodeType() == Node.COMMENT_NODE) {
            String source = node.getNodeValue();
            checkComment(null, context, node, source, 0, 0, source.length());
        }

        NodeList children = node.getChildNodes();
        for (int i = 0, n = children.getLength(); i < n; i++) {
            checkXml(context, children.item(i));
        }
    }

    private static void checkComment(
            @Nullable JavaContext javaContext,
            @Nullable XmlContext xmlContext,
            @Nullable Node xmlNode,
            @NonNull CharSequence source,
            int offset,
            int start,
            int end) {
        assert javaContext != null || xmlContext != null;
        char prev = 0;
        char c;
        Context context = javaContext != null ? javaContext : xmlContext;
        for (int i = start; i < end - 2; i++, prev = c) {
            c = source.charAt(i);
            if (prev == '\\') {
                if (c == 'u' || c == 'U') {
                    if (regionMatches(source,true, i - 1, ESCAPE_STRING,
                            0, ESCAPE_STRING.length())) {
                        String message =
                                "Code might be hidden here; found unicode escape sequence " +
                                "which is interpreted as comment end, compiled code follows";
                        if (javaContext != null) {
                            Location location = Location.create(context.file, source,
                                    offset + i - 1, offset + i - 1 + ESCAPE_STRING.length());
                            javaContext.report(EASTER_EGG, (PsiElement) null, location, message);
                        } else {
                            assert xmlNode != null;
                            Location location = xmlContext.getLocation(xmlNode, i,
                                    i + ESCAPE_STRING.length());
                            xmlContext.report(EASTER_EGG, xmlNode, location, message);
                        }
                    }
                } else {
                    i++;
                }
            } else if (prev == 'S' && c == 'T' &&
                    regionMatches(source, i - 1, STOPSHIP_COMMENT, 0, STOPSHIP_COMMENT.length())) {
                // TODO: Only flag this issue in release mode??
                String message =
                        "`STOPSHIP` comment found; points to code which must be fixed prior " +
                        "to release";
                if (javaContext != null) {
                    Location location = Location.create(context.file, source,
                            offset + i - 1, offset + i - 1 + STOPSHIP_COMMENT.length());
                    javaContext.report(STOP_SHIP, (PsiElement) null, location, message);
                } else {
                    assert xmlNode != null;
                    Location location = xmlContext.getLocation(xmlNode, i,
                            i + STOPSHIP_COMMENT.length());
                    xmlContext.report(STOP_SHIP, xmlNode, location, message);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy