diff options
Diffstat (limited to 'lint/libs/lint_api')
4 files changed, 227 insertions, 8 deletions
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java index 7d9471d..74df385 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java @@ -124,7 +124,7 @@ public abstract class IssueRegistry { } // Determine if the scope matches - if (!scope.containsAll(issueScope)) { + if (!issue.isAdequate(scope)) { continue; } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java index e221faa..0f985f0 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java @@ -21,6 +21,7 @@ import static com.android.tools.lint.detector.api.LintConstants.ATTR_IGNORE; import static com.android.tools.lint.detector.api.LintConstants.DOT_CLASS; import static com.android.tools.lint.detector.api.LintConstants.DOT_JAR; import static com.android.tools.lint.detector.api.LintConstants.DOT_JAVA; +import static com.android.tools.lint.detector.api.LintConstants.DOT_XML; import static com.android.tools.lint.detector.api.LintConstants.OLD_PROGUARD_FILE; import static com.android.tools.lint.detector.api.LintConstants.PROGUARD_FILE; import static com.android.tools.lint.detector.api.LintConstants.RES_FOLDER; @@ -245,7 +246,7 @@ public class LintDriver { String name = file.getName(); if (name.equals(ANDROID_MANIFEST_XML)) { mScope.add(Scope.MANIFEST); - } else if (name.endsWith(".xml")) { + } else if (name.endsWith(DOT_XML)) { mScope.add(Scope.RESOURCE_FILE); } else if (name.equals(PROGUARD_FILE) || name.equals(OLD_PROGUARD_FILE)) { mScope.add(Scope.PROGUARD_FILE); @@ -766,8 +767,13 @@ public class LintDriver { List<Detector> checks = union(mScopeDetectors.get(Scope.JAVA_FILE), mScopeDetectors.get(Scope.ALL_JAVA_FILES)); if (checks != null && checks.size() > 0) { - List<File> sourceFolders = project.getJavaSourceFolders(); - checkJava(project, main, sourceFolders, checks); + if (project.getSubset() != null) { + checkIndividualJavaFiles(project, main, checks, + project.getSubset()); + } else { + List<File> sourceFolders = project.getJavaSourceFolders(); + checkJava(project, main, sourceFolders, checks); + } } } @@ -775,7 +781,9 @@ public class LintDriver { return; } - checkClasses(project, main); + if (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.JAVA_LIBRARIES)) { + checkClasses(project, main); + } if (mCanceled) { return; @@ -785,7 +793,6 @@ public class LintDriver { checkProGuard(project, main); } } - private void checkProGuard(Project project, Project main) { List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE); if (detectors != null) { @@ -884,7 +891,8 @@ public class LintDriver { /** Check the classes in this project (and if applicable, in any library projects */ private void checkClasses(Project project, Project main) { - if (!(mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.JAVA_LIBRARIES))) { + if (project.getSubset() != null) { + checkIndividualClassFiles(project, main, project.getSubset()); return; } @@ -935,6 +943,47 @@ public class LintDriver { runClassDetectors(Scope.CLASS_FILE, classEntries, project, main); } + private void checkIndividualClassFiles( + @NonNull Project project, + @Nullable Project main, + @NonNull List<File> files) { + List<ClassEntry> entries = new ArrayList<ClassEntry>(files.size()); + + List<File> classFolders = project.getJavaClassFolders(); + if (!classFolders.isEmpty()) { + for (File file : files) { + String path = file.getPath(); + if (file.isFile() && path.endsWith(DOT_CLASS)) { + try { + byte[] bytes = Files.toByteArray(file); + if (bytes != null) { + for (File dir : classFolders) { + if (path.startsWith(dir.getPath())) { + entries.add(new ClassEntry(file, null /* jarFile*/, dir, + bytes)); + break; + } + } + } + } catch (IOException e) { + mClient.log(e, null); + continue; + } + + if (mCanceled) { + return; + } + } + } + + if (entries.size() > 0) { + // No superclass info available on individual lint runs + mSuperClassMap = Collections.emptyMap(); + runClassDetectors(Scope.CLASS_FILE, entries, project, main); + } + } + } + /** * Stack of {@link ClassNode} nodes for outer classes of the currently * processed class, including that class itself. Populated by @@ -1186,6 +1235,32 @@ public class LintDriver { } } + private void checkIndividualJavaFiles( + @NonNull Project project, + @Nullable Project main, + @NonNull List<Detector> checks, + @NonNull List<File> files) { + + IJavaParser javaParser = mClient.getJavaParser(); + if (javaParser == null) { + mClient.log(null, "No java parser provided to lint: not running Java checks"); + return; + } + + JavaVisitor visitor = new JavaVisitor(javaParser, checks); + + for (File file : files) { + if (file.isFile() && file.getPath().endsWith(DOT_JAVA)) { + JavaContext context = new JavaContext(this, project, main, file); + fireEvent(EventType.SCANNING_FILE, context); + visitor.visitFile(context, file); + if (mCanceled) { + return; + } + } + } + } + private void gatherJavaFiles(@NonNull File dir, @NonNull List<File> result) { File[] files = dir.listFiles(); if (files != null) { diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java index 3e49bc4..9f42fb4 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java @@ -21,7 +21,10 @@ import com.android.annotations.Nullable; import com.android.tools.lint.client.api.Configuration; import com.google.common.annotations.Beta; +import java.util.ArrayList; +import java.util.Collection; import java.util.EnumSet; +import java.util.List; /** @@ -47,6 +50,7 @@ public final class Issue implements Comparable<Issue> { private String mMoreInfoUrl; private boolean mEnabledByDefault = true; private final EnumSet<Scope> mScope; + private List<EnumSet<Scope>> mAnalysisScopes; private final Class<? extends Detector> mClass; // Use factory methods @@ -244,6 +248,135 @@ public final class Issue implements Comparable<Issue> { } /** + * Returns the sets of scopes required to analyze this issue, or null if all + * scopes named by {@link Issue#getScope()} are necessary. Note that only + * <b>one</b> match out of this collection is required, not all, and that + * the scope set returned by {@link #getScope()} does not have to be returned + * by this method, but is always implied to be included. + * <p> + * The scopes returned by {@link Issue#getScope()} list all the various + * scopes that are <b>affected</b> by this issue, meaning the detector + * should consider it. Frequently, the detector must analyze all these + * scopes in order to properly decide whether an issue is found. For + * example, the unused resource detector needs to consider both the XML + * resource files and the Java source files in order to decide if a resource + * is unused. If it analyzes just the Java files for example, it might + * incorrectly conclude that a resource is unused because it did not + * discover a resource reference in an XML file. + * <p> + * However, there are other issues where the issue can occur in a variety of + * files, but the detector can consider each in isolation. For example, the + * API checker is affected by both XML files and Java class files (detecting + * both layout constructor references in XML layout files as well as code + * references in .class files). It doesn't have to analyze both; it is + * capable of incrementally analyzing just an XML file, or just a class + * file, without considering the other. + * <p> + * The required scope list provides a list of scope sets that can be used to + * analyze this issue. For each scope set, all the scopes must be matched by + * the incremental analysis, but any one of the scope sets can be analyzed + * in isolation. + * <p> + * The required scope list is not required to include the full scope set + * returned by {@link #getScope()}; that set is always assumed to be + * included. + * <p> + * NOTE: You would normally call {@link #isAdequate(EnumSet)} rather + * than calling this method directly. + * + * @return a list of required scopes, or null. + */ + @Nullable + public Collection<EnumSet<Scope>> getAnalysisScopes() { + return mAnalysisScopes; + } + + /** + * Sets the collection of scopes that are allowed to be analyzed independently. + * See the {@link #getAnalysisScopes()} method for a full explanation. + * Note that you usually want to just call {@link #addAnalysisScope(EnumSet)} + * instead of constructing a list up front and passing it in here. This + * method exists primarily such that commonly used share sets of analysis + * scopes can be reused and set directly. + * + * @param required the collection of scopes + * @return this, for constructor chaining + */ + public Issue setAnalysisScopes(@Nullable List<EnumSet<Scope>> required) { + mAnalysisScopes = required; + + return this; + } + + /** + * Returns true if the given scope is adequate for analyzing this issue. + * This looks through the analysis scopes (see + * {@link #addAnalysisScope(EnumSet)}) and if the scope passed in fully + * covers at least one of them, or if it covers the scope of the issue + * itself (see {@link #getScope()}, which should be a superset of all the + * analysis scopes) returns true. + * <p> + * The scope set returned by {@link Issue#getScope()} lists all the various + * scopes that are <b>affected</b> by this issue, meaning the detector + * should consider it. Frequently, the detector must analyze all these + * scopes in order to properly decide whether an issue is found. For + * example, the unused resource detector needs to consider both the XML + * resource files and the Java source files in order to decide if a resource + * is unused. If it analyzes just the Java files for example, it might + * incorrectly conclude that a resource is unused because it did not + * discover a resource reference in an XML file. + * <p> + * However, there are other issues where the issue can occur in a variety of + * files, but the detector can consider each in isolation. For example, the + * API checker is affected by both XML files and Java class files (detecting + * both layout constructor references in XML layout files as well as code + * references in .class files). It doesn't have to analyze both; it is + * capable of incrementally analyzing just an XML file, or just a class + * file, without considering the other. + * <p> + * An issue can register additional scope sets that can are adequate + * for analyzing the issue, by calling {@link #addAnalysisScope(EnumSet)}. + * This method returns true if the given scope matches one or more analysis + * scope, or the overall scope. + * + * @param scope the scope available for analysis + * @return true if this issue can be analyzed with the given available scope + */ + public boolean isAdequate(@Nullable EnumSet<Scope> scope) { + if (scope.containsAll(mScope)) { + return true; + } + + if (mAnalysisScopes != null) { + for (EnumSet<Scope> analysisScope : mAnalysisScopes) { + if (mScope.containsAll(analysisScope)) { + return true; + } + } + } + + return false; + } + + /** + * Adds a scope set that can be analyzed independently to uncover this issue. + * See the {@link #getAnalysisScopes()} method for a full explanation. + * Note that the {@link #getScope()} does not have to be added here; it is + * always considered an analysis scope. + * + * @param scope the additional scope which can analyze this issue independently + * @return this, for constructor chaining + */ + public Issue addAnalysisScope(@Nullable EnumSet<Scope> scope) { + if (mAnalysisScopes == null) { + mAnalysisScopes = new ArrayList<EnumSet<Scope>>(2); + } + mAnalysisScopes.add(scope); + + return this; + } + + /** * Returns the class of the detector to use to find this issue * * @return the class of the detector to use to find this issue diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java index 4b9da60..a917c11 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java @@ -90,12 +90,19 @@ public enum Scope { * @return true if the scope set references a single file */ public static boolean checkSingleFile(@NonNull EnumSet<Scope> scopes) { - return scopes.size() == 1 && + int size = scopes.size(); + if (size == 2) { + // When single checking a Java source file, we check both its Java source + // and the associated class files + return scopes.contains(JAVA_FILE) && scopes.contains(CLASS_FILE); + } else { + return size == 1 && (scopes.contains(JAVA_FILE) || scopes.contains(CLASS_FILE) || scopes.contains(RESOURCE_FILE) || scopes.contains(PROGUARD_FILE) || scopes.contains(MANIFEST)); + } } /** @@ -123,4 +130,8 @@ public enum Scope { public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES); /** Scope-set used for detectors which are affected by a single Java source file */ public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE); + /** Scope-set used for detectors which are affected by a single Java class file */ + public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE); + /** Scope-set used for detectors which are affected by the manifest only */ + public static final EnumSet<Scope> MANIFEST_SCOPE = EnumSet.of(MANIFEST); } |