diff options
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(); } |