aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/tools.atree1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/.classpath1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java180
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/UnusedResourceDetector.java155
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java6
-rwxr-xr-xeclipse/scripts/create_all_symlinks.sh1
-rw-r--r--lint/cli/.classpath1
-rw-r--r--lint/cli/Android.mk3
-rw-r--r--lint/cli/etc/manifest.txt2
-rw-r--r--lint/cli/src/com/android/tools/lint/LombokParser.java117
-rw-r--r--lint/cli/src/com/android/tools/lint/Main.java6
-rw-r--r--lint/libs/lint_api/.classpath1
-rw-r--r--lint/libs/lint_api/Android.mk3
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/IJavaParser.java72
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/JavaVisitor.java1145
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java45
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java7
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java144
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/JavaContext.java71
-rw-r--r--lint/libs/lint_checks/.classpath1
-rw-r--r--lint/libs/lint_checks/Android.mk5
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java186
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java269
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java7
25 files changed, 1980 insertions, 450 deletions
diff --git a/build/tools.atree b/build/tools.atree
index 39deb4b..8ff0554 100644
--- a/build/tools.atree
+++ b/build/tools.atree
@@ -129,6 +129,7 @@ framework/swing-worker-1.1.jar tools/lib/swing-wo
prebuilts/tools/common/asm-tools/asm-4.0.jar tools/lib/asm-4.0.jar
prebuilts/tools/common/asm-tools/asm-tree-4.0.jar tools/lib/asm-tree-4.0.jar
prebuilts/tools/common/guava-tools/guava-10.0.1.jar tools/lib/guava-10.0.1.jar
+prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar tools/lib/lombok_ast-0.2.jar
# Proguard
external/proguard/docs/license.html tools/proguard/license.html
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
index 55eb303..d8d5c9b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
@@ -27,5 +27,6 @@
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-10.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/lombok-ast/src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index b0d1219..053da8b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -18,6 +18,7 @@ Bundle-ClassPath: .,
libs/assetstudio.jar,
libs/lint_api.jar,
libs/lint_checks.jar,
+ libs/lombok-ast-0.2.jar,
libs/httpclient-4.1.1.jar,
libs/httpcore-4.1.jar,
libs/httpmime-4.1.1.jar,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
index 9622781..c223b79 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
@@ -25,12 +25,14 @@ import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.tools.lint.checks.BuiltinIssueRegistry;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.IDomParser;
+import com.android.tools.lint.client.api.IJavaParser;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.DefaultPosition;
import com.android.tools.lint.detector.api.Detector;
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.Location.Handle;
import com.android.tools.lint.detector.api.Position;
@@ -48,6 +50,17 @@ import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.parser.Parser;
+import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
@@ -71,6 +84,10 @@ import java.io.IOException;
import java.util.Collections;
import java.util.List;
+import lombok.ast.ecj.EcjTreeConverter;
+import lombok.ast.grammar.ParseProblem;
+import lombok.ast.grammar.Source;
+
/**
* Eclipse implementation for running lint on workspace files and projects.
*/
@@ -83,6 +100,7 @@ public class EclipseLintClient extends LintClient implements IDomParser {
private boolean mWasFatal;
private boolean mFatalOnly;
private Configuration mConfiguration;
+ private EclipseJavaParser mJavaParser;
/**
* Creates a new {@link EclipseLintClient}.
@@ -116,6 +134,15 @@ public class EclipseLintClient extends LintClient implements IDomParser {
return this;
}
+ @Override
+ public IJavaParser getJavaParser() {
+ if (mJavaParser == null) {
+ mJavaParser = new EclipseJavaParser();
+ }
+
+ return mJavaParser;
+ }
+
// ----- Implements IDomParser -----
@Override
@@ -563,12 +590,6 @@ public class EclipseLintClient extends LintClient implements IDomParser {
@Override
public Class<? extends Detector> replaceDetector(Class<? extends Detector> detectorClass) {
- // Replace the generic UnusedResourceDetector with an Eclipse optimized one
- // which uses the Java AST
- if (detectorClass == com.android.tools.lint.checks.UnusedResourceDetector.class) {
- return UnusedResourceDetector.class;
- }
-
return detectorClass;
}
@@ -643,5 +664,152 @@ public class EclipseLintClient extends LintClient implements IDomParser {
return this;
}
}
+
+ private static class EclipseJavaParser implements IJavaParser {
+ private static final boolean USE_ECLIPSE_PARSER = true;
+ private final Parser mParser;
+
+ EclipseJavaParser() {
+ if (USE_ECLIPSE_PARSER) {
+ CompilerOptions options = new CompilerOptions();
+ // Read settings from project? Note that this doesn't really matter because
+ // we will only be parsing, not actually compiling.
+ options.complianceLevel = ClassFileConstants.JDK1_6;
+ options.sourceLevel = ClassFileConstants.JDK1_6;
+ options.targetJDK = ClassFileConstants.JDK1_6;
+ options.parseLiteralExpressionsAsConstants = true;
+ ProblemReporter problemReporter = new ProblemReporter(
+ DefaultErrorHandlingPolicies.exitOnFirstError(),
+ options,
+ new DefaultProblemFactory());
+ mParser = new Parser(problemReporter, options.parseLiteralExpressionsAsConstants);
+ mParser.javadocParser.checkDocComment = false;
+ } else {
+ mParser = null;
+ }
+ }
+
+ @Override
+ public lombok.ast.Node parseJava(JavaContext context) {
+ if (USE_ECLIPSE_PARSER) {
+ // Use Eclipse's compiler
+ EcjTreeConverter converter = new EcjTreeConverter();
+ String code = context.getContents();
+
+ CompilationUnit sourceUnit = new CompilationUnit(code.toCharArray(),
+ context.file.getName(), "UTF-8"); //$NON-NLS-1$
+ CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0);
+ CompilationUnitDeclaration unit = null;
+ try {
+ unit = mParser.parse(sourceUnit, compilationResult);
+ } catch (AbortCompilation e) {
+
+ String message;
+ Location location;
+ if (e.problem != null) {
+ CategorizedProblem problem = e.problem;
+ message = problem.getMessage();
+ location = Location.create(context.file,
+ new DefaultPosition(problem.getSourceLineNumber() - 1, -1,
+ problem.getSourceStart()),
+ new DefaultPosition(problem.getSourceLineNumber() - 1, -1,
+ problem.getSourceEnd()));
+ } else {
+ location = Location.create(context.file);
+ message = e.getCause() != null ? e.getCause().getLocalizedMessage() :
+ e.getLocalizedMessage();
+ }
+
+ context.report(IssueRegistry.PARSER_ERROR, location, message, null);
+ return null;
+ }
+ if (unit == null) {
+ return null;
+ }
+
+ try {
+ converter.visit(code, unit);
+ List<? extends lombok.ast.Node> nodes = converter.getAll();
+
+ // There could be more than one node when there are errors; pick out the
+ // compilation unit node
+ for (lombok.ast.Node node : nodes) {
+ if (node instanceof CompilationUnit) {
+ return node;
+ }
+ }
+
+ return null;
+ } catch (Throwable t) {
+ AdtPlugin.log(t, "Failed converting ECJ parse tree to Lombok for file %1$s",
+ context.file.getPath());
+ return null;
+ }
+ } else {
+ // Use Lombok for now
+ Source source = new Source(context.getContents(), context.file.getName());
+ List<lombok.ast.Node> nodes = source.getNodes();
+
+ // Don't analyze files containing errors
+ List<ParseProblem> problems = source.getProblems();
+ if (problems != null && problems.size() > 0) {
+ for (ParseProblem problem : problems) {
+ lombok.ast.Position position = problem.getPosition();
+ Location location = Location.create(context.file,
+ context.getContents(), position.getStart(), position.getEnd());
+ String message = problem.getMessage();
+ context.report(
+ IssueRegistry.PARSER_ERROR, location,
+ message,
+ null);
+
+ }
+ return null;
+ }
+
+ // There could be more than one node when there are errors; pick out the
+ // compilation unit node
+ for (lombok.ast.Node node : nodes) {
+ if (node instanceof lombok.ast.CompilationUnit) {
+ return node;
+ }
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public Location getLocation(JavaContext context, lombok.ast.Node node) {
+ lombok.ast.Position position = node.getPosition();
+ return Location.create(context.file, context.getContents(),
+ position.getStart(), position.getEnd());
+ }
+
+ @Override
+ public Handle createLocationHandle(XmlContext context, lombok.ast.Node node) {
+ return new LocationHandle(context.file, node);
+ }
+
+ @Override
+ public void dispose(JavaContext context, lombok.ast.Node compilationUnit) {
+ }
+
+ /* Handle for creating positions cheaply and returning full fledged locations later */
+ private class LocationHandle implements Handle {
+ private File mFile;
+ private lombok.ast.Node mNode;
+
+ public LocationHandle(File file, lombok.ast.Node node) {
+ mFile = file;
+ mNode = node;
+ }
+
+ @Override
+ public Location resolve() {
+ lombok.ast.Position pos = mNode.getPosition();
+ return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd());
+ }
+ }
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/UnusedResourceDetector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/UnusedResourceDetector.java
deleted file mode 100644
index 0713dad..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/UnusedResourceDetector.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.ide.eclipse.adt.internal.lint;
-
-import com.android.ide.eclipse.adt.AdtUtils;
-import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
-import com.android.tools.lint.detector.api.Context;
-
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.jdt.core.ICompilationUnit;
-import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.core.IPackageFragment;
-import org.eclipse.jdt.core.IPackageFragmentRoot;
-import org.eclipse.jdt.core.dom.AST;
-import org.eclipse.jdt.core.dom.ASTParser;
-import org.eclipse.jdt.core.dom.ASTVisitor;
-import org.eclipse.jdt.core.dom.CompilationUnit;
-import org.eclipse.jdt.core.dom.FieldDeclaration;
-import org.eclipse.jdt.core.dom.QualifiedName;
-import org.eclipse.jdt.core.dom.TypeDeclaration;
-import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Finds unused resources, optimized for Eclipse to more effectively and
- * accurately pull out usage information for R fields
- */
-public class UnusedResourceDetector extends com.android.tools.lint.checks.UnusedResourceDetector {
- private static final String R_PREFIX = "R."; //$NON-NLS-1$
-
- public UnusedResourceDetector() {
- }
-
- @Override
- public void checkJavaSources(Context context, List<File> sourceFolders) {
- IProject project = getProject(context);
- if (project == null) {
- return;
- }
- IJavaProject javaProject;
- try {
- javaProject = BaseProjectHelper.getJavaProject(project);
- } catch (CoreException e) {
- context.log(e, null);
- return;
- }
- if (javaProject == null) {
- return;
- }
-
- // Scan Java code in project for R.field references
- try {
- IPackageFragment[] packages = javaProject.getPackageFragments();
- for (IPackageFragment pkg : packages) {
- if (pkg.getKind() == IPackageFragmentRoot.K_SOURCE) {
- for (ICompilationUnit unit : pkg.getCompilationUnits()) {
-
- ASTParser parser = ASTParser.newParser(AST.JLS3);
- parser.setKind(ASTParser.K_COMPILATION_UNIT);
- parser.setSource(unit);
- parser.setResolveBindings(true);
- CompilationUnit parse = (CompilationUnit) parser.createAST(null); // parse
-
- // In the R file, look for known declarations
- if ("R.java".equals(unit.getResource().getName())) { //$NON-NLS-1$
- ResourceDeclarationVisitor visitor = new ResourceDeclarationVisitor();
- parse.accept(visitor);
- mDeclarations.addAll(visitor.getDeclarations());
- } else {
- ResourceRefCollector visitor = new ResourceRefCollector();
- parse.accept(visitor);
- List<String> refs = visitor.getRefs();
- mReferences.addAll(refs);
- }
- }
- }
- }
- } catch (CoreException e) {
- context.log(e, null);
- }
- }
-
- private IProject getProject(Context context) {
- // Look up project
- IResource file = AdtUtils.fileToResource(context.file);
- if (file != null) {
- return file.getProject();
- }
-
- return null;
- }
-
- private static class ResourceRefCollector extends ASTVisitor {
- private List<String> mRefs = new ArrayList<String>();
-
- @Override
- public boolean visit(QualifiedName node) {
- if (node.getQualifier().toString().startsWith(R_PREFIX)) {
- mRefs.add(node.getFullyQualifiedName());
- }
- return super.visit(node);
- }
-
- public List<String> getRefs() {
- return mRefs;
- }
- }
-
- private static class ResourceDeclarationVisitor extends ASTVisitor {
- private List<String> mDeclarations = new ArrayList<String>();
-
- @Override
- public boolean visit(FieldDeclaration node) {
- @SuppressWarnings("rawtypes")
- List fragments = node.fragments();
- for (int i = 0, n = fragments.size(); i < n; i++) {
- Object f = fragments.get(i);
- if (f instanceof VariableDeclarationFragment) {
- VariableDeclarationFragment fragment = (VariableDeclarationFragment) f;
- String name = fragment.getName().toString();
- if (node.getParent() instanceof TypeDeclaration) {
- TypeDeclaration parent = (TypeDeclaration) node.getParent();
- String type = parent.getName().toString();
- mDeclarations.add(R_PREFIX + type + '.' + name);
- }
- }
- }
- return super.visit(node);
- }
-
- // TODO: Check for reflection: check Strings as well?
-
- public List<String> getDeclarations() {
- return mDeclarations;
- }
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java
index e96ebc0..09eb181 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java
@@ -20,6 +20,7 @@ import com.android.tools.lint.checks.DuplicateIdDetector;
import com.android.tools.lint.checks.UnusedResourceDetector;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.IDomParser;
+import com.android.tools.lint.client.api.IJavaParser;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Issue;
@@ -224,5 +225,10 @@ public class ProjectLintConfigurationTest extends AdtProjectTest {
public String readFile(File file) {
return null;
}
+
+ @Override
+ public IJavaParser getJavaParser() {
+ return null;
+ }
}
}
diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh
index 0842342..b23a3b5 100755
--- a/eclipse/scripts/create_all_symlinks.sh
+++ b/eclipse/scripts/create_all_symlinks.sh
@@ -76,6 +76,7 @@ ADT_PREBUILTS="\
prebuilts/tools/common/asm-tools/asm-4.0.jar \
prebuilts/tools/common/asm-tools/asm-tree-4.0.jar \
prebuilts/tools/common/guava-tools/guava-10.0.1.jar \
+ prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar \
prebuilt/common/commons-compress/commons-compress-1.0.jar \
prebuilt/common/http-client/httpclient-4.1.1.jar \
prebuilt/common/http-client/httpcore-4.1.jar \
diff --git a/lint/cli/.classpath b/lint/cli/.classpath
index 362d028..ff20329 100644
--- a/lint/cli/.classpath
+++ b/lint/cli/.classpath
@@ -8,5 +8,6 @@
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-10.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/lombok-ast/src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/lint/cli/Android.mk b/lint/cli/Android.mk
index a78a9fe..acf5241 100644
--- a/lint/cli/Android.mk
+++ b/lint/cli/Android.mk
@@ -12,7 +12,8 @@ LOCAL_JAR_MANIFEST := etc/manifest.txt
LOCAL_JAVA_LIBRARIES := \
common \
lint_api \
- lint_checks
+ lint_checks \
+ lombok-ast-0.2
LOCAL_STATIC_JAVA_LIBRARIES := \
asm-tools \
asm-tree-tools \
diff --git a/lint/cli/etc/manifest.txt b/lint/cli/etc/manifest.txt
index 242c7f4..0cb09ef 100644
--- a/lint/cli/etc/manifest.txt
+++ b/lint/cli/etc/manifest.txt
@@ -1,2 +1,2 @@
Main-Class: com.android.tools.lint.Main
-Class-Path: common.jar lint_api.jar lint_checks.jar asm-4.0.jar asm-tree-4.0.jar guava-10.0.1.jar
+Class-Path: common.jar lint_api.jar lint_checks.jar asm-4.0.jar asm-tree-4.0.jar guava-10.0.1.jar lombok-ast-0.2.jar
diff --git a/lint/cli/src/com/android/tools/lint/LombokParser.java b/lint/cli/src/com/android/tools/lint/LombokParser.java
new file mode 100644
index 0000000..9002e15
--- /dev/null
+++ b/lint/cli/src/com/android/tools/lint/LombokParser.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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;
+
+import com.android.tools.lint.client.api.IJavaParser;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Location.Handle;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import java.io.File;
+import java.util.List;
+
+import lombok.ast.CompilationUnit;
+import lombok.ast.Node;
+import lombok.ast.Position;
+import lombok.ast.grammar.ParseProblem;
+import lombok.ast.grammar.Source;
+
+/**
+ * Java parser which uses the Lombok parser directly. This is a pretty slow parser
+ * (2.5 times slower than javac, which in turn is about 3 times slower than EJC for
+ * some benchmarks).
+ */
+public class LombokParser implements IJavaParser {
+
+ @Override
+ public Node parseJava(JavaContext context) {
+ try {
+ Source source = new Source(context.getContents(), context.file.getName());
+ List<Node> nodes = source.getNodes();
+
+ // Don't analyze files containing errors
+ List<ParseProblem> problems = source.getProblems();
+ if (problems != null && problems.size() > 0) {
+ for (ParseProblem problem : problems) {
+ Position position = problem.getPosition();
+ Location location = Location.create(context.file,
+ context.getContents(), position.getStart(), position.getEnd());
+ // Sanitize the message?
+ // See http://code.google.com/p/projectlombok/issues/detail?id=313
+ String message = problem.getMessage();
+ context.report(
+ IssueRegistry.PARSER_ERROR, location,
+ message,
+ null);
+
+ }
+ return null;
+ }
+
+ // There could be more than one node when there are errors; pick out the
+ // compilation unit node
+ for (Node node : nodes) {
+ if (node instanceof CompilationUnit) {
+ return node;
+ }
+ }
+ return null;
+ } catch (Throwable e) {
+ context.report(
+ IssueRegistry.PARSER_ERROR, Location.create(context.file),
+ e.getCause() != null ? e.getCause().getLocalizedMessage() :
+ e.getLocalizedMessage(),
+ null);
+
+ return null;
+ }
+ }
+
+ @Override
+ public Location getLocation(JavaContext context, lombok.ast.Node node) {
+ lombok.ast.Position position = node.getPosition();
+ return Location.create(context.file, context.getContents(),
+ position.getStart(), position.getEnd());
+ }
+
+ @Override
+ public Handle createLocationHandle(XmlContext context, Node node) {
+ return new LocationHandle(context.file, node);
+ }
+
+ @Override
+ public void dispose(JavaContext context, Node compilationUnit) {
+ }
+
+ /* Handle for creating positions cheaply and returning full fledged locations later */
+ private class LocationHandle implements Handle {
+ private File mFile;
+ private Node mNode;
+
+ public LocationHandle(File file, Node node) {
+ mFile = file;
+ mNode = node;
+ }
+
+ @Override
+ public Location resolve() {
+ Position pos = mNode.getPosition();
+ return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd());
+ }
+ }
+}
diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java
index 3069186..493b092 100644
--- a/lint/cli/src/com/android/tools/lint/Main.java
+++ b/lint/cli/src/com/android/tools/lint/Main.java
@@ -23,6 +23,7 @@ import com.android.tools.lint.checks.BuiltinIssueRegistry;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.DefaultConfiguration;
import com.android.tools.lint.client.api.IDomParser;
+import com.android.tools.lint.client.api.IJavaParser;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.Lint;
import com.android.tools.lint.client.api.LintClient;
@@ -723,6 +724,11 @@ public class Main extends LintClient {
}
@Override
+ public IJavaParser getJavaParser() {
+ return new LombokParser();
+ }
+
+ @Override
public void report(Context context, Issue issue, Location location, String message,
Object data) {
assert context.isEnabled(issue);
diff --git a/lint/libs/lint_api/.classpath b/lint/libs/lint_api/.classpath
index 742729e..e949d20 100644
--- a/lint/libs/lint_api/.classpath
+++ b/lint/libs/lint_api/.classpath
@@ -6,5 +6,6 @@
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-10.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/lombok-ast/src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/lint/libs/lint_api/Android.mk b/lint/libs/lint_api/Android.mk
index 0d3d7e9..12d38c6 100644
--- a/lint/libs/lint_api/Android.mk
+++ b/lint/libs/lint_api/Android.mk
@@ -20,7 +20,8 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_RESOURCE_DIRS := src
LOCAL_JAVA_LIBRARIES := \
- common
+ lombok-ast-0.2 \
+ common
LOCAL_STATIC_JAVA_LIBRARIES := \
asm-tools \
asm-tree-tools \
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IJavaParser.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IJavaParser.java
new file mode 100644
index 0000000..4de463b
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IJavaParser.java
@@ -0,0 +1,72 @@
+/*
+ * 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.client.api;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.XmlContext;
+
+import lombok.ast.Node;
+
+/**
+ * A wrapper for XML parser. This allows tools integrating lint to map directly
+ * to builtin services, such as already-parsed data structures in XML editors.
+ * <p/>
+ * <b>NOTE: This is not a or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public interface IJavaParser {
+ /**
+ * Parse the file pointed to by the given context.
+ *
+ * @param context the context pointing to the file to be parsed, typically
+ * via {@link Context#getContents()} but the file handle (
+ * {@link Context#file} can also be used to map to an existing
+ * editor buffer in the surrounding tool, etc)
+ * @return the compilation unit node for the file
+ */
+ Node parseJava(JavaContext context);
+
+ /**
+ * Returns a {@link Location} for the given node
+ *
+ * @param context information about the file being parsed
+ * @param node the node to create a location for
+ * @return a location for the given node
+ */
+ Location getLocation(JavaContext context, Node node);
+
+ /**
+ * Creates a light-weight handle to a location for the given node. It can be
+ * turned into a full fledged location by
+ * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
+ *
+ * @param context the context providing the node
+ * @param node the node (element or attribute) to create a location handle
+ * for
+ * @return a location handle
+ */
+ Location.Handle createLocationHandle(XmlContext context, Node node);
+
+ /**
+ * Dispose any data structures held for the given context.
+ * @param context information about the file previously parsed
+ * @param compilationUnit the compilation unit being disposed
+ */
+ void dispose(JavaContext context, Node compilationUnit);
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/JavaVisitor.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/JavaVisitor.java
new file mode 100644
index 0000000..14a0627
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/JavaVisitor.java
@@ -0,0 +1,1145 @@
+/*
+ * 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.client.api;
+
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.JavaScanner;
+import com.android.tools.lint.detector.api.Detector.XmlScanner;
+import com.android.tools.lint.detector.api.JavaContext;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import lombok.ast.AlternateConstructorInvocation;
+import lombok.ast.Annotation;
+import lombok.ast.AnnotationDeclaration;
+import lombok.ast.AnnotationElement;
+import lombok.ast.AnnotationMethodDeclaration;
+import lombok.ast.AnnotationValueArray;
+import lombok.ast.ArrayAccess;
+import lombok.ast.ArrayCreation;
+import lombok.ast.ArrayDimension;
+import lombok.ast.ArrayInitializer;
+import lombok.ast.Assert;
+import lombok.ast.AstVisitor;
+import lombok.ast.BinaryExpression;
+import lombok.ast.Block;
+import lombok.ast.BooleanLiteral;
+import lombok.ast.Break;
+import lombok.ast.Case;
+import lombok.ast.Cast;
+import lombok.ast.Catch;
+import lombok.ast.CharLiteral;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ClassLiteral;
+import lombok.ast.Comment;
+import lombok.ast.CompilationUnit;
+import lombok.ast.ConstructorDeclaration;
+import lombok.ast.ConstructorInvocation;
+import lombok.ast.Continue;
+import lombok.ast.Default;
+import lombok.ast.DoWhile;
+import lombok.ast.EmptyDeclaration;
+import lombok.ast.EmptyStatement;
+import lombok.ast.EnumConstant;
+import lombok.ast.EnumDeclaration;
+import lombok.ast.EnumTypeBody;
+import lombok.ast.ExpressionStatement;
+import lombok.ast.FloatingPointLiteral;
+import lombok.ast.For;
+import lombok.ast.ForEach;
+import lombok.ast.Identifier;
+import lombok.ast.If;
+import lombok.ast.ImportDeclaration;
+import lombok.ast.InlineIfExpression;
+import lombok.ast.InstanceInitializer;
+import lombok.ast.InstanceOf;
+import lombok.ast.IntegralLiteral;
+import lombok.ast.InterfaceDeclaration;
+import lombok.ast.KeywordModifier;
+import lombok.ast.LabelledStatement;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Modifiers;
+import lombok.ast.Node;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.NullLiteral;
+import lombok.ast.PackageDeclaration;
+import lombok.ast.Return;
+import lombok.ast.Select;
+import lombok.ast.StaticInitializer;
+import lombok.ast.StringLiteral;
+import lombok.ast.Super;
+import lombok.ast.SuperConstructorInvocation;
+import lombok.ast.Switch;
+import lombok.ast.Synchronized;
+import lombok.ast.This;
+import lombok.ast.Throw;
+import lombok.ast.Try;
+import lombok.ast.TypeReference;
+import lombok.ast.TypeReferencePart;
+import lombok.ast.TypeVariable;
+import lombok.ast.UnaryExpression;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+import lombok.ast.VariableReference;
+import lombok.ast.While;
+
+
+/**
+ * Specialized visitor for running detectors on a Java AST.
+ * It operates in three phases:
+ * <ol>
+ * <li> First, it computes a set of maps where it generates a map from each
+ * significant AST attribute (such as method call names) to a list
+ * of detectors to consult whenever that attribute is encountered.
+ * Examples of "attributes" are method names, Android resource identifiers,
+ * and general AST node types such as "cast" nodes etc. These are
+ * defined on the {@link JavaScanner} interface.
+ * <li> Second, it iterates over the document a single time, delegating to
+ * the detectors found at each relevant AST attribute.
+ * <li> Finally, it calls the remaining visitors (those that need to process a
+ * whole document on their own).
+ * </ol>
+ * It also notifies all the detectors before and after the document is processed
+ * such that they can do pre- and post-processing.
+ */
+public class JavaVisitor {
+ /** Default size of lists holding detectors of the same type for a given node type */
+ private static final int SAME_TYPE_COUNT = 8;
+
+ private final Map<String, List<VisitingDetector>> mMethodDetectors =
+ new HashMap<String, List<VisitingDetector>>();
+ private final List<VisitingDetector> mResourceFieldDetectors =
+ new ArrayList<VisitingDetector>();
+ private final List<VisitingDetector> mAllDetectors;
+ private final List<VisitingDetector> mFullTreeDetectors;
+ private Map<Class<? extends Node>, List<VisitingDetector>> mNodeTypeDetectors =
+ new HashMap<Class<? extends Node>, List<VisitingDetector>>();
+ private final IJavaParser mParser;
+
+ JavaVisitor(IJavaParser parser, List<Detector> detectors) {
+ mParser = parser;
+ mAllDetectors = new ArrayList<VisitingDetector>(detectors.size());
+ mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size());
+
+ for (Detector detector : detectors) {
+ VisitingDetector v = new VisitingDetector(detector, (JavaScanner) detector);
+ mAllDetectors.add(v);
+
+ List<Class<? extends Node>> nodeTypes = detector.getApplicableNodeTypes();
+ if (nodeTypes != null) {
+ for (Class<? extends Node> type : nodeTypes) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(type);
+ if (list == null) {
+ list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
+ mNodeTypeDetectors.put(type, list);
+ }
+ list.add(v);
+ }
+ }
+
+ List<String> names = detector.getApplicableMethodNames();
+ if (names != null) {
+ // not supported in Java visitors; adding a method invocation node is trivial
+ // for that case.
+ assert names != XmlScanner.ALL;
+
+ for (String name : names) {
+ List<VisitingDetector> list = mMethodDetectors.get(name);
+ if (list == null) {
+ list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
+ mMethodDetectors.put(name, list);
+ }
+ list.add(v);
+ }
+ }
+
+ if (detector.appliesToResourceRefs()) {
+ mResourceFieldDetectors.add(v);
+ } else if ((names == null || names.size() == 0)
+ && (nodeTypes == null || nodeTypes.size() ==0)) {
+ mFullTreeDetectors.add(v);
+ }
+ }
+ }
+
+ void visitFile(JavaContext context, File file) {
+ context.parser = mParser;
+
+ Node compilationUnit = null;
+ try {
+ compilationUnit = mParser.parseJava(context);
+ if (compilationUnit == null) {
+ // No need to log this; the parser should be reporting
+ // a full warning (such as IssueRegistry#PARSER_ERROR)
+ // with details, location, etc.
+ return;
+ }
+
+ for (VisitingDetector v : mAllDetectors) {
+ v.setContext(context);
+ v.getDetector().beforeCheckFile(context);
+ }
+
+ for (VisitingDetector v : mFullTreeDetectors) {
+ AstVisitor visitor = v.getVisitor();
+ if (visitor != null) {
+ compilationUnit.accept(visitor);
+ } else {
+ assert false : v.getDetector().getClass().getName();
+ }
+ }
+
+ if (mMethodDetectors.size() > 0 || mResourceFieldDetectors.size() > 0) {
+ AstVisitor visitor = new DelegatingJavaVisitor(context);
+ compilationUnit.accept(visitor);
+ } else if (mNodeTypeDetectors.size() > 0) {
+ AstVisitor visitor = new DispatchVisitor();
+ compilationUnit.accept(visitor);
+ }
+
+ for (VisitingDetector v : mAllDetectors) {
+ v.getDetector().afterCheckFile(context);
+ }
+ } finally {
+ if (compilationUnit != null) {
+ mParser.dispose(context, compilationUnit);
+ }
+ }
+ }
+
+ private static class VisitingDetector {
+ private AstVisitor mVisitor; // construct lazily, and clear out on context switch!
+ private JavaContext mContext;
+ public final Detector mDetector;
+ public final JavaScanner mJavaScanner;
+
+ public VisitingDetector(Detector detector, JavaScanner javaScanner) {
+ mDetector = detector;
+ mJavaScanner = javaScanner;
+ }
+
+ public Detector getDetector() {
+ return mDetector;
+ }
+
+ public JavaScanner getJavaScanner() {
+ return mJavaScanner;
+ }
+
+ public void setContext(JavaContext context) {
+ mContext = context;
+
+ // The visitors are one-per-context, so clear them out here and construct
+ // lazily only if needed
+ mVisitor = null;
+ }
+
+ AstVisitor getVisitor() {
+ if (mVisitor == null) {
+ mVisitor = mDetector.createJavaVisitor(mContext);
+ }
+ return mVisitor;
+ }
+ }
+
+ /**
+ * Generic dispatcher which visits all nodes (once) and dispatches to
+ * specific visitors for each node. Each visitor typically only wants to
+ * look at a small part of a tree, such as a method call or a class
+ * declaration, so this means we avoid visiting all "uninteresting" nodes in
+ * the tree repeatedly.
+ */
+ private class DispatchVisitor extends AstVisitor {
+ @Override
+ public void endVisit(Node node) {
+ }
+
+ @Override
+ public boolean visitAlternateConstructorInvocation(AlternateConstructorInvocation node) {
+ List<VisitingDetector> list =
+ mNodeTypeDetectors.get(AlternateConstructorInvocation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAlternateConstructorInvocation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotation(Annotation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Annotation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotationDeclaration(AnnotationDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotationDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotationElement(AnnotationElement node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationElement.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotationElement(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotationMethodDeclaration(AnnotationMethodDeclaration node) {
+ List<VisitingDetector> list =
+ mNodeTypeDetectors.get(AnnotationMethodDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotationMethodDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAnnotationValueArray(AnnotationValueArray node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationValueArray.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAnnotationValueArray(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitArrayAccess(ArrayAccess node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayAccess.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitArrayAccess(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitArrayCreation(ArrayCreation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayCreation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitArrayCreation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitArrayDimension(ArrayDimension node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayDimension.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitArrayDimension(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitArrayInitializer(ArrayInitializer node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayInitializer.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitArrayInitializer(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitAssert(Assert node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Assert.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitAssert(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitBinaryExpression(BinaryExpression node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(BinaryExpression.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitBinaryExpression(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitBlock(Block node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Block.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitBlock(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitBooleanLiteral(BooleanLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(BooleanLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitBooleanLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitBreak(Break node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Break.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitBreak(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCase(Case node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Case.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCase(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCast(Cast node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Cast.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCast(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCatch(Catch node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Catch.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCatch(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCharLiteral(CharLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(CharLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCharLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ClassDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitClassDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitClassLiteral(ClassLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ClassLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitClassLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitComment(Comment node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Comment.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitComment(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitCompilationUnit(CompilationUnit node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(CompilationUnit.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitCompilationUnit(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitConstructorDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitConstructorInvocation(ConstructorInvocation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorInvocation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitConstructorInvocation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitContinue(Continue node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Continue.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitContinue(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitDefault(Default node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Default.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitDefault(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitDoWhile(DoWhile node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(DoWhile.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitDoWhile(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEmptyDeclaration(EmptyDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEmptyDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEmptyStatement(EmptyStatement node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyStatement.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEmptyStatement(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEnumConstant(EnumConstant node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EnumConstant.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEnumConstant(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEnumDeclaration(EnumDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EnumDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEnumDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitEnumTypeBody(EnumTypeBody node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(EnumTypeBody.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitEnumTypeBody(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitExpressionStatement(ExpressionStatement node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ExpressionStatement.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitExpressionStatement(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitFloatingPointLiteral(FloatingPointLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(FloatingPointLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitFloatingPointLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitFor(For node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(For.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitFor(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitForEach(ForEach node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ForEach.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitForEach(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitIdentifier(Identifier node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Identifier.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitIdentifier(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitIf(If node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(If.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitIf(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitImportDeclaration(ImportDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(ImportDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitImportDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitInlineIfExpression(InlineIfExpression node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(InlineIfExpression.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitInlineIfExpression(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitInstanceInitializer(InstanceInitializer node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceInitializer.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitInstanceInitializer(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitInstanceOf(InstanceOf node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceOf.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitInstanceOf(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitIntegralLiteral(IntegralLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(IntegralLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitIntegralLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitInterfaceDeclaration(InterfaceDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(InterfaceDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitInterfaceDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitKeywordModifier(KeywordModifier node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(KeywordModifier.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitKeywordModifier(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitLabelledStatement(LabelledStatement node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(LabelledStatement.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitLabelledStatement(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(MethodDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitMethodDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(MethodInvocation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitMethodInvocation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitModifiers(Modifiers node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Modifiers.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitModifiers(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitNormalTypeBody(NormalTypeBody node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(NormalTypeBody.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitNormalTypeBody(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitNullLiteral(NullLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(NullLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitNullLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitPackageDeclaration(PackageDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(PackageDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitPackageDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitParseArtefact(Node node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Node.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitParseArtefact(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitReturn(Return node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Return.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitReturn(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSelect(Select node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Select.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSelect(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitStaticInitializer(StaticInitializer node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(StaticInitializer.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitStaticInitializer(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitStringLiteral(StringLiteral node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(StringLiteral.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitStringLiteral(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSuper(Super node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Super.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSuper(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSuperConstructorInvocation(SuperConstructorInvocation node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(SuperConstructorInvocation.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSuperConstructorInvocation(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSwitch(Switch node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Switch.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSwitch(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitSynchronized(Synchronized node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Synchronized.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitSynchronized(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitThis(This node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(This.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitThis(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitThrow(Throw node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Throw.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitThrow(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitTry(Try node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(Try.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitTry(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitTypeReference(TypeReference node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReference.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitTypeReference(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitTypeReferencePart(TypeReferencePart node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReferencePart.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitTypeReferencePart(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitTypeVariable(TypeVariable node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(TypeVariable.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitTypeVariable(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitUnaryExpression(UnaryExpression node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(UnaryExpression.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitUnaryExpression(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitVariableDeclaration(VariableDeclaration node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDeclaration.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitVariableDeclaration(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitVariableDefinition(VariableDefinition node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinition.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitVariableDefinition(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinitionEntry.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitVariableDefinitionEntry(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitVariableReference(VariableReference node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(VariableReference.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitVariableReference(node);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitWhile(While node) {
+ List<VisitingDetector> list = mNodeTypeDetectors.get(While.class);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getVisitor().visitWhile(node);
+ }
+ }
+ return false;
+ }
+ }
+
+ /** Performs common AST searches for method calls and R-type-field references.
+ * Note that this is a specialized form of the {@link DispatchVisitor}. */
+ private class DelegatingJavaVisitor extends DispatchVisitor {
+ private final JavaContext mContext;
+ private final boolean mVisitResources;
+ private final boolean mVisitMethods;
+
+ public DelegatingJavaVisitor(JavaContext context) {
+ mContext = context;
+
+ mVisitMethods = mMethodDetectors.size() > 0;
+ mVisitResources = mResourceFieldDetectors.size() > 0;
+ }
+
+ @Override
+ public boolean visitVariableReference(VariableReference node) {
+ if (mVisitResources) {
+ if (node.astIdentifier().getDescription().equals("R") && //$NON-NLS-1$
+ node.getParent() instanceof Select &&
+ node.getParent().getParent() instanceof Select) {
+ for (VisitingDetector v : mResourceFieldDetectors) {
+ Select parentSelect = (Select) node.getParent();
+ Select grandParentSelect = (Select) parentSelect.getParent();
+ String type = parentSelect.astIdentifier().astValue();
+ String name = grandParentSelect.astIdentifier().astValue();
+
+ JavaScanner detector = v.getJavaScanner();
+ detector.visitResourceReference(mContext, v.getVisitor(), node,
+ type, name);
+ }
+ }
+ }
+
+ return super.visitVariableReference(node);
+ }
+
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (mVisitMethods) {
+ String methodName = node.astName().getDescription();
+ List<VisitingDetector> list = mMethodDetectors.get(methodName);
+ if (list != null) {
+ for (VisitingDetector v : list) {
+ v.getJavaScanner().visitMethod(mContext, v.getVisitor(), node);
+ }
+ }
+ }
+
+ return super.visitMethodInvocation(node);
+ }
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
index 1bb6fe3..ac28b47 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
@@ -26,6 +26,7 @@ import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Project;
@@ -573,14 +574,41 @@ public class Lint {
private void checkJava(Project project, Project main,
List<File> sourceFolders, List<Detector> checks) {
- Context context = new Context(mClient, project, main, project.getDir(), mScope);
- fireEvent(EventType.SCANNING_FILE, context);
+ IJavaParser javaParser = mClient.getJavaParser();
+ if (javaParser == null) {
+ mClient.log(null, "No java parser provided to lint: not running Java checks");
+ return;
+ }
- for (Detector detector : checks) {
- ((Detector.JavaScanner) detector).checkJavaSources(context, sourceFolders);
+ assert checks.size() > 0;
- if (mCanceled) {
- return;
+ // Gather all Java source files in a single pass; more efficient.
+ List<File> sources = new ArrayList<File>(100);
+ for (File folder : sourceFolders) {
+ gatherJavaFiles(folder, sources);
+ }
+ if (sources.size() > 0) {
+ JavaVisitor visitor = new JavaVisitor(javaParser, checks);
+ for (File file : sources) {
+ JavaContext context = new JavaContext(mClient, project, main, file, mScope);
+ fireEvent(EventType.SCANNING_FILE, context);
+ visitor.visitFile(context, file);
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ }
+
+ private void gatherJavaFiles(File dir, List<File> result) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile() && file.getName().endsWith(".java")) { //$NON-NLS-1$
+ result.add(file);
+ } else if (file.isDirectory()) {
+ gatherJavaFiles(file, result);
+ }
}
}
}
@@ -820,5 +848,10 @@ public class Lint {
public Project getProject(File dir, File referenceDir) {
return mDelegate.getProject(dir, referenceDir);
}
+
+ @Override
+ public IJavaParser getJavaParser() {
+ return mDelegate.getJavaParser();
+ }
}
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
index 282ec48..10d21ed 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
@@ -100,6 +100,13 @@ public abstract class LintClient {
public abstract IDomParser getDomParser();
/**
+ * Returns a {@link IJavaParser} to use to parse Java
+ *
+ * @return a new {@link IJavaParser}
+ */
+ public abstract IJavaParser getJavaParser();
+
+ /**
* Returns an optimal detector, if applicable. By default, just returns the
* original detector, but tools can replace detectors using this hook with a version
* that takes advantage of native capabilities of the tool.
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
index c41a2b6..674b71a 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
@@ -28,6 +28,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import lombok.ast.AstVisitor;
+import lombok.ast.MethodInvocation;
+import lombok.ast.VariableReference;
+
/**
* A detector is able to find a particular problem. It might also be thought of as enforcing
* a rule, but "rule" is a bit overloaded in ADT terminology since ViewRules are used in
@@ -48,9 +52,116 @@ import java.util.List;
public abstract class Detector {
/** Specialized interface for detectors that scan Java source file parse trees */
public interface JavaScanner {
- // TODO: Java scanning support not yet implemented. This
- // is a placeholder.
- void checkJavaSources(Context context, List<File> sourceFolders);
+ /**
+ * Create a parse tree visitor to process the parse tree. All
+ * {@link JavaScanner} detectors must provide a visitor, unless they
+ * either return true from {@link #appliesToResourceRefs()} or return
+ * non null from {@link #getApplicableMethodNames()}.
+ * <p>
+ * If you return specific AST node types from
+ * {@link #getApplicableNodeTypes()}, then the visitor will <b>only</b>
+ * be called for the specific requested node types. This is more
+ * efficient, since it allows many detectors that apply to only a small
+ * part of the AST (such as method call nodes) to share iteration of the
+ * majority of the parse tree.
+ * <p>
+ * If you return null from {@link #getApplicableNodeTypes()}, then your
+ * visitor will be called from the top and all node types visited.
+ * <p>
+ * Note that a new visitor is created for each separate compilation
+ * unit, so you can store per file state in the visitor.
+ *
+ * @param context the {@link Context} for the file being analyzed
+ * @return a visitor, or null.
+ */
+ AstVisitor createJavaVisitor(JavaContext context);
+
+ /**
+ * Return the types of AST nodes that the visitor returned from
+ * {@link #createJavaVisitor(JavaContext)} should visit. See the
+ * documentation for {@link #createJavaVisitor(JavaContext)} for details
+ * on how the shared visitor is used.
+ * <p>
+ * If you return null from this method, then the visitor will process
+ * the full tree instead.
+ * <p>
+ * Note that for the shared visitor, the return codes from the visit
+ * methods are ignored: returning true will <b>not</b> prune iteration
+ * of the subtree, since there may be other node types interested in the
+ * children. If you need to ensure that your visitor only processes a
+ * part of the tree, use a full visitor instead. See the
+ * OverdrawDetector implementation for an example of this.
+ *
+ * @return the list of applicable node types (AST node classes), or null
+ */
+ List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes();
+
+ /**
+ * Return the list of method names this detector is interested in, or
+ * null. If this method returns non-null, then any AST nodes that match
+ * a method call in the list will be passed to the
+ * {@link #visitMethod(JavaContext, AstVisitor, MethodInvocation)}
+ * method for processing. The visitor created by
+ * {@link #createJavaVisitor(JavaContext)} is also passed to that
+ * method, although it can be null.
+ * <p>
+ * This makes it easy to write detectors that focus on some fixed calls.
+ * For example, the StringFormatDetector uses this mechanism to look for
+ * "format" calls, and when found it looks around (using the AST's
+ * {@link lombok.ast.Node#getParent()} method) to see if it's called on
+ * a String class instance, and if so do its normal processing. Note
+ * that since it doesn't need to do any other AST processing, that
+ * detector does not actually supply a visitor.
+ *
+ * @return a set of applicable method names, or null.
+ */
+ List<String> getApplicableMethodNames();
+
+ /**
+ * Method invoked for any method calls found that matches any names
+ * returned by {@link #getApplicableMethodNames()}. This also passes
+ * back the visitor that was created by
+ * {@link #createJavaVisitor(JavaContext)}, but a visitor is not
+ * required. It is intended for detectors that need to do additional AST
+ * processing, but also want the convenience of not having to look for
+ * method names on their own.
+ *
+ * @param context the context of the lint request
+ * @param visitor the visitor created from
+ * {@link #createJavaVisitor(JavaContext)}, or null
+ * @param node the {@link MethodInvocation} node for the invoked method
+ */
+ void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node);
+
+ /**
+ * Returns whether this detector cares about Android resource references
+ * (such as {@code R.layout.main} or {@code R.string.app_name}). If it
+ * does, then the visitor will look for these patterns, and if found, it
+ * will invoke {@link #visitResourceReference} passing the resource type
+ * and resource name. It also passes the visitor, if any, that was
+ * created by {@link #createJavaVisitor(JavaContext)}, such that a
+ * detector can do more than just look for resources.
+ *
+ * @return true if this detector wants to be notified of R resource
+ * identifiers found in the code.
+ */
+ boolean appliesToResourceRefs();
+
+ /**
+ * Called for any resource references (such as {@code R.layout.main}
+ * found in Java code, provided this detector returned {@code true} from
+ * {@link #appliesToResourceRefs()}.
+ *
+ * @param context the lint scanning context
+ * @param visitor the visitor created from
+ * {@link #createJavaVisitor(JavaContext)}, or null
+ * @param node the variable reference for the resource
+ * @param type the resource type, such as "layout" or "string"
+ * @param name the resource name, such as "main" from
+ * {@code R.layout.main}
+ */
+ void visitResourceReference(JavaContext context, AstVisitor visitor,
+ VariableReference node, String type, String name);
}
/** Specialized interface for detectors that scan Java class files */
@@ -260,7 +371,32 @@ public abstract class Detector {
// ---- Dummy implementations to make implementing JavaScanner easier: ----
@SuppressWarnings("javadoc")
- public void checkJavaSources(Context context, List<File> sourceFolders) {
+ public List<String> getApplicableMethodNames() {
+ return null;
+ }
+
+ @SuppressWarnings("javadoc")
+ public AstVisitor createJavaVisitor(JavaContext context) {
+ return null;
+ }
+
+ @SuppressWarnings("javadoc")
+ public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
+ return null;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) {
+ }
+
+ @SuppressWarnings("javadoc")
+ public boolean appliesToResourceRefs() {
+ return false;
+ }
+
+ @SuppressWarnings("javadoc")
+ public void visitResourceReference(JavaContext context, AstVisitor visitor,
+ VariableReference node, String type, String name) {
}
// ---- Dummy implementations to make implementing a ClassScanner easier: ----
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/JavaContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/JavaContext.java
new file mode 100644
index 0000000..2d360d2
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/JavaContext.java
@@ -0,0 +1,71 @@
+/*
+ * 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.detector.api;
+
+import com.android.tools.lint.client.api.IJavaParser;
+import com.android.tools.lint.client.api.LintClient;
+
+import java.io.File;
+import java.util.EnumSet;
+
+import lombok.ast.Node;
+
+/**
+ * A {@link Context} used when checking Java files.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public class JavaContext extends Context {
+ /** The parse tree */
+ public Node compilationUnit;
+ /** The parser which produced the parse tree */
+ public IJavaParser parser;
+
+ /**
+ * Constructs a {@link JavaContext} for running lint on the given file, with
+ * the given scope, in the given project reporting errors to the given
+ * client.
+ *
+ * @param client the client to report errors to
+ * @param project the project to run lint on which contains the given file
+ * @param main the main project if this project is a library project, or
+ * null if this is not a library project. The main project is
+ * the root project of all library projects, not necessarily the
+ * directly including project.
+ * @param file the file to be analyzed
+ * @param scope the scope used for analysis
+ */
+ public JavaContext(LintClient client, Project project, Project main, File file,
+ EnumSet<Scope> scope) {
+ super(client, project, main, file, scope);
+ }
+
+ /**
+ * Returns a location for the given node
+ *
+ * @param node the AST node to get a location for
+ * @return a location for the given node
+ */
+ public Location getLocation(Node node) {
+ if (parser != null) {
+ return parser.getLocation(this, node);
+ }
+
+ return new Location(file, null, null);
+ }
+}
diff --git a/lint/libs/lint_checks/.classpath b/lint/libs/lint_checks/.classpath
index 7ccda94..848a8aa 100644
--- a/lint/libs/lint_checks/.classpath
+++ b/lint/libs/lint_checks/.classpath
@@ -8,5 +8,6 @@
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-10.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/lombok-ast/src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/lint/libs/lint_checks/Android.mk b/lint/libs/lint_checks/Android.mk
index 9038199..d55e8b3 100644
--- a/lint/libs/lint_checks/Android.mk
+++ b/lint/libs/lint_checks/Android.mk
@@ -11,8 +11,9 @@ LOCAL_JAR_MANIFEST := etc/manifest.txt
# If the dependency list is changed, etc/manifest.txt
LOCAL_JAVA_LIBRARIES := \
- common \
- androidprefs \
+ common \
+ lombok-ast-0.2 \
+ androidprefs \
lint_api
LOCAL_STATIC_JAVA_LIBRARIES := \
asm-tools \
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java
index 447653e..bad3c42 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java
@@ -16,7 +16,6 @@
package com.android.tools.lint.checks;
-import static com.android.tools.lint.detector.api.LintConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI;
import static com.android.tools.lint.detector.api.LintConstants.ATTR_BACKGROUND;
import static com.android.tools.lint.detector.api.LintConstants.ATTR_NAME;
@@ -41,6 +40,7 @@ import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.LayoutDetector;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
@@ -68,12 +68,21 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import lombok.ast.AstVisitor;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.CompilationUnit;
+import lombok.ast.Expression;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Select;
+import lombok.ast.StrictListAccessor;
+import lombok.ast.VariableReference;
+
/**
* Check which looks for overdraw problems where view areas are painted and then
* painted over, meaning that the bottom paint operation is a waste of time.
*/
public class OverdrawDetector extends LayoutDetector implements Detector.JavaScanner {
- private static final String R_LAYOUT_PREFIX = "R.layout."; //$NON-NLS-1$
private static final String R_STYLE_PREFIX = "R.style."; //$NON-NLS-1$
private static final String SET_THEME = "setTheme"; //$NON-NLS-1$
@@ -438,121 +447,92 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca
// ---- Implements JavaScanner ----
@Override
- public void checkJavaSources(Context context, List<File> sourceFolders) {
- if (mActivities == null) {
- return;
- }
+ public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
+ // This detector does not specify specific node types; this means
+ // that the infrastructure will run the full visitor on the compilation
+ // unit rather than on individual nodes. This is important since this
+ // detector relies on pruning (if it gets to a class declaration that is
+ // not an activity, it skips everything inside).
+ return null;
+ }
- // For right now, this is hacked via String scanning in .java files instead.
- for (File dir : sourceFolders) {
- scanJavaFile(context, dir, null);
- }
+ @Override
+ public AstVisitor createJavaVisitor(JavaContext context) {
+ return new OverdrawVisitor();
}
- // TODO: Use a proper Java AST... Not only does this rely on string pattern
- // matching, it also does not track inheritance so if you inherit code from another
- // activity (such as setTheme) calls those won't be reflected in all the children...
- private void scanJavaFile(Context context, File file, String pkg) {
- String fileName = file.getName();
- if (fileName.endsWith(DOT_JAVA) && file.exists()) {
- String clz = fileName.substring(0, fileName.length() - DOT_JAVA.length());
- String fqn = pkg + '.' + clz;
-
- if (mActivities.contains(fqn) || fqn.endsWith("Activity")) { //$NON-NLS-1$
- String code = context.getClient().readFile(file);
- scanLayoutReferences(code, fqn);
- scanThemeReferences(code, fqn);
- }
- } else if (file.isDirectory()) {
- File[] children = file.listFiles();
- if (children != null) {
- String subPackage;
- if (pkg == null) {
- subPackage = "";
- } else if (pkg.length() == 0) {
- subPackage = file.getName();
- } else {
- subPackage = pkg + '.' + file.getName();
+ private class OverdrawVisitor extends ForwardingAstVisitor {
+ private static final String ACTIVITY = "Activity"; //$NON-NLS-1$
+ private String mClassFqn;
+
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ String name = node.getDescription();
+
+ if (mActivities != null && mActivities.contains(mClassFqn) || name.endsWith(ACTIVITY)
+ || node.astExtending() != null &&
+ node.astExtending().getDescription().endsWith(ACTIVITY)) {
+ String packageName = "";
+ if (node.getParent() instanceof CompilationUnit) {
+ CompilationUnit compilationUnit = (CompilationUnit) node.getParent();
+ packageName = compilationUnit.astPackageDeclaration().getPackageName();
}
- for (File child : children) {
- scanJavaFile(context, child, subPackage);
- }
- }
- }
- }
+ mClassFqn = (packageName.length() > 0 ? (packageName + '.') : "") + name;
- /** Look for setTheme references in this file and if found store activity-to-theme mapping */
- private void scanThemeReferences(String code, String fqn) {
- int index = 0;
- int length = code.length();
- // Search for R.layout references based on simple string patterns.
- // This needs to be replaced with a proper AST search as soon as we
- // have AST support in lint.
- while (index < length) {
- index = code.indexOf(SET_THEME, index);
- if (index == -1) {
- break;
+ return false;
}
- index += SET_THEME.length();
- index = code.indexOf(R_STYLE_PREFIX, index);
- if (index == -1) {
- break;
- }
- int styleStart = index;
- index += R_STYLE_PREFIX.length();
+ return true; // Done: No need to look inside this class
+ }
- int start = index;
- while (index < length && Character.isJavaIdentifierPart(code.charAt(index))) {
- index++;
- }
- String style = code.substring(start, index);
-
- String resource;
- String androidPkgPrefix = "android."; //$NON-NLS-1$
- if (styleStart > androidPkgPrefix.length() &&
- code.regionMatches(styleStart - androidPkgPrefix.length(),
- androidPkgPrefix, 0, androidPkgPrefix.length())) {
- resource = ANDROID_STYLE_RESOURCE_PREFIX + style;
- } else {
- resource = STYLE_RESOURCE_PREFIX + style;
- }
- if (mActivityToTheme == null) {
- mActivityToTheme = new HashMap<String, String>();
+ // Store R.layout references in activity classes in a map mapping back layouts
+ // to activities
+ @Override
+ public boolean visitSelect(Select node) {
+ if (node.astIdentifier().astValue().equals("layout") //$NON-NLS-1$
+ && node.astOperand() instanceof VariableReference
+ && ((VariableReference) node.astOperand()).astIdentifier().astValue()
+ .equals("R") //$NON-NLS-1$
+ && node.getParent() instanceof Select) {
+ String layout = ((Select) node.getParent()).astIdentifier().astValue();
+ if (mLayoutToActivity == null) {
+ mLayoutToActivity = new HashMap<String, List<String>>();
+ }
+ List<String> list = mLayoutToActivity.get(layout);
+ if (list == null) {
+ list = new ArrayList<String>();
+ mLayoutToActivity.put(layout, list);
+ }
+ list.add(mClassFqn);
}
- mActivityToTheme.put(fqn, resource);
+
+ return false;
}
- }
- /** Look for layout references in this file and if found store layout-to-activity mapping */
- private void scanLayoutReferences(String code, String fqn) {
- int index = 0;
- int length = code.length();
- // Search for R.layout references based on simple string patterns.
- // This needs to be replaced with a proper AST search as soon as we
- // have AST support in lint.
- while (index < length) {
- index = code.indexOf(R_LAYOUT_PREFIX, index);
- if (index == -1) {
- break;
- }
- index += R_LAYOUT_PREFIX.length();
- int start = index;
- while (index < length && Character.isJavaIdentifierPart(code.charAt(index))) {
- index++;
+ // Look for setTheme(R.style.whatever) and register as a theme registration
+ // for the current activity
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (node.astName().astValue().equals(SET_THEME)) {
+ // Look at argument
+ StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
+ if (args.size() == 1) {
+ Expression arg = args.first();
+ if (arg instanceof Select) {
+ String resource = arg.toString();
+ if (resource.startsWith(R_STYLE_PREFIX)) {
+ if (mActivityToTheme == null) {
+ mActivityToTheme = new HashMap<String, String>();
+ }
+ String name = ((Select) arg).astIdentifier().astValue();
+ mActivityToTheme.put(mClassFqn, STYLE_RESOURCE_PREFIX + name);
+ }
+ }
+ }
}
- String layout = code.substring(start, index);
- if (mLayoutToActivity == null) {
- mLayoutToActivity = new HashMap<String, List<String>>();
- }
- List<String> list = mLayoutToActivity.get(layout);
- if (list == null) {
- list = new ArrayList<String>();
- mLayoutToActivity.put(layout, list);
- }
- list.add(fqn);
+ return false;
}
}
}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java
index 86f0118..35e11d8 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java
@@ -37,6 +37,7 @@ import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Location.Handle;
@@ -63,6 +64,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import lombok.ast.AstVisitor;
+import lombok.ast.ClassDeclaration;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.NormalTypeBody;
+import lombok.ast.Select;
+import lombok.ast.VariableDeclaration;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableReference;
+
/**
* Finds unused resources.
* <p>
@@ -144,177 +154,6 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec
// ---- Implements JavaScanner ----
@Override
- public void checkJavaSources(Context context, List<File> sourceFolders) {
- // For right now, this is hacked via String scanning in .java files instead.
- for (File dir : sourceFolders) {
- scanJavaFile(context, dir);
- }
- }
-
- // TODO: Use a proper Java AST...
- private void scanJavaFile(Context context, File file) {
- String fileName = file.getName();
- if (fileName.endsWith(DOT_JAVA) && file.exists()) {
- if (fileName.equals("R.java")) { //$NON-NLS-1$
- addJavaDeclarations(context, file);
- } else {
- addJavaReferences(context, file);
- }
- } else if (file.isDirectory()) {
- File[] children = file.listFiles();
- if (children != null) {
- for (File child : children) {
- scanJavaFile(context, child);
- }
- }
- }
- }
-
- private static final String CLASS_DECLARATION = "public static final class "; //$NON-NLS-1$
- private static final String FIELD_CONST_DECLARATION = "public static final int "; //$NON-NLS-1$
- private static final String FIELD_DECLARATION = "public static int "; //$NON-NLS-1$
-
- private void addJavaDeclarations(Context context, File file) {
- // mDeclarations
- String s = context.getClient().readFile(file);
- String[] lines = s.split("\n"); //$NON-NLS-1$
- String currentType = null;
- for (int i = 0; i < lines.length; i++) {
- String line = lines[i];
- for (int j = 0; j < line.length(); j++) {
- char c = line.charAt(j);
- if (!Character.isWhitespace(c)) {
- // Found beginning of line
- boolean startsWithConstField = line.startsWith(FIELD_CONST_DECLARATION, j);
- boolean startsWithField = line.startsWith(FIELD_DECLARATION, j);
- if (startsWithConstField || startsWithField) {
- // Field (constant
- int nameBegin = j + (startsWithField
- ? FIELD_DECLARATION.length() : FIELD_CONST_DECLARATION.length());
- int nameEnd = line.indexOf('=', nameBegin);
- assert currentType != null;
- if (nameEnd != -1 && currentType != null) {
- String name = line.substring(nameBegin, nameEnd);
- String r = R_PREFIX + currentType + '.' + name;
- mDeclarations.add(r);
- }
- } else if (line.startsWith(CLASS_DECLARATION, j)) {
- // New class
- int typeBegin = j + CLASS_DECLARATION.length();
- int typeEnd = line.indexOf(' ', typeBegin);
- if (typeEnd != -1) {
- currentType = line.substring(typeBegin, typeEnd);
- }
- }
- }
- }
- }
- }
-
- /** Adds the resource identifiers found in the given file into the given set */
- private void addJavaReferences(Context context, File file) {
- String s = context.getClient().readFile(file);
- if (s == null || s.length() <= 2) {
- return;
- }
-
- // Scan looking for R.{type}.name identifiers
- // Extremely simple state machine which just avoids comments, line comments
- // and strings, and outside of that records any R. identifiers it finds
- int index = 0;
- int length = s.length();
-
- char c = s.charAt(0);
- char next = s.charAt(1);
- for (; index < length; index++) {
- c = s.charAt(index);
- if (index == length - 1) {
- break;
- }
- next = s.charAt(index + 1);
- if (Character.isWhitespace(c)) {
- continue;
- }
- if (c == '/') {
- if (next == '*') {
- // Block comment
- while (index < length - 2) {
- if (s.charAt(index) == '*' && s.charAt(index + 1) == '/') {
- break;
- }
- index++;
- }
- index++;
- } else if (next == '/') {
- // Line comment
- while (index < length && s.charAt(index) != '\n') {
- index++;
- }
- }
- } else if (c == '\'') {
- // Character
- if (next == '\\') {
- // Skip '\c'
- index += 2;
- } else {
- // Skip 'c'
- index++;
- }
- } else if (c == '\"') {
- // String: Skip to end
- index++;
- while (index < length - 1) {
- char t = s.charAt(index);
- if (t == '\\') {
- index++;
- } else if (t == '"') {
- break;
- }
- index++;
- }
- } else if (c == 'R' && next == '.') {
- // This might be a pattern
- int begin = index;
- index += 2;
- while (index < length) {
- char t = s.charAt(index);
- if (t == '.') {
- String typeName = s.substring(begin + 2, index);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type != null) {
- index++;
- begin = index;
- while (index < length &&
- Character.isJavaIdentifierPart(s.charAt(index))) {
- index++;
- }
- if (index > begin) {
- String name = R_PREFIX + typeName + '.'
- + s.substring(begin, index);
- mReferences.add(name);
- }
- }
- index--;
- break;
- } else if (!Character.isJavaIdentifierStart(t)) {
- break;
- }
- index++;
- }
- } else if (Character.isJavaIdentifierPart(c)) {
- // Skip to the end of the identifier
- while (index < length && Character.isJavaIdentifierPart(s.charAt(index))) {
- index++;
- }
- // Back up so the next character can be checked to see if it's a " etc
- index--;
- } else {
- // Just punctuation/operators ( ) ; etc
- }
- }
- }
-
- @Override
public void beforeCheckFile(Context context) {
File file = context.file;
String fileName = file.getName();
@@ -526,4 +365,92 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec
public Speed getSpeed() {
return Speed.SLOW;
}
+
+ @Override
+ public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
+ return Collections.<Class<? extends lombok.ast.Node>>singletonList(ClassDeclaration.class);
+ }
+
+ @Override
+ public boolean appliesToResourceRefs() {
+ return true;
+ }
+
+ @Override
+ public void visitResourceReference(JavaContext context, AstVisitor visitor, VariableReference node,
+ String type, String name) {
+ String reference = R_PREFIX + type + '.' + name;
+ mReferences.add(reference);
+ }
+
+ @Override
+ public AstVisitor createJavaVisitor(JavaContext context) {
+ return new UnusedResourceVisitor();
+ }
+
+ // Look for references and declarations
+ private class UnusedResourceVisitor extends ForwardingAstVisitor {
+ // Look for references to field R.<class>.<name>
+ // and store them in mReferences
+ @Override
+ public boolean visitVariableReference(VariableReference node) {
+ if (node.astIdentifier().getDescription().equals("R") &&
+ node.getParent() instanceof Select &&
+ node.getParent().getParent() instanceof Select) {
+ String reference = node.getParent().getParent().toString();
+ mReferences.add(reference);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visitClassDeclaration(ClassDeclaration node) {
+ // Look for declarations of R class fields and store them in
+ // mDeclarations
+ String description = node.getDescription();
+ if (description.equals("R")) { //$NON-NLS-1$
+ // This is an R class. We can process this class very deliberately.
+ // The R class has a very specific AST format:
+ // ClassDeclaration ("R")
+ // NormalTypeBody
+ // ClassDeclaration (e.g. "drawable")
+ // NormalTypeBody
+ // VariableDeclaration
+ // VariableDefinition (e.g. "ic_launcher")
+ for (lombok.ast.Node body : node.getChildren()) {
+ if (body instanceof NormalTypeBody) {
+ for (lombok.ast.Node subclass : body.getChildren()) {
+ if (subclass instanceof ClassDeclaration) {
+ String className = ((ClassDeclaration) subclass).getDescription();
+ for (lombok.ast.Node innerBody : subclass.getChildren()) {
+ if (innerBody instanceof NormalTypeBody) {
+ for (lombok.ast.Node field : innerBody.getChildren()) {
+ if (field instanceof VariableDeclaration) {
+ for (lombok.ast.Node child : field.getChildren()) {
+ if (child instanceof VariableDefinition) {
+ VariableDefinition def =
+ (VariableDefinition) child;
+ String name = def.astVariables().first()
+ .astName().astValue();
+ String resource = R_PREFIX + className + '.'
+ + name;
+ mDeclarations.add(resource);
+ } // Else: It could be a comment node
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ }
}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
index dd18ffa..2678d9f 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
@@ -18,8 +18,10 @@ package com.android.tools.lint.checks;
import com.android.tools.lint.LintCliXmlParser;
import com.android.tools.lint.Main;
+import com.android.tools.lint.LombokParser;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.IDomParser;
+import com.android.tools.lint.client.api.IJavaParser;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.Lint;
import com.android.tools.lint.detector.api.Context;
@@ -347,6 +349,11 @@ abstract class AbstractCheckTest extends TestCase {
}
@Override
+ public IJavaParser getJavaParser() {
+ return new LombokParser();
+ }
+
+ @Override
public Configuration getConfiguration(Project project) {
return new TestConfiguration();
}