aboutsummaryrefslogtreecommitdiffstats
path: root/lint/libs/lint_api
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-05-07 09:33:13 -0700
committerTor Norbye <tnorbye@google.com>2012-05-14 14:26:38 -0700
commitd02dad911cea4f854e32cc99e10cdd31ab89a795 (patch)
tree499c90f565312abbfdc12ec7b8b114da5b1a406e /lint/libs/lint_api
parentbbee9ca7579750dc3812036554b3494d81bf2e8f (diff)
downloadsdk-d02dad911cea4f854e32cc99e10cdd31ab89a795.zip
sdk-d02dad911cea4f854e32cc99e10cdd31ab89a795.tar.gz
sdk-d02dad911cea4f854e32cc99e10cdd31ab89a795.tar.bz2
Run lint on save in Java files, checking .java and .class files
This changeset adds support for per-save file checking in Java source files. It will run both source file and bytecode based checks, possibly at different times (since they are updated at different times). This required some changes to the incremental lint runner, since now incremental checking means possibly touching more than one file (multiple inner classes for a single source), as well as distinguishing between the source files containing markers (the .java file) and the actual files being analyzed (the .class files). This changeset also formalizes incremental lint checking a bit: it now distinguishes between the affected scope of an issue (all the various file types that can affect an issue), as well as the scope sets that are capable of analyzing the issue independently. Take the API check for example. Its affected scope includes both XML files and Java class files, since both can contain API references (in the case of XML, a <GridLayout> reference is an invocation of a constructor of the GridLayout class for example). However, we can analyze a standalone class file, or a standalone XML file, and incrementally update issues found in the file, without regard for the other. Therefore, the API detector has two separate analysis scopes: classes, and XML resources. The manifest registration detector on the other hand needs to look at both the manifest file and the class files; it cannot look at just a subset of these. Change-Id: Ibf5ca8a90846256e0817b419908ee53f8354412a
Diffstat (limited to 'lint/libs/lint_api')
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java2
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java87
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java133
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java13
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);
}