aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/tools.atree4
-rwxr-xr-xbuild/tools.windows.atree3
-rw-r--r--eclipse/dictionary.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/.classpath2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml37
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java87
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlQuickAssistManager.java102
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java70
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java18
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ClearLintMarkersAction.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEclipseContext.java506
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java396
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java406
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintRunner.java183
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java139
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java36
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java493
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java6
-rwxr-xr-xeclipse/scripts/create_adt_symlinks.sh4
-rw-r--r--layoutopt/app/src/com/android/layoutopt/cli/Main.java8
-rw-r--r--lint/.gitignore4
-rw-r--r--lint/Android.mk5
-rw-r--r--lint/MODULE_LICENSE_APACHE20
-rw-r--r--lint/cli/.classpath8
-rw-r--r--lint/cli/.project17
-rw-r--r--lint/cli/.settings/org.eclipse.jdt.core.prefs71
-rw-r--r--lint/cli/Android.mk22
-rw-r--r--lint/cli/NOTICE190
-rw-r--r--lint/cli/etc/Android.mk10
-rwxr-xr-xlint/cli/etc/lint72
-rwxr-xr-xlint/cli/etc/lint.bat55
-rw-r--r--lint/cli/etc/manifest.txt2
-rw-r--r--lint/cli/src/com/android/tools/lint/Main.java274
-rw-r--r--lint/cli/src/com/android/tools/lint/PositionXmlParser.java166
-rw-r--r--lint/libs/Android.mk5
-rw-r--r--lint/libs/lint_api/.classpath7
-rw-r--r--lint/libs/lint_api/.project17
-rw-r--r--lint/libs/lint_api/.settings/org.eclipse.jdt.core.prefs71
-rw-r--r--lint/libs/lint_api/.settings/org.moreunit.prefs6
-rw-r--r--lint/libs/lint_api/Android.mk31
-rw-r--r--lint/libs/lint_api/NOTICE190
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/api/DetectorRegistry.java100
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/api/IDomParser.java65
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/api/Lint.java266
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/api/ToolContext.java87
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/api/XmlVisitor.java213
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java126
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java105
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java157
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/LayoutDetector.java91
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java100
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Position.java47
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/ResourceXmlDetector.java200
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java71
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java62
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/Speed.java45
-rw-r--r--lint/libs/lint_checks/.classpath8
-rw-r--r--lint/libs/lint_checks/.project17
-rw-r--r--lint/libs/lint_checks/.settings/org.eclipse.jdt.core.prefs71
-rw-r--r--lint/libs/lint_checks/.settings/org.moreunit.prefs5
-rw-r--r--lint/libs/lint_checks/Android.mk23
-rw-r--r--lint/libs/lint_checks/NOTICE190
-rw-r--r--lint/libs/lint_checks/etc/manifest.txt1
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java92
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinDetectorRegistry.java60
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java101
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java388
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java108
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java76
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java152
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java91
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java100
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java147
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java91
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java202
-rw-r--r--lint/libs/lint_checks/tests/.classpath11
-rw-r--r--lint/libs/lint_checks/tests/.project17
-rw-r--r--lint/libs/lint_checks/tests/Android.mk30
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java209
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AccessibilityDetectorTest.java36
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ChildCountDetectorTest.java34
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java47
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java36
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java33
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java34
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java34
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/StateListDetectorTest.java34
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TooManyViewsDetectorTest.java41
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java34
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UselessViewDetectorTest.java36
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/drawable/states.xml7
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/accessibility.xml8
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/compound.xml17
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/duplicate.xml8
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/has_children.xml13
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/inefficient_weight.xml44
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout1.xml24
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout2.xml23
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout3.xml19
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout4.xml19
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/scrolling.xml19
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/simple.xml7
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_deep.xml85
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_many.xml413
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/useless.xml19
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/wrong_dimension.xml13
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/ScopeTest.java49
115 files changed, 8949 insertions, 46 deletions
diff --git a/build/tools.atree b/build/tools.atree
index 7b8bb76..eb273eb 100644
--- a/build/tools.atree
+++ b/build/tools.atree
@@ -61,6 +61,7 @@ bin/layoutopt tools/layoutopt
bin/traceview tools/traceview
bin/android tools/android
bin/monkeyrunner tools/monkeyrunner
+bin/lint tools/lint
# sdk.git Ant templates for project build files
@@ -105,6 +106,9 @@ framework/guavalib.jar tools/lib/guavalib.jar
framework/jsilver.jar tools/lib/jsilver.jar
framework/jython.jar tools/lib/jython.jar
framework/mkidentity-prebuilt.jar tools/lib/mkidentity.jar
+framework/lint.jar tools/lib/lint.jar
+framework/lint_api.jar tools/lib/lint_api.jar
+framework/lint_checks.jar tools/lib/lint_checks.jar
# 3rd Party java libraries
framework/groovy-all-1.7.0.jar tools/lib/groovy-all-1.7.0.jar
diff --git a/build/tools.windows.atree b/build/tools.windows.atree
index 066050e..1340498 100755
--- a/build/tools.windows.atree
+++ b/build/tools.windows.atree
@@ -33,6 +33,9 @@ bin/dmtracedump.exe strip tools/dmtracedump.exe
rm tools/draw9patch
sdk/draw9patch/etc/draw9patch.bat tools/draw9patch.bat
+rm tools/lint
+sdk/lint/cli/etc/lint.bat tools/lint.bat
+
rm tools/emulator
rm tools/emulator-arm
rm tools/emulator-x86
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index 4eb4b94..66911d5 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -123,6 +123,7 @@ inset
instanceof
instantiatable
int
+inter
interpolator
interpolators
iterable
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
index 82159df..d7bf98e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
@@ -19,6 +19,8 @@
<classpathentry kind="lib" path="libs/sdkuilib.jar" sourcepath="/SdkUiLib"/>
<classpathentry kind="lib" path="libs/common.jar" sourcepath="/common"/>
<classpathentry kind="lib" path="libs/rule_api.jar" sourcepath="/rule_api"/>
+ <classpathentry kind="lib" path="libs/lint_api.jar" sourcepath="/lint-api"/>
+ <classpathentry kind="lib" path="libs/lint_checks.jar" sourcepath="/lint-checks"/>
<classpathentry kind="lib" path="libs/assetstudio.jar" sourcepath="/assetstudio"/>
<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
<classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
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 3855d87..8de0617 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
@@ -16,6 +16,8 @@ Bundle-ClassPath: .,
libs/common.jar,
libs/rule_api.jar,
libs/assetstudio.jar,
+ libs/lint_api.jar,
+ libs/lint_checks.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/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index 73e3d6b..fb0357e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -49,8 +49,19 @@
<super type="org.eclipse.core.resources.textmarker" />
<persistent value="true" />
</extension>
+ <extension
+ id="com.android.ide.eclipse.adt.lintProblem"
+ name="Android Lint Problem"
+ point="org.eclipse.core.resources.markers">
+ <super type="org.eclipse.core.resources.problemmarker" />
+ <super type="org.eclipse.core.resources.textmarker" />
+ <persistent value="true" />
+ </extension>
<extension point="org.eclipse.ui.ide.markerResolution">
<markerResolutionGenerator
+ markerType="com.android.ide.eclipse.adt.lintProblem"
+ class="com.android.ide.eclipse.adt.internal.lint.LintFixGenerator" />
+ <markerResolutionGenerator
markerType="com.android.ide.eclipse.common.aaptProblem"
class="com.android.ide.eclipse.adt.internal.build.AaptQuickFix" />
</extension>
@@ -258,6 +269,8 @@
path="additions">
<separator name="group1" />
<separator name="group2" />
+ <separator name="group3" />
+ <separator name="group4" />
</menu>
<filter
name="projectNature"
@@ -320,6 +333,18 @@
id="com.android.ide.eclipse.adt.DexDumpAction"
label="Display dex bytecode"
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group3" />
+ <action
+ class="com.android.ide.eclipse.adt.internal.lint.ClearLintMarkersAction"
+ enablesFor="1"
+ id="com.android.ide.eclipse.adt.internal.lint.ClearLintMarkersAction"
+ label="Clear Lint Markers"
+ menubarPath="com.android.ide.eclipse.adt.AndroidTools/group4" />
+ <action
+ class="com.android.ide.eclipse.adt.internal.lint.RunLintAction"
+ enablesFor="1"
+ id="com.android.ide.eclipse.adt.internal.lint.RunLintAction"
+ label="Run Lint: Check for Common Errors"
+ menubarPath="com.android.ide.eclipse.adt.AndroidTools/group4" />
</objectContribution>
</extension>
<extension point="org.eclipse.ui.preferencePages">
@@ -349,6 +374,12 @@
id="com.android.ide.eclipse.adt.preferences.EditorsPage"
name="Editors">
</page>
+ <page
+ category="com.android.ide.eclipse.preferences.main"
+ class="com.android.ide.eclipse.adt.internal.preferences.LintPreferencePage"
+ id="com.android.ide.eclipse.common.preferences.LintPreferencePage"
+ name="Lint Error Checking">
+ </page>
</extension>
<extension point="org.eclipse.core.runtime.preferences">
<initializer class="com.android.ide.eclipse.adt.internal.preferences.AdtPrefs" />
@@ -587,11 +618,7 @@
</sourceViewerConfiguration>
<provisionalConfiguration
type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor"
- class="com.android.ide.eclipse.adt.internal.build.AaptQuickFix"
- target="org.eclipse.wst.xml.XML_DEFAULT" />
- <provisionalConfiguration
- type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor"
- class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.RefactoringAssistant"
+ class="com.android.ide.eclipse.adt.internal.editors.formatting.XmlQuickAssistManager"
target="org.eclipse.wst.xml.XML_DEFAULT" />
<provisionalConfiguration
type="characterpairmatcher"
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
index 63746ed..0a1f2bb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
@@ -243,6 +243,8 @@ public class AdtConstants {
/** final packaging error marker, only to be used in {@link PostCompilerBuilder} */
public final static String MARKER_PACKAGING = AdtPlugin.PLUGIN_ID + ".packagingProblem"; //$NON-NLS-1$
+ /** Marker for lint errors */
+ public final static String MARKER_LINT = AdtPlugin.PLUGIN_ID + ".lintProblem"; //$NON-NLS-1$
/** Name for the "type" marker attribute */
public final static String MARKER_ATTR_TYPE = "android.type"; //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index 17090fd..8cd03d4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -17,10 +17,16 @@
package com.android.ide.eclipse.adt;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
@@ -29,6 +35,10 @@ import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
/** Utility methods for ADT */
public class AdtUtils {
@@ -291,4 +301,81 @@ public class AdtUtils {
IPath workspacePath = workspace.getLocation();
return workspacePath.append(resource.getFullPath());
}
+
+ /**
+ * Converts a {@link File} to an {@link IFile}, if possible.
+ *
+ * @param file a file to be converted
+ * @return the corresponding {@link IFile}, or null
+ */
+ public static IFile fileToIFile(File file) {
+ IPath filePath = new Path(file.getPath());
+ return pathToIFile(filePath);
+ }
+
+ /**
+ * Converts a {@link IPath} to an {@link IFile}, if possible.
+ *
+ * @param path a path to be converted
+ * @return the corresponding {@link IFile}, or null
+ */
+ public static IFile pathToIFile(IPath path) {
+ IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+ IPath workspacePath = workspace.getLocation();
+ if (workspacePath.isPrefixOf(path)) {
+ IPath relativePath = path.makeRelativeTo(workspacePath);
+ IResource member = workspace.findMember(relativePath);
+ if (member instanceof IFile) {
+ return (IFile) member;
+ }
+ } else if (path.isAbsolute()) {
+ return workspace.getFileForLocation(path);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns all markers in a file/document that fit on the same line as the given offset
+ *
+ * @param markerType the marker type
+ * @param file the file containing the markers
+ * @param document the document showing the markers
+ * @param offset the offset to be checked
+ * @return a list (possibly empty but never null) of matching markers
+ */
+ public static List<IMarker> findMarkersOnLine(String markerType,
+ IFile file, IDocument document, int offset) {
+ List<IMarker> matchingMarkers = new ArrayList<IMarker>(2);
+ try {
+ IMarker[] markers = file.findMarkers(markerType, true, IResource.DEPTH_ZERO);
+
+ // Look for a match on the same line as the caret.
+ IRegion lineInfo = document.getLineInformationOfOffset(offset);
+ int lineStart = lineInfo.getOffset();
+ int lineEnd = lineStart + lineInfo.getLength();
+ int offsetLine = document.getLineOfOffset(offset);
+
+
+ for (IMarker marker : markers) {
+ int start = marker.getAttribute(IMarker.CHAR_START, -1);
+ int end = marker.getAttribute(IMarker.CHAR_END, -1);
+ if (start >= lineStart && start <= lineEnd && end > start) {
+ matchingMarkers.add(marker);
+ } else if (start == -1 && end == -1) {
+ // Some markers don't set character range, they only set the line
+ int line = marker.getAttribute(IMarker.LINE_NUMBER, -1);
+ if (line == offsetLine + 1) {
+ matchingMarkers.add(marker);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return matchingMarkers;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
index 7d79086..c6928a9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
@@ -20,6 +20,7 @@ import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
@@ -58,6 +59,8 @@ import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import java.util.List;
+
/**
* Shared handler for both quick assist processors (Control key handler) and quick fix
* marker resolution (Problem view handling), since there is a lot of overlap between
@@ -159,34 +162,24 @@ public class AaptQuickFix implements IMarkerResolutionGenerator2, IQuickAssistPr
AndroidXmlEditor editor = AndroidXmlEditor.getAndroidXmlEditor(sourceViewer);
if (editor != null) {
IFile file = editor.getInputFile();
-
+ IDocument document = sourceViewer.getDocument();
+ List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_AAPT_COMPILE,
+ file, document, invocationContext.getOffset());
try {
- IMarker[] markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true,
- IResource.DEPTH_ZERO);
-
- // Look for a match on the same line as the caret.
- int offset = invocationContext.getOffset();
- IDocument document = sourceViewer.getDocument();
- IRegion lineInfo = document.getLineInformationOfOffset(offset);
- int lineStart = lineInfo.getOffset();
- int lineEnd = lineStart + lineInfo.getLength();
-
for (IMarker marker : markers) {
String message = marker.getAttribute(IMarker.MESSAGE, ""); //$NON-NLS-1$
if (message.contains(getTargetMarkerErrorMessage())) {
int start = marker.getAttribute(IMarker.CHAR_START, 0);
int end = marker.getAttribute(IMarker.CHAR_END, 0);
- if (start >= lineStart && start <= lineEnd && end > start) {
- int length = end - start;
- String resource = document.get(start, length);
- // Can only offer create value for non-framework value
- // resources
- if (ResourceHelper.canCreateResource(resource)) {
- IProject project = editor.getProject();
- return new ICompletionProposal[] {
- new CreateResourceProposal(project, resource)
- };
- }
+ int length = end - start;
+ String resource = document.get(start, length);
+ // Can only offer create value for non-framework value
+ // resources
+ if (ResourceHelper.canCreateResource(resource)) {
+ IProject project = editor.getProject();
+ return new ICompletionProposal[] {
+ new CreateResourceProposal(project, resource)
+ };
}
} else if (message.contains(getUnboundErrorMessage())) {
return new ICompletionProposal[] {
@@ -194,8 +187,6 @@ public class AaptQuickFix implements IMarkerResolutionGenerator2, IQuickAssistPr
};
}
}
- } catch (CoreException e) {
- AdtPlugin.log(e, null);
} catch (BadLocationException e) {
AdtPlugin.log(e, null);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
index c854b48..4b0673f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
@@ -18,8 +18,11 @@ package com.android.ide.eclipse.adt.internal.editors;
import static org.eclipse.wst.sse.ui.internal.actions.StructuredTextEditorActionConstants.ACTION_NAME_FORMAT_DOCUMENT;
+import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.lint.LintRunner;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -28,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
@@ -38,6 +42,7 @@ import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.BadLocationException;
@@ -65,6 +70,7 @@ import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.events.IHyperlinkListener;
import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.part.WorkbenchPart;
@@ -243,6 +249,34 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
// ---- Base Class Overrides, Interfaces Implemented ----
+ @Override
+ public Object getAdapter(Class adapter) {
+ Object result = super.getAdapter(adapter);
+
+ if (result != null && adapter.equals(IGotoMarker.class) ) {
+ final IGotoMarker gotoMarker = (IGotoMarker) result;
+ return new IGotoMarker() {
+ public void gotoMarker(IMarker marker) {
+ gotoMarker.gotoMarker(marker);
+ try {
+ // Lint markers should always jump to XML text
+ if (marker.getType().equals(AdtConstants.MARKER_LINT)) {
+ IEditorPart editor = AdtUtils.getActiveEditor();
+ if (editor instanceof AndroidXmlEditor) {
+ AndroidXmlEditor xmlEditor = (AndroidXmlEditor) editor;
+ xmlEditor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ };
+ }
+
+ return result;
+ }
+
/**
* Creates the pages of the multi-page editor.
*/
@@ -522,6 +556,17 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
// The actual "save" operation is done by the Structured XML Editor
getEditor(mTextPageIndex).doSave(monitor);
+
+ runLintOnSave();
+ }
+
+ protected Job runLintOnSave() {
+ // Check for errors, if enabled
+ if (AdtPrefs.getPrefs().isLintOnSave()) {
+ return LintRunner.startLint(getInputFile(), getStructuredDocument());
+ }
+
+ return null;
}
/* (non-Javadoc)
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlQuickAssistManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlQuickAssistManager.java
new file mode 100644
index 0000000..659bfe1
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlQuickAssistManager.java
@@ -0,0 +1,102 @@
+/*
+ * 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.editors.formatting;
+
+import com.android.ide.eclipse.adt.internal.build.AaptQuickFix;
+import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.RefactoringAssistant;
+import com.android.ide.eclipse.adt.internal.lint.LintFixGenerator;
+
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class implements Quick Assists for XML files. It does not perform any
+ * quick assistance on its own, but it coordinates the various separate quick
+ * assists available for XML such that the order is logical. This is necessary
+ * because without it, the order of suggestions (when more than one assistant
+ * provides suggestions) is not always optimal. There doesn't seem to be a way
+ * from non-Java languages to set the sorting order (see
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=229983 ). So instead of
+ * registering our multiple XML quick assistants via the plugin.xml file, we
+ * register <b>just</b> this manager, which delegates to the various XML quick
+ * assistants as appropriate.
+ */
+public class XmlQuickAssistManager implements IQuickAssistProcessor {
+ private final IQuickAssistProcessor[] mProcessors;
+
+ /** Constructs a new {@link XmlQuickAssistManager} which orders the quick fixes */
+ public XmlQuickAssistManager() {
+ mProcessors = new IQuickAssistProcessor[] {
+ new AaptQuickFix(),
+ new LintFixGenerator(),
+ new RefactoringAssistant()
+ };
+ }
+
+ public String getErrorMessage() {
+ return null;
+ }
+
+ public boolean canFix(Annotation annotation) {
+ for (IQuickAssistProcessor processor : mProcessors) {
+ if (processor.canFix(annotation)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+ for (IQuickAssistProcessor processor : mProcessors) {
+ if (processor.canAssist(invocationContext)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public ICompletionProposal[] computeQuickAssistProposals(
+ IQuickAssistInvocationContext invocationContext) {
+ List<ICompletionProposal> allProposals = null;
+ for (IQuickAssistProcessor processor : mProcessors) {
+ if (processor.canAssist(invocationContext)) {
+ ICompletionProposal[] proposals =
+ processor.computeQuickAssistProposals(invocationContext);
+ if (proposals != null && proposals.length > 0) {
+ if (allProposals == null) {
+ allProposals = new ArrayList<ICompletionProposal>();
+ }
+ for (ICompletionProposal proposal : proposals) {
+ allProposals.add(proposal);
+ }
+ }
+ }
+ }
+
+ if (allProposals != null) {
+ return allProposals.toArray(new ICompletionProposal[allProposals.size()]);
+ }
+
+ return null;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
index 109201f..de1e3a3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
@@ -27,6 +27,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDes
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutActionBar;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.OutlinePage;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.PropertySheetPage;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
@@ -40,6 +41,9 @@ import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
@@ -164,6 +168,21 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput,
return true;
}
+ @Override
+ protected Job runLintOnSave() {
+ Job job = super.runLintOnSave();
+ if (job != null) {
+ job.addJobChangeListener(new JobChangeAdapter() {
+ @Override
+ public void done(IJobChangeEvent event) {
+ LayoutActionBar bar = getGraphicalEditor().getLayoutActionBar();
+ bar.updateErrorIndicator();
+ }
+ });
+ }
+ return job;
+ }
+
/**
* Create the various form pages.
*/
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index f3e625e..13c7944 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -58,6 +58,7 @@ import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.lint.LintEclipseContext;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
@@ -340,6 +341,9 @@ public class GraphicalEditorPart extends EditorPart
mActionBar = new LayoutActionBar(layoutBarAndCanvas, SWT.NONE, this);
GridData detailsData = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
mActionBar.setLayoutData(detailsData);
+ if (file != null) {
+ mActionBar.updateErrorIndicator(LintEclipseContext.hasMarkers(file));
+ }
mSashError = new SashForm(layoutBarAndCanvas, SWT.VERTICAL | SWT.BORDER);
mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));
@@ -887,6 +891,8 @@ public class GraphicalEditorPart extends EditorPart
if (!mActive) {
mActive = true;
+ mActionBar.updateErrorIndicator();
+
boolean changed = mConfigComposite.syncRenderState();
if (changed) {
// Will also force recomputeLayout()
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 844c8d3..5469d07 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -28,9 +28,11 @@ import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
+import com.android.ide.eclipse.adt.internal.lint.LintEclipseContext;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
+import org.eclipse.core.resources.IFile;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
@@ -40,12 +42,15 @@ import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
import java.net.URL;
import java.util.ArrayList;
@@ -59,12 +64,14 @@ import java.util.List;
public class LayoutActionBar extends Composite {
private GraphicalEditorPart mEditor;
private ToolBar mLayoutToolBar;
+ private ToolBar mLintToolBar;
private ToolBar mZoomToolBar;
private ToolItem mZoomRealSizeButton;
private ToolItem mZoomOutButton;
private ToolItem mZoomResetButton;
private ToolItem mZoomInButton;
private ToolItem mZoomFitButton;
+ private ToolItem mLintButton;
/**
* Creates a new {@link LayoutActionBar} and adds it to the given parent.
@@ -77,13 +84,18 @@ public class LayoutActionBar extends Composite {
super(parent, style | SWT.NO_FOCUS);
mEditor = editor;
- GridLayout layout = new GridLayout(2, false);
+ GridLayout layout = new GridLayout(3, false);
setLayout(layout);
mLayoutToolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
mLayoutToolBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, true, false));
mZoomToolBar = createZoomControls();
mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false));
+ mLintToolBar = createLintControls();
+
+ GridData lintData = new GridData(SWT.END, SWT.BEGINNING, false, false);
+ lintData.exclude = true;
+ mLintToolBar.setLayoutData(lintData);
}
/** Updates the layout contents based on the current selection */
@@ -364,6 +376,7 @@ public class LayoutActionBar extends Composite {
// ---- Zoom Controls ----
+ @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused
private ToolBar createZoomControls() {
ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
@@ -432,6 +445,61 @@ public class LayoutActionBar extends Composite {
return toolBar;
}
+ @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused
+ private ToolBar createLintControls() {
+ ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
+
+ // Separate from adjacent toolbar
+ new ToolItem(toolBar, SWT.SEPARATOR);
+
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ mLintButton = new ToolItem(toolBar, SWT.PUSH);
+ mLintButton.setToolTipText("Show Lint Warnings for this Layout");
+ mLintButton.setImage(sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK));
+ mLintButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ IFile file = mEditor.getLayoutEditor().getInputFile();
+ LintEclipseContext.showErrors(getShell(), file);
+ }
+ });
+
+ return toolBar;
+ }
+
+ /**
+ * Updates the lint indicator state in the given layout editor
+ */
+ public void updateErrorIndicator() {
+ updateErrorIndicator(LintEclipseContext.hasMarkers(mEditor.getEditedFile()));
+ }
+
+ /**
+ * Sets whether the action bar should show the "lint warnings" button
+ *
+ * @param hasLintWarnings whether there are lint errors to be shown
+ */
+ void updateErrorIndicator(final boolean hasLintWarnings) {
+ Display display = getDisplay();
+ if (display.getThread() != Thread.currentThread()) {
+ display.asyncExec(new Runnable() {
+ public void run() {
+ if (!isDisposed()) {
+ updateErrorIndicator(hasLintWarnings);
+ }
+ }
+ });
+ return;
+ }
+
+ GridData layoutData = (GridData) mLintToolBar.getLayoutData();
+ if (layoutData.exclude == hasLintWarnings) {
+ layoutData.exclude = !hasLintWarnings;
+ mLintToolBar.setVisible(hasLintWarnings);
+ layout();
+ }
+ }
+
/**
* Returns true if zooming in/out/to-fit/etc is allowed (which is not the case while
* emulating real size)
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
index 8d0ea33..c2425fc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
@@ -249,7 +249,7 @@ public class RefactoringAssistant implements IQuickAssistProcessor {
}
public String getAdditionalProposalInfo() {
- return "Initiates the given refactoring operation";
+ return String.format("Initiates the \"%1$s\" refactoring", mRefactoring.getName());
}
public IContextInformation getContextInformation() {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
index ec68323..c434712 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
@@ -81,8 +81,6 @@ import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IWorkspaceRoot;
-import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
@@ -536,19 +534,9 @@ public class Hyperlinks {
private static void openPath(IPath filePath, IRegion region, int offset) {
IEditorPart sourceEditor = getEditor();
IWorkbenchPage page = sourceEditor.getEditorSite().getPage();
- IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
- IPath workspacePath = workspace.getLocation();
- IFile file = null;
- if (workspacePath.isPrefixOf(filePath)) {
- IPath relativePath = filePath.makeRelativeTo(workspacePath);
- IResource member = workspace.findMember(relativePath);
- if (member instanceof IFile) {
- file = (IFile) member;
- }
- } else if (filePath.isAbsolute()) {
- file = workspace.getFileForLocation(filePath);
- }
- if (file != null) {
+
+ IFile file = AdtUtils.pathToIFile(filePath);
+ if (file != null && file.exists()) {
try {
AdtPlugin.openFile(file, region);
return;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ClearLintMarkersAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ClearLintMarkersAction.java
new file mode 100644
index 0000000..21d90e6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ClearLintMarkersAction.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.eclipse.core.resources.IProject;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IActionDelegate;
+
+/** Action which clear lint markers from the current project */
+public class ClearLintMarkersAction implements IActionDelegate {
+
+ private ISelection mSelection;
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ mSelection = selection;
+ }
+
+ public void run(IAction action) {
+ IProject project = RunLintAction.getSelectedProject(mSelection);
+ if (project != null) {
+ LintRunner.cancelCurrentJobs();
+ LintEclipseContext.clearMarkers(project);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEclipseContext.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEclipseContext.java
new file mode 100644
index 0000000..1ae061b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEclipseContext.java
@@ -0,0 +1,506 @@
+/*
+ * 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 static com.android.ide.eclipse.adt.AdtConstants.MARKER_LINT;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.api.ToolContext;
+import com.android.tools.lint.checks.BuiltinDetectorRegistry;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.util.Pair;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.editors.text.TextFileDocumentProvider;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Eclipse implementation for running lint on workspace files and projects.
+ */
+@SuppressWarnings("restriction") // DOM model
+public class LintEclipseContext implements ToolContext, IDomParser {
+ static final String MARKER_CHECKID_PROPERTY = "checkid"; //$NON-NLS-1$
+ private static final String DOCUMENT_PROPERTY = "document"; //$NON-NLS-1$
+ private final DetectorRegistry mRegistry;
+ private final IResource mResource;
+ private final IDocument mDocument;
+ private boolean mFatal;
+ private Map<Issue, Severity> mSeverities;
+ private String mDisabledIdsList; // String version of map: used to detect when to replace map
+ private Set<String> mDisabledIds;
+
+
+ /**
+ * Creates a new {@link LintEclipseContext}.
+ *
+ * @param registry the associated detector registry
+ * @param resource the associated resource (project, file or null)
+ * @param document the associated document, or null if the {@code resource}
+ * param is not a file
+ */
+ public LintEclipseContext(DetectorRegistry registry, IResource resource, IDocument document) {
+ mRegistry = registry;
+ mResource = resource;
+ mDocument = document;
+ }
+
+ // ----- Implements ToolContext -----
+
+ public void log(Throwable exception, String format, Object... args) {
+ if (exception == null) {
+ AdtPlugin.log(IStatus.WARNING, format, args);
+ } else {
+ AdtPlugin.log(exception, format, args);
+ }
+ }
+
+ public IDomParser getParser() {
+ return this;
+ }
+
+ public boolean isEnabled(Issue issue) {
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ String idList = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
+ if (idList == null) {
+ idList = ""; //$NON-NLS-1$
+ }
+ if (mDisabledIds == null || !mDisabledIdsList.equals(idList)) {
+ mDisabledIdsList = idList;
+ if (idList.length() > 0) {
+ String[] ids = idList.split(","); //$NON-NLS-1$
+ mDisabledIds = new HashSet<String>(ids.length);
+ for (String s : ids) {
+ mDisabledIds.add(s);
+ }
+ } else {
+ mDisabledIds = Collections.emptySet();
+ }
+ }
+
+ return !mDisabledIds.contains(issue.getId());
+ }
+
+ // ----- Implements IDomParser -----
+ public Document parse(Context context) {
+ // Map File to IFile
+ IFile file = AdtUtils.fileToIFile(context.file);
+ if (file == null || !file.exists()) {
+ String path = context.file.getPath();
+ AdtPlugin.log(IStatus.ERROR, "Can't find file %1$s in workspace", path);
+ return null;
+ }
+
+ IStructuredModel model = null;
+ try {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ model = modelManager.getModelForRead(file);
+ if (model instanceof IDOMModel) {
+ context.setProperty(DOCUMENT_PROPERTY, model.getStructuredDocument());
+ IDOMModel domModel = (IDOMModel) model;
+ return domModel.getDocument();
+ }
+ } catch (IOException e) {
+ AdtPlugin.log(e, "Cannot read XML file");
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (model != null) {
+ // TODO: This may be too early...
+ model.releaseFromRead();
+ }
+ }
+
+ return null;
+ }
+
+ private static Position getPosition(Context context, int offset) {
+ IStructuredDocument doc = (IStructuredDocument) context.getProperty(DOCUMENT_PROPERTY);
+ if (doc != null && offset < doc.getLength()) {
+ int line = doc.getLineOfOffset(offset);
+ int column = -1;
+ try {
+ int lineOffset = doc.getLineOffset(line);
+ column = offset - lineOffset;
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ }
+ return new OffsetPosition(line, column, offset);
+ }
+
+ return null;
+ }
+
+ public Position getStartPosition(Context context, Node node) {
+ if (node instanceof IndexedRegion) {
+ IndexedRegion region = (IndexedRegion) node;
+ return getPosition(context, region.getStartOffset());
+ }
+
+ return null;
+ }
+
+ public Position getEndPosition(Context context, Node node) {
+ if (node instanceof IndexedRegion) {
+ IndexedRegion region = (IndexedRegion) node;
+ return getPosition(context, region.getEndOffset());
+ }
+
+ return null;
+ }
+
+ public Severity getSeverity(Issue issue) {
+ if (mSeverities == null) {
+ mSeverities = new HashMap<Issue, Severity>();
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ String assignments = store.getString(AdtPrefs.PREFS_LINT_SEVERITIES);
+ if (assignments != null && assignments.length() > 0) {
+ for (String assignment : assignments.split(",")) { //$NON-NLS-1$
+ String[] s = assignment.split("="); //$NON-NLS-1$
+ if (s.length == 2) {
+ Issue d = mRegistry.getIssue(s[0]);
+ if (d != null) {
+ Severity severity = Severity.valueOf(s[1]);
+ if (severity != null) {
+ mSeverities.put(d, severity);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Severity severity = mSeverities.get(issue);
+ if (severity != null) {
+ return severity;
+ }
+
+ return issue.getDefaultSeverity();
+ }
+
+ /**
+ * Sets the custom severity for the given detector to the given new severity
+ *
+ * @param severities a map from detector to severity to use from now on
+ */
+ public void setSeverities(Map<Issue, Severity> severities) {
+ mSeverities = null;
+
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ if (severities.size() == 0) {
+ store.setToDefault(AdtPrefs.PREFS_LINT_SEVERITIES);
+ return;
+ }
+ List<Issue> sortedKeys = new ArrayList<Issue>(severities.keySet());
+ Collections.sort(sortedKeys);
+
+ StringBuilder sb = new StringBuilder(severities.size() * 20);
+ for (Issue issue : sortedKeys) {
+ Severity severity = severities.get(issue);
+ if (severity != issue.getDefaultSeverity()) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append(issue.getId());
+ sb.append('=');
+ sb.append(severity.name());
+ }
+ }
+
+ if (sb.length() > 0) {
+ store.setValue(AdtPrefs.PREFS_LINT_SEVERITIES, sb.toString());
+ } else {
+ store.setToDefault(AdtPrefs.PREFS_LINT_SEVERITIES);
+ }
+ }
+
+ public void report(Issue issue, Location location, String message) {
+ if (!isEnabled(issue)) {
+ return;
+ }
+
+ Severity s = getSeverity(issue);
+ if (s == Severity.IGNORE) {
+ return;
+ }
+
+ int severity = getMarkerSeverity(s);
+ IMarker marker = null;
+ if (location == null) {
+ marker = BaseProjectHelper.markResource(mResource, MARKER_LINT,
+ message, 0, severity);
+ } else {
+ Position startPosition = location.getStart();
+ if (startPosition == null) {
+ marker = BaseProjectHelper.markResource(mResource, MARKER_LINT,
+ message, 0, severity);
+ } else {
+ Position endPosition = location.getEnd();
+ int line = startPosition.getLine() + 1; // Marker API is 1-based
+ IFile file = AdtUtils.fileToIFile(location.getFile());
+ if (file != null) {
+ Pair<Integer, Integer> r = getRange(file, mDocument,
+ startPosition, endPosition);
+ int startOffset = r.getFirst();
+ int endOffset = r.getSecond();
+
+ marker = BaseProjectHelper.markResource(file, MARKER_LINT,
+ message, line, startOffset, endOffset, severity);
+ }
+ }
+ }
+
+ if (marker != null) {
+ // Store marker id such that we can recognize it from the suppress quickfix
+ try {
+ marker.setAttribute(MARKER_CHECKID_PROPERTY, issue.getId());
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ if (s == Severity.ERROR) {
+ mFatal = true;
+ }
+ }
+
+ public boolean isSuppressed(Issue issue, Location range, String message, Severity severity) {
+ // Not yet supported
+ return false;
+ }
+
+ /** Clears any lint markers from the given resource (project, folder or file) */
+ static void clearMarkers(IResource resource) {
+ try {
+ resource.deleteMarkers(MARKER_LINT, false, IResource.DEPTH_INFINITE);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ IEditorPart active = AdtUtils.getActiveEditor();
+ if (active instanceof LayoutEditor) {
+ LayoutEditor editor = (LayoutEditor) active;
+ editor.getGraphicalEditor().getLayoutActionBar().updateErrorIndicator();
+ }
+ }
+
+ /**
+ * Returns whether the given resource has one or more lint markers
+ *
+ * @param resource the resource to be checked, typically a source file
+ * @return true if the given resource has one or more lint markers
+ */
+ public static boolean hasMarkers(IResource resource) {
+ return getMarkers(resource).length > 0;
+ }
+
+ /**
+ * Returns the lint marker for the given resource (which may be a project, folder or file)
+ *
+ * @param resource the resource to be checked, typically a source file
+ * @return an array of markers, possibly empty but never null
+ */
+ public static IMarker[] getMarkers(IResource resource) {
+ try {
+ return resource.findMarkers(MARKER_LINT, false, IResource.DEPTH_INFINITE);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return new IMarker[0];
+ }
+
+ private static int getMarkerSeverity(Severity severity) {
+ switch (severity) {
+ case INFORMATIONAL:
+ return IMarker.SEVERITY_INFO;
+ case WARNING:
+ return IMarker.SEVERITY_WARNING;
+ case ERROR:
+ default:
+ return IMarker.SEVERITY_ERROR;
+ }
+ }
+
+ private static Pair<Integer, Integer> getRange(IFile file, IDocument doc,
+ Position startPosition, Position endPosition) {
+ int startOffset = startPosition.getOffset();
+ int endOffset = endPosition != null ? endPosition.getOffset() : -1;
+ if (endOffset != -1) {
+ // Attribute ranges often include trailing whitespace; trim this up
+ if (doc == null) {
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ try {
+ provider.connect(file);
+ doc = provider.getDocument(file);
+ if (doc != null) {
+ return trimTrailingSpace(doc, startOffset, endOffset);
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
+ } finally {
+ provider.disconnect(file);
+ }
+ } else {
+ return trimTrailingSpace(doc, startOffset, endOffset);
+ }
+ }
+
+ return Pair.of(startOffset, startOffset);
+ }
+
+ /** Trim off any trailing space on the given offset range in the given document */
+ private static Pair<Integer, Integer> trimTrailingSpace(IDocument doc, int startOffset,
+ int endOffset) {
+ if (doc != null) {
+ while (endOffset > startOffset && endOffset < doc.getLength()) {
+ try {
+ if (!Character.isWhitespace(doc.getChar(endOffset - 1))) {
+ break;
+ } else {
+ endOffset--;
+ }
+ } catch (BadLocationException e) {
+ // Pass - we've already validated offset range above
+ break;
+ }
+ }
+ }
+
+ return Pair.of(startOffset, endOffset);
+ }
+
+ /**
+ * Returns true if a fatal error was encountered
+ *
+ * @return true if a fatal error was encountered
+ */
+ public boolean isFatal() {
+ return mFatal;
+ }
+
+ /**
+ * Show a dialog with errors for the given file
+ *
+ * @param shell the parent shell to attach the dialog to
+ * @param file the file to show the errors for
+ */
+ public static void showErrors(Shell shell, final IFile file) {
+ LintListDialog dialog = new LintListDialog(shell, file);
+ dialog.open();
+ }
+
+ /**
+ * Returns the registry of detectors to use from within Eclipse. This method
+ * should be used rather than calling
+ * {@link BuiltinDetectorRegistry#BuiltinDetectorRegistry} directly since it can replace
+ * some detectors with Eclipse-optimized replacements.
+ *
+ * @return the detector registry to use to access detectors and issues
+ */
+ public static DetectorRegistry getRegistry() {
+ return new BuiltinDetectorRegistry();
+ }
+
+ private static class OffsetPosition extends Position {
+ /** The line number (0-based where the first line is line 0) */
+ private final int mLine;
+
+ /**
+ * The column number (where the first character on the line is 0), or -1 if
+ * unknown
+ */
+ private final int mColumn;
+
+ /** The character offset */
+ private final int mOffset;
+
+ /**
+ * Creates a new {@link Position}
+ *
+ * @param line the 0-based line number, or -1 if unknown
+ * @param column the 0-based column number, or -1 if unknown
+ * @param offset the offset, or -1 if unknown
+ */
+ public OffsetPosition(int line, int column, int offset) {
+ super();
+ this.mLine = line;
+ this.mColumn = column;
+ this.mOffset = offset;
+ }
+
+ /**
+ * Returns the line number (0-based where the first line is line 0)
+ *
+ * @return the 0-based line number
+ */
+ @Override
+ public int getLine() {
+ return mLine;
+ }
+
+ @Override
+ public int getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Returns the column number (where the first character on the line is 0),
+ * or -1 if unknown
+ *
+ * @return the 0-based column number
+ */
+ @Override
+ public int getColumn() {
+ return mColumn;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
new file mode 100644
index 0000000..174b1c9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -0,0 +1,396 @@
+/*
+ * 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.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.tools.lint.detector.api.Issue;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IMarkerResolution;
+import org.eclipse.ui.IMarkerResolution2;
+import org.eclipse.ui.IMarkerResolutionGenerator2;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+
+import java.util.List;
+
+/**
+ * A quickfix and marker resolution for disabling lint checks, and any
+ * IDE specific implementations for fixing the warnings.
+ * <p>
+ * I would really like for this quickfix to show up as a light bulb on top of the error
+ * icon in the editor, and I've spent a whole day trying to make it work. I did not
+ * succeed, but here are the steps I tried in case I want to pick up the work again
+ * later:
+ * <ul>
+ * <li>
+ * The WST has some support for quick fixes, and I came across some forum posts
+ * referencing the ability to show light bulbs. However, it turns out that the
+ * quickfix support for annotations in WST is hardcoded to source validation
+ * errors *only*.
+ * <li>
+ * I tried defining my own editor annotations, and customizing the icon directly
+ * by either setting an icon or using the image provider. This works fine
+ * if I make my marker be a new independent marker type. However, whenever I
+ * switch the marker type back to extend the "Problem" type, then the icon reverts
+ * back to the standard error icon and it ignores my custom settings.
+ * And if I switch away from the Problems marker type, then the errors no longer
+ * show up in the Problems view. (I also tried extending the JDT marker but that
+ * still didn't work.)
+ * <li>
+ * It looks like only JDT handles quickfix icons. It has a bunch of custom code
+ * to handle this, along with its own Annotation subclass used by the editor.
+ * I tried duplicating some of this by subclassing StructuredTextEditor, but
+ * it was evident that I'd have to pull in a *huge* amount of duplicated code to
+ * make this work, which seems risky given that all this is internal code that
+ * can change from one Eclipse version to the next.
+ * </ul>
+ * It looks like our best bet would be to reconsider whether these should show up
+ * in the Problems view; perhaps we should use a custom view for these. That would also
+ * make marker management more obvious.
+ */
+public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssistProcessor {
+ /** Constructs a new {@link LintFixGenerator} */
+ public LintFixGenerator() {
+ }
+
+ // ---- Implements IMarkerResolutionGenerator2 ----
+
+ public boolean hasResolutions(IMarker marker) {
+ try {
+ assert marker.getType().equals(AdtConstants.MARKER_LINT);
+ } catch (CoreException e) {
+ }
+
+ return true;
+ }
+
+ public IMarkerResolution[] getResolutions(IMarker marker) {
+ String id = marker.getAttribute(LintRunner.MARKER_CHECKID_PROPERTY,
+ ""); //$NON-NLS-1$
+ IResource resource = marker.getResource();
+ return new IMarkerResolution[] {
+ new MoreInfoProposal(id, marker.getAttribute(IMarker.MESSAGE, null)),
+ new SuppressProposal(id, true /* all */),
+ // Not yet implemented
+ //new SuppressProposal(id, false),
+ new ClearMarkersProposal(resource, false /* all */),
+ new ClearMarkersProposal(resource, true /* all */),
+ };
+ }
+
+ // ---- Implements IQuickAssistProcessor ----
+
+ public String getErrorMessage() {
+ return "Disable Lint Error";
+ }
+
+ public boolean canFix(Annotation annotation) {
+ return true;
+ }
+
+ public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+ return true;
+ }
+
+ public ICompletionProposal[] computeQuickAssistProposals(
+ IQuickAssistInvocationContext invocationContext) {
+ ISourceViewer sourceViewer = invocationContext.getSourceViewer();
+ AndroidXmlEditor editor = AndroidXmlEditor.getAndroidXmlEditor(sourceViewer);
+ if (editor != null) {
+ IFile file = editor.getInputFile();
+ IDocument document = sourceViewer.getDocument();
+ List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_LINT,
+ file, document, invocationContext.getOffset());
+ if (markers.size() > 0) {
+ for (IMarker marker : markers) {
+ String id = marker.getAttribute(LintRunner.MARKER_CHECKID_PROPERTY,
+ ""); //$NON-NLS-1$
+ return new ICompletionProposal[] {
+ new MoreInfoProposal(id, marker.getAttribute(IMarker.MESSAGE, null)),
+ new SuppressProposal(id, true /* all */),
+ // Not yet implemented
+ //new SuppressProposal(id, false),
+ new ClearMarkersProposal(file, false /* all */),
+ new ClearMarkersProposal(file, true /* all */),
+ };
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Suppress the given detector, and rerun the checks on the file
+ *
+ * @param id the id of the detector to be suppressed
+ */
+ public static void suppressDetector(String id) {
+ // Excluded checks
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ String value = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
+ assert value == null || !value.contains(id);
+ if (value == null || value.length() == 0) {
+ value = id;
+ } else {
+ value = value + ',' + id;
+ }
+ store.setValue(AdtPrefs.PREFS_DISABLED_ISSUES, value);
+
+ // Rerun analysis on the current file to remove this and related markers.
+ // TODO: if mGlobal, rerun on whole project?
+ IEditorPart activeEditor = AdtUtils.getActiveEditor();
+ if (activeEditor instanceof AndroidXmlEditor) {
+ AndroidXmlEditor editor = (AndroidXmlEditor) activeEditor;
+ LintRunner.startLint(editor.getInputFile(), editor.getStructuredDocument());
+ }
+ }
+
+ private static class SuppressProposal implements ICompletionProposal, IMarkerResolution2 {
+ private final String mId;
+ private final boolean mGlobal;
+
+ public SuppressProposal(String check, boolean global) {
+ super();
+ mId = check;
+ mGlobal = global;
+ }
+
+ private void perform() {
+ suppressDetector(mId);
+ }
+
+ public String getDisplayString() {
+ return mGlobal ? "Disable Check" : "Disable Check in this file only";
+ }
+
+ // ---- Implements MarkerResolution2 ----
+
+ public String getLabel() {
+ return getDisplayString();
+ }
+
+ public void run(IMarker marker) {
+ perform();
+ }
+
+ public String getDescription() {
+ return getAdditionalProposalInfo();
+ }
+
+ // ---- Implements ICompletionProposal ----
+
+ public void apply(IDocument document) {
+ perform();
+ }
+
+ public Point getSelection(IDocument document) {
+ return null;
+ }
+
+ public String getAdditionalProposalInfo() {
+ StringBuilder sb = new StringBuilder(200);
+ if (mGlobal) {
+ sb.append("Suppresses this type of lint warning in all files.");
+ } else {
+ sb.append("Suppresses this type of lint warning in the current file only.");
+ }
+ sb.append("<br><br>"); //$NON-NLS-1$
+ sb.append("You can re-enable checks from the \"Android > Lint Error Checking\" preference page.");
+
+ return sb.toString();
+ }
+
+ public Image getImage() {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ }
+
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+ }
+
+ private static class ClearMarkersProposal implements ICompletionProposal, IMarkerResolution2 {
+ private final boolean mGlobal;
+ private final IResource mResource;
+
+ public ClearMarkersProposal(IResource resource, boolean global) {
+ mResource = resource;
+ mGlobal = global;
+ }
+
+ private void perform() {
+ IResource resource = mGlobal ? mResource.getProject() : mResource;
+ LintEclipseContext.clearMarkers(resource);
+ }
+
+ public String getDisplayString() {
+ return mGlobal ? "Clear All Lint Markers" : "Clear Markers in This File Only";
+ }
+
+ // ---- Implements MarkerResolution2 ----
+
+ public String getLabel() {
+ return getDisplayString();
+ }
+
+ public void run(IMarker marker) {
+ perform();
+ }
+
+ public String getDescription() {
+ return getAdditionalProposalInfo();
+ }
+
+ // ---- Implements ICompletionProposal ----
+
+ public void apply(IDocument document) {
+ perform();
+ }
+
+ public Point getSelection(IDocument document) {
+ return null;
+ }
+
+ public String getAdditionalProposalInfo() {
+ StringBuilder sb = new StringBuilder(200);
+ if (mGlobal) {
+ sb.append("Clears all lint warning markers from the project.");
+ } else {
+ sb.append("Clears all lint warnings from this file.");
+ }
+ sb.append("<br><br>"); //$NON-NLS-1$
+ sb.append("This temporarily hides the problem, but does not suppress it. " +
+ "Running Lint again can bring the error back.");
+ if (AdtPrefs.getPrefs().isLintOnSave()) {
+ sb.append(' ');
+ sb.append("This will happen the next time the file is saved since lint-on-save " +
+ "is enabled. You can turn this off in the \"Lint Error Checking\" " +
+ "preference page.");
+ }
+
+ return sb.toString();
+ }
+
+ public Image getImage() {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ }
+
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+ }
+
+ private static class MoreInfoProposal implements ICompletionProposal, IMarkerResolution2 {
+ private final String mId;
+ private final String mMessage;
+
+ public MoreInfoProposal(String id, String message) {
+ mId = id;
+ mMessage = message;
+ }
+
+ private void perform() {
+ Issue issue = LintEclipseContext.getRegistry().getIssue(mId);
+ assert issue != null : mId;
+
+ StringBuilder sb = new StringBuilder(300);
+ sb.append(mMessage);
+ sb.append('\n').append('\n');
+ sb.append("Issue Explanation:");
+ sb.append('\n');
+ if (issue.getExplanation() != null) {
+ sb.append('\n');
+ sb.append(issue.getExplanation());
+ } else {
+ sb.append(issue.getDescription());
+ }
+
+ if (issue.getMoreInfo() != null) {
+ sb.append('\n').append('\n');
+ sb.append("More Information: ");
+ sb.append(issue.getMoreInfo());
+ }
+
+ MessageDialog.openInformation(AdtPlugin.getDisplay().getActiveShell(), "More Info",
+ sb.toString());
+ }
+
+ public String getDisplayString() {
+ return "Explain Issue";
+ }
+
+ // ---- Implements MarkerResolution2 ----
+
+ public String getLabel() {
+ return getDisplayString();
+ }
+
+ public void run(IMarker marker) {
+ perform();
+ }
+
+ public String getDescription() {
+ return getAdditionalProposalInfo();
+ }
+
+ // ---- Implements ICompletionProposal ----
+
+ public void apply(IDocument document) {
+ perform();
+ }
+
+ public Point getSelection(IDocument document) {
+ return null;
+ }
+
+ public String getAdditionalProposalInfo() {
+ return "Provides more information about this issue";
+ }
+
+ public Image getImage() {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
+ }
+
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
new file mode 100644
index 0000000..29d7275
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
@@ -0,0 +1,406 @@
+/*
+ * 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 static com.android.ide.eclipse.adt.internal.lint.LintEclipseContext.MARKER_CHECKID_PROPERTY;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutActionBar;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.detector.api.Issue;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IMarkerDelta;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+class LintListDialog extends TitleAreaDialog implements SelectionListener,
+ IResourceChangeListener {
+ private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$
+ private IFile mFile;
+ private Table mTable;
+ private Button mFixButton;
+ private Button mIgnoreButton;
+ private Button mShowButton;
+ private Text mDetailsText;
+ private Button mIgnoreTypeButton;
+ private TableViewer mTableViewer;
+
+ LintListDialog(Shell parentShell, IFile file) {
+ super(parentShell);
+ this.mFile = file;
+ }
+
+ @Override
+ protected Control createContents(Composite parent) {
+ Control contents = super.createContents(parent);
+ setTitle("Lint Warnings in Layout");
+ setMessage("Lint Errors found for the current layout:");
+ setTitleImage(AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE).createImage());
+ return contents;
+ }
+
+ @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite area = (Composite) super.createDialogArea(parent);
+ Composite container = new Composite(area, SWT.NONE);
+ container.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ container.setLayout(new GridLayout(2, false));
+ mTableViewer = new TableViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
+ mTable = mTableViewer.getTable();
+ mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 5));
+
+ TableViewerColumn messageViewerColumn = new TableViewerColumn(mTableViewer, SWT.FILL);
+ final TableColumn messageColumn = messageViewerColumn.getColumn();
+ messageColumn.setWidth(100);
+ messageColumn.setText("Message");
+
+ TableViewerColumn lineViewerColumn = new TableViewerColumn(mTableViewer, SWT.NONE);
+ final TableColumn lineColumn = lineViewerColumn.getColumn();
+ lineColumn.setWidth(100);
+ lineColumn.setText("Line");
+ lineColumn.setAlignment(SWT.RIGHT);
+
+ mTableViewer.setContentProvider(new ContentProvider());
+ mTableViewer.setLabelProvider(new LabelProvider());
+
+ mShowButton = new Button(container, SWT.NONE);
+ mShowButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mShowButton.setText("Show");
+ mShowButton.addSelectionListener(this);
+
+ mIgnoreButton = new Button(container, SWT.NONE);
+ mIgnoreButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mIgnoreButton.setText("Ignore");
+ mIgnoreButton.setEnabled(false);
+ mIgnoreButton.addSelectionListener(this);
+
+ mIgnoreTypeButton = new Button(container, SWT.NONE);
+ mIgnoreTypeButton.setText("Ignore Type");
+ mIgnoreTypeButton.addSelectionListener(this);
+
+ mFixButton = new Button(container, SWT.NONE);
+ mFixButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mFixButton.setText("Fix");
+ mFixButton.setEnabled(false);
+ mFixButton.addSelectionListener(this);
+
+ new Label(container, SWT.NONE);
+
+ mDetailsText = new Text(container, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP
+ | SWT.V_SCROLL | SWT.MULTI);
+ GridData gdText = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
+ gdText.heightHint = 80;
+ mDetailsText.setLayoutData(gdText);
+
+ IMarker[] markers = LintEclipseContext.getMarkers(mFile);
+ mTableViewer.setInput(markers);
+
+ mTable.setLinesVisible(true);
+ mTable.setHeaderVisible(true);
+ new Label(container, SWT.NONE);
+
+ mTable.addSelectionListener(this);
+
+ // Add a listener to resize the column to the full width of the
+ // table
+ mTable.addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ Rectangle r = mTable.getClientArea();
+ int availableWidth = r.width;
+
+ int categoryFixedSize = 50;
+ lineColumn.setWidth(categoryFixedSize);
+ availableWidth -= categoryFixedSize;
+
+ // Name absorbs everything else
+ messageColumn.setWidth(availableWidth);
+ }
+ });
+
+ if (mTable.getItemCount() > 0) {
+ mTable.select(0);
+ IMarker marker = (IMarker) mTable.getItem(0).getData();
+ selectMarker(marker);
+ }
+
+ ResourcesPlugin.getWorkspace().addResourceChangeListener(
+ this,
+ IResourceChangeEvent.POST_CHANGE
+ | IResourceChangeEvent.PRE_BUILD
+ | IResourceChangeEvent.POST_BUILD);
+
+
+ return area;
+ }
+
+ /**
+ * Create contents of the button bar.
+ */
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
+ }
+
+ /**
+ * Return the initial size of the dialog.
+ */
+ @Override
+ protected Point getInitialSize() {
+ return new Point(600, 400);
+ }
+
+ private void selectMarker(IMarker marker) {
+ String id = getId(marker);
+ DetectorRegistry registry = LintEclipseContext.getRegistry();
+ Issue issue = registry.getIssue(id);
+ String summary = issue.getDescription();
+ String explanation = issue.getExplanation();
+
+ StringBuilder sb = new StringBuilder(summary.length() + explanation.length() + 20);
+ sb.append(summary);
+ sb.append('\n').append('\n');
+ sb.append(explanation);
+ mDetailsText.setText(sb.toString());
+ }
+
+ private String getId(IMarker marker) {
+ try {
+ return (String) marker.getAttribute(MARKER_CHECKID_PROPERTY);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ private void showMarker(IMarker marker) {
+ IRegion region = null;
+ try {
+ int start = marker.getAttribute(IMarker.CHAR_START, -1);
+ int end = marker.getAttribute(IMarker.CHAR_END, -1);
+ if (start >= 0 && end >= 0) {
+ region = new org.eclipse.jface.text.Region(start, end - start);
+ }
+
+ AdtPlugin.openFile(mFile, region, true /* showEditorTab */);
+ } catch (PartInitException ex) {
+ AdtPlugin.log(ex, null);
+ }
+ }
+
+ @Override
+ public boolean close() {
+ ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
+
+ return super.close();
+ }
+
+ // ---- Implements SelectionListener ----
+
+ public void widgetSelected(SelectionEvent e) {
+ Object source = e.getSource();
+ if (source == mTable) {
+ TableItem item = (TableItem) e.item;
+ IMarker marker = (IMarker) item.getData();
+ selectMarker(marker);
+ } else if (source == mShowButton) {
+ int index = mTable.getSelectionIndex();
+ if (index != -1) {
+ IMarker marker = (IMarker) mTable.getItem(index).getData();
+ showMarker(marker);
+ }
+ } else if (source == mIgnoreTypeButton) {
+ int index = mTable.getSelectionIndex();
+ if (index != -1) {
+ IMarker marker = (IMarker) mTable.getItem(index).getData();
+ String id = getId(marker);
+ if (id != null) {
+ mTableViewer.setInput(null);
+ mTableViewer.refresh();
+ LintFixGenerator.suppressDetector(id);
+ }
+ }
+ }
+ }
+
+ public void widgetDefaultSelected(SelectionEvent e) {
+ Object source = e.getSource();
+ if (source == mTable) {
+ // Jump to editor
+ TableItem item = (TableItem) e.item;
+ IMarker marker = (IMarker) item.getData();
+ showMarker(marker);
+ close();
+ }
+ }
+
+ // ---- Implements IResourceChangeListener ----
+
+ public void resourceChanged(IResourceChangeEvent event) {
+ IMarkerDelta[] deltas = event.findMarkerDeltas(AdtConstants.MARKER_LINT, true);
+ if (deltas.length > 0) {
+ for (IMarkerDelta delta : deltas) {
+ if (delta.getResource().equals(mFile)) {
+ Shell shell = LintListDialog.this.getShell();
+ if (shell == null) {
+ return;
+ }
+ Display display = shell.getDisplay();
+ if (display == null) {
+ return;
+ }
+ display.asyncExec(new Runnable() {
+ public void run() {
+ mTableViewer.setInput(null);
+ IMarker[] markers = LintEclipseContext.getMarkers(mFile);
+ if (markers.length == 0) {
+ IEditorPart active = AdtUtils.getActiveEditor();
+ if (active instanceof LayoutEditor) {
+ LayoutEditor editor = (LayoutEditor) active;
+ if (mFile.equals(editor.getInputFile())) {
+ GraphicalEditorPart g = editor.getGraphicalEditor();
+ LayoutActionBar bar = g.getLayoutActionBar();
+ bar.updateErrorIndicator();
+ }
+ }
+
+ close();
+ return;
+ }
+ mTableViewer.setInput(markers);
+ mTableViewer.refresh();
+ }
+ });
+ return;
+ }
+ }
+ }
+ }
+
+
+ private class ContentProvider implements IStructuredContentProvider {
+ public Object[] getElements(Object inputElement) {
+ if (inputElement == null) {
+ return new IMarker[0];
+ }
+
+ return (IMarker[]) inputElement;
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+ }
+
+ private class LabelProvider implements ITableLabelProvider {
+ public Image getColumnImage(Object element, int columnIndex) {
+ if (columnIndex != 0) {
+ return null;
+ }
+
+ IMarker marker = (IMarker) element;
+ int severity = marker.getAttribute(IMarker.SEVERITY, 0);
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ switch (severity) {
+ case IMarker.SEVERITY_ERROR:
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
+ case IMarker.SEVERITY_WARNING:
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ case IMarker.SEVERITY_INFO:
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
+ default:
+ return null;
+ }
+ }
+
+ public String getColumnText(Object element, int columnIndex) {
+ IMarker marker = (IMarker) element;
+ try {
+ switch (columnIndex) {
+ case 0:
+ return (String) marker.getAttribute(IMarker.MESSAGE);
+ case 1: {
+ int line = marker.getAttribute(IMarker.LINE_NUMBER, 0);
+ return Integer.toString(line);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public void addListener(ILabelProviderListener listener) {
+ }
+
+ public void removeListener(ILabelProviderListener listener) {
+ }
+
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ public void dispose() {
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintRunner.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintRunner.java
new file mode 100644
index 0000000..9bfbe29
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintRunner.java
@@ -0,0 +1,183 @@
+/*
+ * 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.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.api.Lint;
+import com.android.tools.lint.detector.api.Scope;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobManager;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.swt.widgets.Shell;
+
+import java.io.File;
+import java.util.Collections;
+
+/**
+ * Eclipse implementation for running lint on workspace files and projects.
+ */
+public class LintRunner {
+ static final String MARKER_CHECKID_PROPERTY = "checkid"; //$NON-NLS-1$
+
+ /**
+ * Runs lint and updates the markers, and waits for the result. Returns
+ * true if fatal errors were found.
+ *
+ * @param resource the resource (project, folder or file) to be analyzed
+ * @param doc the associated document, if known, or null
+ * @return true if any fatal errors were encountered.
+ */
+ public static boolean runLint(IResource resource, IDocument doc) {
+ CheckFileJob job = (CheckFileJob) startLint(resource, doc);
+ try {
+ job.join();
+ return job.isFatal();
+ } catch (InterruptedException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return false;
+ }
+
+ /**
+ * Runs lint and updates the markers. Does not wait for the job to
+ * finish - just returns immediately.
+ *
+ * @param resource the resource (project, folder or file) to be analyzed
+ * @param doc the associated document, if known, or null
+ * @return the job running lint in the background.
+ */
+ public static Job startLint(IResource resource, IDocument doc) {
+ if (resource != null) {
+ cancelCurrentJobs();
+
+ CheckFileJob job = new CheckFileJob(resource, doc);
+ job.schedule();
+ return job;
+ }
+
+ return null;
+ }
+
+ /**
+ * Run Lint for an Export APK action. If it succeeds (no fatal errors)
+ * returns true, and if it fails it will display an error message and return
+ * false.
+ *
+ * @param shell the parent shell to show error messages in
+ * @param project the project to run lint on
+ * @return true if the lint run succeeded with no fatal errors
+ */
+ public static boolean runLintOnExport(Shell shell, IProject project) {
+ if (AdtPrefs.getPrefs().isLintOnExport()) {
+ boolean fatal = LintRunner.runLint(project, null);
+ if (fatal) {
+ MessageDialog.openWarning(shell,
+ "Export Aborted",
+ "Export aborted because fatal lint errors were found. These " +
+ "are listed in the Problems view. Either fix these before " +
+ "running Export again, or turn off \"Run full error check " +
+ "when exporting app\" in the Android > Lint Error Checking " +
+ "preference page.");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /** Cancels the current lint jobs, if any */
+ static void cancelCurrentJobs() {
+ // Cancel any current running jobs first
+ IJobManager jobManager = Job.getJobManager();
+ Job[] jobs = jobManager.find(CheckFileJob.FAMILY_RUN_LINT);
+ for (Job job : jobs) {
+ job.cancel();
+ }
+ }
+
+ private static final class CheckFileJob extends Job {
+ /** Job family */
+ private static final Object FAMILY_RUN_LINT = new Object();
+ private final IResource mResource;
+ private final IDocument mDocument;
+ private Lint mLint;
+ private boolean mFatal;
+
+ private CheckFileJob(IResource resource, IDocument doc) {
+ super("Running Android Lint");
+ this.mResource = resource;
+ this.mDocument = doc;
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return family == FAMILY_RUN_LINT;
+ }
+
+ @Override
+ protected void canceling() {
+ super.canceling();
+ if (mLint != null) {
+ mLint.cancel();
+ }
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
+ LintEclipseContext.clearMarkers(mResource);
+
+ DetectorRegistry registry = LintEclipseContext.getRegistry();
+ LintEclipseContext toolContext = new LintEclipseContext(registry, mResource,
+ mDocument);
+ File file = AdtUtils.getAbsolutePath(mResource).toFile();
+ Scope scope = (mResource instanceof IProject) ? Scope.PROJECT :
+ (mResource instanceof IFolder) ? Scope.RESOURCES : Scope.SINGLE_FILE;
+ mLint = new Lint(registry, toolContext, scope);
+ mLint.analyze(Collections.singletonList(file));
+ mFatal = toolContext.isFatal();
+ return Status.OK_STATUS;
+ } catch (Exception e) {
+ return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
+ "Failed", e); //$NON-NLS-1$
+ } finally {
+ if (monitor != null) {
+ monitor.done();
+ }
+ }
+ }
+
+ /**
+ * Returns true if a fatal error was encountered
+ */
+ boolean isFatal() {
+ return mFatal;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
new file mode 100644
index 0000000..e6a09fd
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
@@ -0,0 +1,139 @@
+/*
+ * 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.AdtPlugin;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IActionDelegate;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+/** Action which runs Lint on the current project */
+public class RunLintAction implements IActionDelegate {
+
+ private ISelection mSelection;
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ mSelection = selection;
+ }
+
+ public void run(IAction action) {
+ final IProject project = RunLintAction.getSelectedProject(mSelection);
+ if (project != null) {
+ Job job = LintRunner.startLint(project, null);
+ if (job != null) {
+ job.addJobChangeListener(new FinishListener(project));
+ }
+
+ // Show problems view since that's where the results are listed
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (window != null) {
+ IWorkbenchPage page = window.getActivePage();
+ if (page != null) {
+ try {
+ String id = "org.eclipse.ui.views.ProblemView"; //$NON-NLS-1$
+ page.showView(id);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, "Cannot open Problems View");
+ }
+ }
+ }
+ }
+ }
+
+ /** Returns the selected project, if there is exactly one selected project */
+ static IProject getSelectedProject(ISelection selection) {
+ if (selection instanceof IStructuredSelection) {
+ IStructuredSelection structuredSelection = (IStructuredSelection) selection;
+ // get the unique selected item.
+ if (structuredSelection.size() == 1) {
+ Object element = structuredSelection.getFirstElement();
+
+ // get the project object from it.
+ IProject project = null;
+ if (element instanceof IProject) {
+ project = (IProject) element;
+ } else if (element instanceof IAdaptable) {
+ project = (IProject) ((IAdaptable) element).getAdapter(IProject.class);
+ }
+
+ return project;
+ }
+ }
+
+ return null;
+ }
+
+ private final class FinishListener extends JobChangeAdapter implements Runnable {
+ private final IProject mProject;
+
+ private FinishListener(IProject project) {
+ this.mProject = project;
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ Display display = AdtPlugin.getDisplay();
+ if (display.getThread() != Thread.currentThread()) {
+ display.asyncExec(this);
+ } else {
+ run();
+ }
+ }
+
+ // ---- Implements Runnable ----
+
+ public void run() {
+ IMarker[] markers = LintEclipseContext.getMarkers(mProject);
+ int warningCount = 0;
+ int errorCount = 0;
+ for (IMarker marker : markers) {
+ int severity = marker.getAttribute(IMarker.SEVERITY, -1);
+ if (severity == IMarker.SEVERITY_ERROR) {
+ errorCount++;
+ } else if (severity == IMarker.SEVERITY_WARNING) {
+ warningCount++;
+ }
+ }
+ String message = null;
+ if (errorCount == 0 && warningCount == 0) {
+ message = "Finished running lint, no problems found.";
+ } else {
+ message = String.format(
+ "Finished running lint, found %1$d errors and %2$d warnings.\n" +
+ "Results are shown in the Problems View.\n" +
+ "Warnings can be configured in the \"Lint Error Checking\" preferences page.",
+ errorCount, warningCount);
+ }
+ MessageDialog.openInformation(AdtPlugin.getDisplay().getActiveShell(),
+ "Finished Checking", message);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
index 577c431..b35af5c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
@@ -61,7 +61,11 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
public final static String PREFS_ONE_ATTR_PER_LINE = AdtPlugin.PLUGIN_ID + ".oneAttrPerLine"; //$NON-NLS-1$
public final static String PREFS_SPACE_BEFORE_CLOSE = AdtPlugin.PLUGIN_ID + ".spaceBeforeClose"; //$NON-NLS-1$
public final static String PREFS_FORMAT_ON_SAVE = AdtPlugin.PLUGIN_ID + ".formatOnSave"; //$NON-NLS-1$
+ public final static String PREFS_LINT_ON_SAVE = AdtPlugin.PLUGIN_ID + ".lintOnSave"; //$NON-NLS-1$
+ public final static String PREFS_LINT_ON_EXPORT = AdtPlugin.PLUGIN_ID + ".lintOnExport"; //$NON-NLS-1$
public final static String PREFS_ATTRIBUTE_SORT = AdtPlugin.PLUGIN_ID + ".attrSort"; //$NON-NLS-1$
+ public final static String PREFS_DISABLED_ISSUES = AdtPlugin.PLUGIN_ID + ".disabedIssues"; //$NON-NLS-1$
+ public final static String PREFS_LINT_SEVERITIES = AdtPlugin.PLUGIN_ID + ".lintSeverities"; //$NON-NLS-1$
/** singleton instance */
private final static AdtPrefs sThis = new AdtPrefs();
@@ -88,6 +92,8 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
private boolean mOneAttributeOnFirstLine;
private boolean mSpaceBeforeClose;
private boolean mFormatOnSave;
+ private boolean mLintOnSave;
+ private boolean mLintOnExport;
private AttributeSortOrder mAttributeSort;
public static enum BuildVerbosity {
@@ -225,6 +231,14 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
if (property == null || PREFS_FORMAT_ON_SAVE.equals(property)) {
mFormatOnSave = mStore.getBoolean(PREFS_FORMAT_ON_SAVE);
}
+
+ if (property == null || PREFS_LINT_ON_SAVE.equals(property)) {
+ mLintOnSave = mStore.getBoolean(PREFS_LINT_ON_SAVE);
+ }
+
+ if (property == null || PREFS_LINT_ON_EXPORT.equals(property)) {
+ mLintOnExport = mStore.getBoolean(PREFS_LINT_ON_EXPORT);
+ }
}
/**
@@ -334,6 +348,26 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
return mFormatOnSave;
}
+ public boolean isLintOnSave() {
+ return mLintOnSave;
+ }
+
+ public void setLintOnSave(boolean on) {
+ mLintOnSave = on;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ store.setValue(PREFS_LINT_ON_SAVE, on);
+ }
+
+ public boolean isLintOnExport() {
+ return mLintOnExport;
+ }
+
+ public void setLintOnExport(boolean on) {
+ mLintOnExport = on;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ store.setValue(PREFS_LINT_ON_EXPORT, on);
+ }
+
public boolean getBuildForceErrorOnNativeLibInJar() {
return mBuildForceErrorOnNativeLibInJar;
}
@@ -407,6 +441,8 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
store.setDefault(PREFS_USE_CUSTOM_XML_FORMATTER, true);
store.setDefault(PREFS_ONE_ATTR_PER_LINE, true);
store.setDefault(PREFS_SPACE_BEFORE_CLOSE, true);
+ store.setDefault(PREFS_LINT_ON_SAVE, true);
+ store.setDefault(PREFS_LINT_ON_EXPORT, true);
// Defaults already handled; no need to write into map:
//store.setDefault(PREFS_ATTRIBUTE_SORT, AttributeSortOrder.LOGICAL.key);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
new file mode 100644
index 0000000..6c55fc0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
@@ -0,0 +1,493 @@
+/*
+ * 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.preferences;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.lint.LintEclipseContext;
+import com.android.ide.eclipse.adt.internal.lint.LintRunner;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.sdklib.SdkConstants;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ComboBoxViewerCellEditor;
+import org.eclipse.jface.viewers.EditingSupport;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.PlatformUI;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Preference page for configuring Lint preferences */
+public class LintPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
+ private static final int CATEGORY_COLUMN_WIDTH = 60;
+ private static final int SEVERITY_COLUMN_WIDTH = 80;
+ private static final int ID_COLUMN_WIDTH = 80;
+
+ private Map<Issue, Severity> mSeverities = new HashMap<Issue, Severity>();
+ private LintEclipseContext mContext;
+ private DetectorRegistry mRegistry;
+
+ private Table mTable;
+ private Text mDetailsText;
+ private Text mSuppressedText;
+ private Button mCheckFileCheckbox;
+ private Button mCheckExportCheckbox;
+
+ /**
+ * Create the preference page.
+ */
+ public LintPreferencePage() {
+ setPreferenceStore(AdtPlugin.getDefault().getPreferenceStore());
+ }
+
+ @Override
+ public Control createContents(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ container.setLayout(new GridLayout(2, false));
+
+ mCheckFileCheckbox = new Button(container, SWT.CHECK);
+ mCheckFileCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mCheckFileCheckbox.setSelection(true);
+ mCheckFileCheckbox.setText("When saving files, check for errors");
+
+ mCheckExportCheckbox = new Button(container, SWT.CHECK);
+ mCheckExportCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mCheckExportCheckbox.setSelection(true);
+ mCheckExportCheckbox.setText("Run full error check when exporting app");
+
+ Label label = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
+ label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1));
+
+ Label checkListLabel = new Label(container, SWT.NONE);
+ checkListLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ checkListLabel.setText("Enabled checks:");
+
+ CheckboxTableViewer checkboxTableViewer = CheckboxTableViewer.newCheckList(
+ container, SWT.BORDER | SWT.FULL_SELECTION);
+ mTable = checkboxTableViewer.getTable();
+ mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+
+ TableViewerColumn column1 = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
+ final TableColumn idColumn = column1.getColumn();
+ idColumn.setWidth(100);
+ idColumn.setText("Id");
+
+ TableViewerColumn column2 = new TableViewerColumn(checkboxTableViewer, SWT.FILL);
+ final TableColumn nameColumn = column2.getColumn();
+ nameColumn.setWidth(100);
+ nameColumn.setText("Name");
+
+ TableViewerColumn column3 = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
+ final TableColumn categoryColumn = column3.getColumn();
+ categoryColumn.setWidth(100);
+ categoryColumn.setText("Category");
+
+ TableViewerColumn column4 = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
+ final TableColumn severityColumn = column4.getColumn();
+ severityColumn.setWidth(100);
+ severityColumn.setText("Severity");
+ column4.setEditingSupport(new SeverityEditingSupport(column4.getViewer()));
+
+ checkboxTableViewer.setContentProvider(new ContentProvider());
+ checkboxTableViewer.setLabelProvider(new LabelProvider());
+
+ mDetailsText = new Text(container, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP |
+ SWT.V_SCROLL | SWT.MULTI);
+ GridData gdText = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1);
+ gdText.heightHint = 80;
+ mDetailsText.setLayoutData(gdText);
+
+ mRegistry = LintEclipseContext.getRegistry();
+ mContext = new LintEclipseContext(mRegistry, null, null);
+
+ List<Issue> issues = mRegistry.getIssues();
+ for (Issue issue : issues) {
+ mSeverities.put(issue, mContext.getSeverity(issue));
+ }
+ checkboxTableViewer.setInput(issues);
+
+ mTable.setLinesVisible(true);
+ mTable.setHeaderVisible(true);
+
+ Label suppressLabel = new Label(container, SWT.NONE);
+ suppressLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ suppressLabel.setText("Suppressed Warnings:");
+
+ mSuppressedText = new Text(container, SWT.BORDER);
+ // Default path relative to the project
+ mSuppressedText.setText("${project}/lint-suppressed.xml"); //$NON-NLS-1$
+ mSuppressedText.setEnabled(false);
+ mSuppressedText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+ mTable.addSelectionListener(new SelectionListener() {
+ public void widgetDefaultSelected(SelectionEvent e) {
+ widgetSelected(e);
+ }
+
+ public void widgetSelected(SelectionEvent e) {
+ TableItem item = (TableItem) e.item;
+ Issue issue = (Issue) item.getData();
+ String summary = issue.getDescription();
+ String explanation = issue.getExplanation();
+
+ StringBuilder sb = new StringBuilder(summary.length() + explanation.length() + 20);
+ sb.append(summary);
+ sb.append('\n').append('\n');
+ sb.append(explanation);
+ mDetailsText.setText(sb.toString());
+ }
+ });
+
+ // Add a listener to resize the column to the full width of the table
+ mTable.addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ Rectangle r = mTable.getClientArea();
+ int availableWidth = r.width;
+
+ // On the Mac, the width of the checkbox column is not included (and checkboxes
+ // are shown if mAllowSelection=true). Subtract this size from the available
+ // width to be distributed among the columns.
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ availableWidth -= getCheckboxWidth(e.display.getActiveShell());
+ }
+
+ idColumn.setWidth(ID_COLUMN_WIDTH);
+ availableWidth -= ID_COLUMN_WIDTH;
+
+ severityColumn.setWidth(SEVERITY_COLUMN_WIDTH);
+ availableWidth -= SEVERITY_COLUMN_WIDTH;
+
+ categoryColumn.setWidth(CATEGORY_COLUMN_WIDTH);
+ availableWidth -= CATEGORY_COLUMN_WIDTH;
+
+ // Name absorbs everything else
+ nameColumn.setWidth(availableWidth);
+ }
+ });
+
+ loadSettings();
+
+ return container;
+ }
+
+ /**
+ * Initialize the preference page.
+ */
+ public void init(IWorkbench workbench) {
+ // Initialize the preference page
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ }
+
+ /** Cache for {@link #getCheckboxWidth()} */
+ private static int sCheckboxWidth = -1;
+
+ /** Computes the width of a checkbox */
+ private int getCheckboxWidth(Shell shell) {
+ if (sCheckboxWidth == -1) {
+ Shell tempShell = new Shell(shell, SWT.NO_TRIM);
+ Button checkBox = new Button(tempShell, SWT.CHECK);
+ sCheckboxWidth = checkBox.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+ tempShell.dispose();
+ }
+
+ return sCheckboxWidth;
+ }
+
+ @Override
+ public boolean performOk() {
+ storeSettings();
+ return true;
+ }
+
+ private void loadSettings() {
+ AdtPrefs prefs = AdtPrefs.getPrefs();
+ mCheckFileCheckbox.setSelection(prefs.isLintOnSave());
+ mCheckExportCheckbox.setSelection(prefs.isLintOnExport());
+
+ IPreferenceStore store = getPreferenceStore();
+ Set<String> excluded = new HashSet<String>();
+ String ids = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
+ if (ids != null && ids.length() > 0) {
+ for (String s : ids.split(",")) { //$NON-NLS-1$
+ excluded.add(s);
+ }
+ }
+ TableItem[] itemList = mTable.getItems();
+ for (int i = 0; i < itemList.length; i++) {
+ Issue issue = (Issue) itemList[i].getData();
+ itemList[i].setChecked(!excluded.contains(issue.getId()));
+ }
+ }
+
+ private void storeSettings() {
+ // Lint on Save, Lint on Export
+ AdtPrefs prefs = AdtPrefs.getPrefs();
+ prefs.setLintOnExport(mCheckExportCheckbox.getSelection());
+ prefs.setLintOnSave(mCheckFileCheckbox.getSelection());
+
+ // Severities
+ mContext.setSeverities(mSeverities);
+
+ // Excluded checks
+ StringBuilder sb = new StringBuilder();
+ TableItem[] itemList = mTable.getItems();
+ for (int i = 0; i < itemList.length; i++) {
+ if (!itemList[i].getChecked()) {
+ Issue check = (Issue) itemList[i].getData();
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append(check.getId());
+ }
+ }
+ String value = sb.toString();
+ if (value.length() == 0) {
+ value = null;
+ }
+
+ IPreferenceStore store = getPreferenceStore();
+ String previous = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
+ boolean unchanged = (previous != null && previous.equals(value)) || (previous == value);
+ if (!unchanged) {
+ if (value == null) {
+ store.setToDefault(AdtPrefs.PREFS_DISABLED_ISSUES);
+ } else {
+ store.setValue(AdtPrefs.PREFS_DISABLED_ISSUES, value);
+ }
+
+ // Ask user whether we should re-run the rules.
+ MessageDialog dialog = new MessageDialog(
+ null, "Lint Settings Have Changed", null,
+ "The list of enabled checks has changed. Would you like to run lint now " +
+ "to update the results?",
+ MessageDialog.QUESTION,
+ new String[] {
+ "Yes", "No"
+ },
+ 0); // yes is the default
+ int result = dialog.open();
+ if (result == 0) {
+ // Run lint on all the open Android projects
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IProject[] projects = workspace.getRoot().getProjects();
+ for (IProject project : projects) {
+ if (project.isOpen() && BaseProjectHelper.isAndroidProject(project)) {
+ LintRunner.startLint(project, null);
+ }
+ }
+ }
+ }
+ }
+
+ private static class ContentProvider implements IStructuredContentProvider {
+ public Object[] getElements(Object inputElement) {
+ @SuppressWarnings("unchecked")
+ List<Issue> issues = (List<Issue>) inputElement;
+ return issues.toArray();
+ }
+ public void dispose() {
+ }
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+ }
+
+ private class LabelProvider implements ITableLabelProvider {
+ // TODO: add IColorProvider ?
+
+ public void addListener(ILabelProviderListener listener) {
+ }
+
+ public void dispose() {
+ }
+
+ public boolean isLabelProperty(Object element, String property) {
+ return true;
+ }
+
+ public void removeListener(ILabelProviderListener listener) {
+ }
+
+ public Image getColumnImage(Object element, int columnIndex) {
+ if (columnIndex == 3) {
+ Issue issue = (Issue) element;
+ Severity severity = mSeverities.get(issue);
+ if (severity == null) {
+ return null;
+ }
+
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ switch (severity) {
+ case ERROR:
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
+ case WARNING:
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ case INFORMATIONAL:
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
+ case IGNORE:
+ // TBD: Is this icon okay?
+ return sharedImages.getImage(ISharedImages.IMG_ELCL_REMOVE_DISABLED);
+ }
+ }
+ return null;
+ }
+
+ public String getColumnText(Object element, int columnIndex) {
+ Issue issue = (Issue) element;
+ switch (columnIndex) {
+ case 0:
+ return issue.getId();
+ case 1:
+ return issue.getDescription();
+ case 2:
+ return issue.getCategory();
+ case 3: {
+ Severity severity = mSeverities.get(issue);
+ if (severity == null) {
+ return null;
+ }
+ return severity.getDescription();
+ }
+ }
+
+ return null;
+ }
+ }
+
+ /** Editing support for the severity column */
+ private class SeverityEditingSupport extends EditingSupport
+ implements ILabelProvider, IStructuredContentProvider {
+ private final ComboBoxViewerCellEditor mCellEditor;
+
+ @SuppressWarnings("deprecation") // Can't use the new form of setContentProvider until 3.7
+ private SeverityEditingSupport(ColumnViewer viewer) {
+ super(viewer);
+ Composite control = (Composite) getViewer().getControl();
+ mCellEditor = new ComboBoxViewerCellEditor(control, SWT.READ_ONLY);
+ mCellEditor.setLabelProvider(this);
+ mCellEditor.setContenProvider(this);
+ mCellEditor.setInput(Severity.values());
+ }
+
+ @Override
+ protected boolean canEdit(Object element) {
+ return true;
+ }
+
+ @Override
+ protected Object getValue(Object element) {
+ if (element instanceof Issue) {
+ Issue issue = (Issue) element;
+ Severity severity = mSeverities.get(issue);
+ return severity;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void setValue(Object element, Object value) {
+ if (element instanceof Issue && value instanceof Severity) {
+ Issue issue = (Issue) element;
+ Severity newValue = (Severity) value;
+ mSeverities.put(issue, newValue);
+ getViewer().update(element, null);
+ }
+ }
+
+ @Override
+ protected CellEditor getCellEditor(Object element) {
+ return mCellEditor;
+ }
+
+ // ---- Implements ILabelProvider ----
+
+ public String getText(Object element) {
+ return ((Severity) element).getDescription();
+ }
+
+ public void addListener(ILabelProviderListener listener) {
+ }
+
+ public void dispose() {
+ }
+
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ public void removeListener(ILabelProviderListener listener) {
+ }
+
+ public Image getImage(Object element) {
+ return null;
+ }
+
+ // ---- Implements IStructuredContentProvider ----
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ public Object[] getElements(Object inputElement) {
+ return Severity.values();
+ }
+ }
+
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
index f9ef11b..41cf397 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
@@ -16,6 +16,7 @@
package com.android.ide.eclipse.adt.internal.wizards.actions;
+import com.android.ide.eclipse.adt.internal.lint.LintRunner;
import com.android.ide.eclipse.adt.internal.project.ExportHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -59,6 +60,10 @@ public class ExportAction implements IObjectActionDelegate {
// and finally do the action
if (project != null) {
+ if (!LintRunner.runLintOnExport(mShell, project)) {
+ return;
+ }
+
ProjectState state = Sdk.getProjectState(project);
if (state.isLibrary()) {
MessageDialog.openError(mShell, "Android Export",
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java
index dc5dbeb..dfd05c0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportWizardAction.java
@@ -16,6 +16,7 @@
package com.android.ide.eclipse.adt.internal.wizards.actions;
+import com.android.ide.eclipse.adt.internal.lint.LintRunner;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard;
@@ -61,6 +62,11 @@ public class ExportWizardAction implements IObjectActionDelegate {
// and finally do the action
if (project != null) {
+ if (!LintRunner.runLintOnExport(
+ mWorkbench.getActiveWorkbenchWindow().getShell(), project)) {
+ return;
+ }
+
ProjectState state = Sdk.getProjectState(project);
if (state.isLibrary()) {
MessageDialog.openError(mWorkbench.getDisplay().getActiveShell(),
diff --git a/eclipse/scripts/create_adt_symlinks.sh b/eclipse/scripts/create_adt_symlinks.sh
index 9298ac0..97ed877 100755
--- a/eclipse/scripts/create_adt_symlinks.sh
+++ b/eclipse/scripts/create_adt_symlinks.sh
@@ -21,6 +21,10 @@ LIBS="sdkstats androidprefs common layoutlib_api ide_common rule_api ninepatch s
echo "make java libs ..."
make -j3 showcommands $LIBS || die "ADT: Fail to build one of $LIBS."
+# Add in lint_api and lint_checks: Not build targets but are copy targets
+LIBS="$LIBS lint_api lint_checks"
+make -j3 showcommands lint || die "ADT: Fail to build one of $LIBS."
+
echo "Copying java libs to $DEST"
# Prebuilts required by sdklib & co, to be linked/copied in the ADT libs folder
diff --git a/layoutopt/app/src/com/android/layoutopt/cli/Main.java b/layoutopt/app/src/com/android/layoutopt/cli/Main.java
index 0275aa0..ed937d5 100644
--- a/layoutopt/app/src/com/android/layoutopt/cli/Main.java
+++ b/layoutopt/app/src/com/android/layoutopt/cli/Main.java
@@ -40,10 +40,13 @@ public class Main {
Parameters p = checkParameters(args);
if (!p.valid) {
displayHelpMessage();
+ displayObsoleteMessage();
exit();
}
analyzeFiles(p.files);
+
+ displayObsoleteMessage();
}
private static void analyzeFiles(File[] files) {
@@ -80,6 +83,11 @@ public class Main {
System.out.println("usage: layoutopt <directories/files to analyze>");
}
+ /** Layoutopt is obsolete; inform user */
+ private static void displayObsoleteMessage() {
+ System.err.println("\"layoutopt\" is obsolete; use \"lint\" instead which includes layout analysis.");
+ }
+
/**
* Builds a valid Parameters object. Parses the paramters if necessary
* and checks for errors.
diff --git a/lint/.gitignore b/lint/.gitignore
new file mode 100644
index 0000000..99d9ac8
--- /dev/null
+++ b/lint/.gitignore
@@ -0,0 +1,4 @@
+cli/bin
+libs/lint_api/bin
+libs/lint_checks/bin
+libs/lint_checks/tests/bin
diff --git a/lint/Android.mk b/lint/Android.mk
new file mode 100644
index 0000000..8a99bd7
--- /dev/null
+++ b/lint/Android.mk
@@ -0,0 +1,5 @@
+# Copyright 2011 The Android Open Source Project
+#
+LINT_LOCAL_DIR := $(call my-dir)
+include $(LINT_LOCAL_DIR)/libs/Android.mk
+include $(LINT_LOCAL_DIR)/cli/Android.mk
diff --git a/lint/MODULE_LICENSE_APACHE2 b/lint/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lint/MODULE_LICENSE_APACHE2
diff --git a/lint/cli/.classpath b/lint/cli/.classpath
new file mode 100644
index 0000000..f6efb87
--- /dev/null
+++ b/lint/cli/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/lint-checks"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/lint/cli/.project b/lint/cli/.project
new file mode 100644
index 0000000..2809612
--- /dev/null
+++ b/lint/cli/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>lint-cli</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/lint/cli/.settings/org.eclipse.jdt.core.prefs b/lint/cli/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e755df2
--- /dev/null
+++ b/lint/cli/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,71 @@
+#Thu Jun 09 12:26:44 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/lint/cli/Android.mk b/lint/cli/Android.mk
new file mode 100644
index 0000000..5b99dc4
--- /dev/null
+++ b/lint/cli/Android.mk
@@ -0,0 +1,22 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+
+LOCAL_JAR_MANIFEST := etc/manifest.txt
+
+# If the dependency list is changed, etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+ lint_api \
+ lint_checks
+LOCAL_MODULE := lint
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/lint/cli/NOTICE b/lint/cli/NOTICE
new file mode 100644
index 0000000..becc120
--- /dev/null
+++ b/lint/cli/NOTICE
@@ -0,0 +1,190 @@
+
+ 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/lint/cli/etc/Android.mk b/lint/cli/etc/Android.mk
new file mode 100644
index 0000000..987b452
--- /dev/null
+++ b/lint/cli/etc/Android.mk
@@ -0,0 +1,10 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_EXECUTABLES := lint
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/lint/cli/etc/lint b/lint/cli/etc/lint
new file mode 100755
index 0000000..2b53df6
--- /dev/null
+++ b/lint/cli/etc/lint
@@ -0,0 +1,72 @@
+#!/bin/bash
+# Copyright 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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=lint.jar
+frameworkdir="$progdir"
+libdir="$progdir"
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/tools/lib
+ libdir=`dirname "$progdir"`/tools/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/framework
+ libdir=`dirname "$progdir"`/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ echo `basename "$prog"`": can't find $jarfile"
+ exit 1
+fi
+
+# Check args.
+if [ debug = "$1" ]; then
+ # add this in for debugging
+ java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+ shift 1
+else
+ java_debug=
+fi
+
+javaCmd="java"
+
+jarpath="$frameworkdir/$jarfile"
+
+exec "$javaCmd" \
+ -Xmx256M $os_opts $java_debug \
+ -Dcom.android.tools.lint.bindir="$progdir" \
+ -classpath "$jarpath" \
+ com.android.tools.lint.Main "$@"
diff --git a/lint/cli/etc/lint.bat b/lint/cli/etc/lint.bat
new file mode 100755
index 0000000..41e52d8
--- /dev/null
+++ b/lint/cli/etc/lint.bat
@@ -0,0 +1,55 @@
+@echo off
+rem Copyright (C) 2011 The Android Open Source Project
+rem
+rem Licensed under the Apache License, Version 2.0 (the "License");
+rem you may not use this file except in compliance with the License.
+rem You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+rem don't modify the caller's environment
+setlocal
+
+rem Set up prog to be the path of this script, including following symlinks,
+rem and set up progdir to be the fully-qualified pathname of its directory.
+set prog=%~f0
+
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
+
+rem Get the CWD as a full path with short names only (without spaces)
+for %%i in ("%cd%") do set prog_dir=%%~fsi
+
+rem Check we have a valid Java.exe in the path.
+set java_exe=
+call lib\find_java.bat
+if not defined java_exe goto :EOF
+
+set jarfile=lint.jar
+set frameworkdir=
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=lib\
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=..\framework\
+
+:JarFileOk
+
+if debug NEQ "%1" goto NoDebug
+ set java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+ shift 1
+:NoDebug
+
+set jarpath=%frameworkdir%%jarfile%
+set javaextdirs=%frameworkdir%
+
+call %java_exe% %java_debug% -Dcom.android.tools.lint.bindir=%prog_dir% -classpath "%jarpath%" com.android.tools.lint.Main %*
+
diff --git a/lint/cli/etc/manifest.txt b/lint/cli/etc/manifest.txt
new file mode 100644
index 0000000..e75087d
--- /dev/null
+++ b/lint/cli/etc/manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: com.android.tools.lint.Main
+Class-Path: lint_api.jar lint_checks.jar
diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java
new file mode 100644
index 0000000..9e8dc28
--- /dev/null
+++ b/lint/cli/src/com/android/tools/lint/Main.java
@@ -0,0 +1,274 @@
+/*
+ * 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;
+
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.api.Lint;
+import com.android.tools.lint.api.ToolContext;
+import com.android.tools.lint.checks.BuiltinDetectorRegistry;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Command line driver for the rules framework
+ * <p>
+ * TODO:
+ * <ul>
+ * <li>Offer priority or category sorting
+ * <li>Offer suppressing violations
+ * </ul>
+ */
+public class Main implements ToolContext {
+ private static final int ERRNO_ERRORS = -1;
+ private static final int ERRNO_USAGE = -2;
+ private static final int ERRNO_EXISTS = -3;
+ private static final int ERRNO_HELP = -4;
+ private static final int ERRNO_INVALIDARGS = -5;
+
+ private Set<String> mSuppress = new HashSet<String>();
+ private Set<String> mEnabled = null;
+ private StringBuilder mOutput = new StringBuilder(2000);
+ private boolean mFatal;
+ private String mCommonPrefix;
+
+ /** Creates a CLI driver */
+ public Main() {
+ }
+
+ /**
+ * Runs the static analysis command line driver
+ *
+ * @param args program arguments
+ */
+ public static void main(String[] args) {
+ new Main().run(args);
+ }
+
+ /**
+ * Runs the static analysis command line driver
+ *
+ * @param args program arguments
+ */
+ private void run(String[] args) {
+ if (args.length < 1) {
+ printUsage();
+ System.exit(ERRNO_USAGE);
+ }
+
+ DetectorRegistry registry = new BuiltinDetectorRegistry();
+
+ List<File> files = new ArrayList<File>();
+ for (int index = 0; index < args.length; index++) {
+ String arg = args[index];
+ if (arg.equals("--help") || arg.equals("-h")) { //$NON-NLS-1$ //$NON-NLS-2$
+ printUsage();
+ System.err.println("\n" +
+ "Run with --suppress <category1[,category2,...]> to suppress categories.");
+ System.exit(ERRNO_HELP);
+ } else if (arg.equals("--suppress")) {
+ if (index == args.length - 1) {
+ System.err.println("Missing categories to suppress");
+ System.exit(ERRNO_INVALIDARGS);
+ }
+ String[] ids = args[++index].split(",");
+ for (String id : ids) {
+ if (!registry.isIssueId(id)) {
+ System.err.println("Invalid id \"" + id + "\".");
+ displayValidIds(registry);
+ System.exit(ERRNO_INVALIDARGS);
+ }
+ mSuppress.add(id);
+ }
+ } else if (arg.equals("--enable")) {
+ if (index == args.length - 1) {
+ System.err.println("Missing categories to enable");
+ System.exit(ERRNO_INVALIDARGS);
+ }
+ String[] ids = args[++index].split(",");
+ mEnabled = new HashSet<String>();
+ for (String id : ids) {
+ if (!registry.isIssueId(id)) {
+ System.err.println("Invalid id \"" + id + "\".");
+ displayValidIds(registry);
+ System.exit(ERRNO_INVALIDARGS);
+ }
+ mEnabled.add(id);
+ }
+ } else {
+ String filename = arg;
+ File file = new File(filename);
+ if (!file.exists()) {
+ System.err.println(String.format("%1$s does not exist.", filename));
+ System.exit(ERRNO_EXISTS);
+ }
+ files.add(file);
+ }
+ // TODO: Add flag to point to a file of specific errors to suppress
+ }
+
+ if (files.size() == 0) {
+ System.err.println("No files to analyze.");
+ System.exit(ERRNO_INVALIDARGS);
+ }
+
+ mCommonPrefix = files.get(0).getPath();
+ for (int i = 1; i < files.size(); i++) {
+ File file = files.get(i);
+ String path = file.getPath();
+ mCommonPrefix = getCommonPrefix(mCommonPrefix, path);
+ }
+
+ Lint analyzer = new Lint(new BuiltinDetectorRegistry(), this, Scope.PROJECT);
+ analyzer.analyze(files);
+ if (mOutput.length() == 0) {
+ System.out.println("No warnings.");
+ System.exit(0); // Success error code
+ } else {
+ System.err.println(mOutput.toString());
+ System.err.println("Run with --suppress to turn off specific types of errors, or this message");
+
+ System.exit(mFatal ? ERRNO_ERRORS : 0);
+ }
+ }
+
+ private void displayValidIds(DetectorRegistry registry) {
+ List<Issue> issues = registry.getIssues();
+ System.err.println("Valid issue ids:");
+ for (Issue issue : issues) {
+ System.err.println("\"" + issue.getId() + "\": " + issue.getDescription());
+ }
+ }
+
+ private static void printUsage() {
+ // TODO: Look up launcher script name!
+ System.err.println("Usage: lint [--suppress ids] [--enable ids] <project | file> ...");
+ }
+
+ private static String getCommonPrefix(String a, String b) {
+ int aLength = a.length();
+ int bLength = b.length();
+ int aIndex = 0, bIndex = 0;
+ for (; aIndex < aLength && bIndex < bLength; aIndex++, bIndex++) {
+ if (a.charAt(aIndex) != b.charAt(bIndex)) {
+ break;
+ }
+ }
+
+ return a.substring(0, aIndex);
+ }
+
+ public void log(Throwable exception, String format, Object... args) {
+ System.err.println(String.format(format, args));
+ if (exception != null) {
+ exception.printStackTrace();
+ }
+ }
+
+ public IDomParser getParser() {
+ return new PositionXmlParser();
+ }
+
+ public boolean isEnabled(Issue issue) {
+ if (mEnabled != null) {
+ return mEnabled.contains(issue.getId());
+ }
+ return !mSuppress.contains(issue.getId());
+ }
+
+ public void report(Issue issue, Location location, String message) {
+ if (!isEnabled(issue)) {
+ return;
+ }
+
+ Severity severity = getSeverity(issue);
+ if (severity == Severity.IGNORE) {
+ return;
+ }
+ if (severity == Severity.ERROR){
+ mFatal = true;
+ }
+
+ int startLength = mOutput.length();
+
+ File file = location.getFile();
+ if (file != null) {
+ String path = file.getPath();
+ if (path.startsWith(mCommonPrefix)) {
+ int chop = mCommonPrefix.length();
+ if (path.length() > chop && path.charAt(chop) == File.separatorChar) {
+ chop++;
+ }
+ path = path.substring(chop);
+ }
+ mOutput.append(path);
+ mOutput.append(':');
+ }
+
+ Position startPosition = location.getStart();
+ if (startPosition != null) {
+ int line = startPosition.getLine();
+ if (line >= 0) {
+ // line is 0-based, should display 1-based
+ mOutput.append(Integer.toString(line + 1));
+ mOutput.append(':');
+ }
+ }
+
+ // Column is not particularly useful here
+ //int column = location.getColumn();
+ //if (column > 0) {
+ // mOutput.append(Integer.toString(column));
+ // mOutput.append(':');
+ //}
+
+ if (startLength < mOutput.length()) {
+ mOutput.append(' ');
+ }
+ mOutput.append(severity.getDescription());
+ mOutput.append(':');
+ mOutput.append(' ');
+
+ mOutput.append(message);
+ if (issue != null) {
+ mOutput.append(' ').append('[');
+ mOutput.append(issue.getId());
+ mOutput.append(']');
+ }
+
+ mOutput.append('\n');
+ }
+
+ public boolean isSuppressed(Issue issue, Location range, String message,
+ Severity severity) {
+ // Not yet supported
+ return false;
+ }
+
+ public Severity getSeverity(Issue issue) {
+ return issue.getDefaultSeverity();
+ }
+}
diff --git a/lint/cli/src/com/android/tools/lint/PositionXmlParser.java b/lint/cli/src/com/android/tools/lint/PositionXmlParser.java
new file mode 100644
index 0000000..0857425
--- /dev/null
+++ b/lint/cli/src/com/android/tools/lint/PositionXmlParser.java
@@ -0,0 +1,166 @@
+/*
+ * 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;
+
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Position;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLFilterImpl;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.StringReader;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXSource;
+
+/** A simple XML parser which can store and retrieve line:column information for the nodes */
+public class PositionXmlParser implements IDomParser {
+ private static final String ATTR_LOCATION = "location"; //$NON-NLS-1$
+ private static final String PRIVATE_NAMESPACE = "http://tools.android.com"; //$NON-NLS-1$
+ private static final String PRIVATE_PREFIX = "temp"; //$NON-NLS-1$
+
+ public Document parse(Context context) {
+ InputSource input = new InputSource(new StringReader(context.getContents()));
+ try {
+ Filter filter = new Filter(XMLReaderFactory.createXMLReader());
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ DOMResult result = new DOMResult();
+ transformer.transform(new SAXSource(filter, input), result);
+ return (Document) result.getNode();
+ } catch (SAXException e) {
+ // The file doesn't parse: not an exception. Infrastructure will log a warning
+ // that this file was not analyzed.
+ return null;
+ } catch (TransformerConfigurationException e) {
+ context.toolContext.log(e, null);
+ } catch (TransformerException e) {
+ context.toolContext.log(e, null);
+ }
+
+ return null;
+ }
+
+ private static class Filter extends XMLFilterImpl {
+ private Locator mLocator;
+
+ Filter(XMLReader reader) {
+ super(reader);
+ }
+
+ @Override
+ public void setDocumentLocator(Locator locator) {
+ super.setDocumentLocator(locator);
+ this.mLocator = locator;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qualifiedName,
+ Attributes attributes) throws SAXException {
+ int lineno = mLocator.getLineNumber();
+ int column = mLocator.getColumnNumber();
+ String location = Integer.toString(lineno) + ':' + Integer.toString(column);
+
+ // Modify attributes parameter to a copy that includes our private attribute
+ AttributesImpl wrapper = new AttributesImpl(attributes);
+ wrapper.addAttribute(PRIVATE_NAMESPACE, ATTR_LOCATION,
+ PRIVATE_PREFIX + ':' + ATTR_LOCATION, "CDATA", location); //$NON-NLS-1$
+
+ super.startElement(uri, localName, qualifiedName, wrapper);
+ }
+ }
+
+ public Position getStartPosition(Context context, Node node) {
+ if (node instanceof Attr) {
+ Attr attr = (Attr) node;
+ node = attr.getOwnerElement();
+ }
+ if (node instanceof Element) {
+ Attr attribute = ((Element) node).getAttributeNodeNS(PRIVATE_NAMESPACE, ATTR_LOCATION);
+ if (attribute != null) {
+ String position = attribute.getValue();
+ int separator = position.indexOf(':');
+ int line = Integer.parseInt(position.substring(0, separator));
+ int column = Integer.parseInt(position.substring(separator + 1));
+ return new OffsetPosition(line, column, -1);
+ }
+ }
+
+ return null;
+ }
+
+ public Position getEndPosition(Context context, Node node) {
+ // TODO: Currently unused
+ return null;
+ }
+
+ private static class OffsetPosition extends Position {
+ /** The line number (0-based where the first line is line 0) */
+ private final int mLine;
+
+ /**
+ * The column number (where the first character on the line is 0), or -1 if
+ * unknown
+ */
+ private final int mColumn;
+
+ /** The character offset */
+ private final int mOffset;
+
+ /**
+ * Creates a new {@link Position}
+ *
+ * @param line the 0-based line number, or -1 if unknown
+ * @param column the 0-based column number, or -1 if unknown
+ * @param offset the offset, or -1 if unknown
+ */
+ public OffsetPosition(int line, int column, int offset) {
+ this.mLine = line;
+ this.mColumn = column;
+ this.mOffset = offset;
+ }
+
+ @Override
+ public int getLine() {
+ return mLine;
+ }
+
+ @Override
+ public int getOffset() {
+ return mOffset;
+ }
+
+ @Override
+ public int getColumn() {
+ return mColumn;
+ }
+ }
+}
diff --git a/lint/libs/Android.mk b/lint/libs/Android.mk
new file mode 100644
index 0000000..67aeea2
--- /dev/null
+++ b/lint/libs/Android.mk
@@ -0,0 +1,5 @@
+# Copyright 2011 The Android Open Source Project
+#
+CHECKERLIBS_LOCAL_DIR := $(call my-dir)
+include $(CHECKERLIBS_LOCAL_DIR)/lint_api/Android.mk
+include $(CHECKERLIBS_LOCAL_DIR)/lint_checks/Android.mk
diff --git a/lint/libs/lint_api/.classpath b/lint/libs/lint_api/.classpath
new file mode 100644
index 0000000..012d361
--- /dev/null
+++ b/lint/libs/lint_api/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry excluding="Android.mk" kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/common"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/lint/libs/lint_api/.project b/lint/libs/lint_api/.project
new file mode 100644
index 0000000..7c50676
--- /dev/null
+++ b/lint/libs/lint_api/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>lint-api</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/lint/libs/lint_api/.settings/org.eclipse.jdt.core.prefs b/lint/libs/lint_api/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e755df2
--- /dev/null
+++ b/lint/libs/lint_api/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,71 @@
+#Thu Jun 09 12:26:44 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/lint/libs/lint_api/.settings/org.moreunit.prefs b/lint/libs/lint_api/.settings/org.moreunit.prefs
new file mode 100644
index 0000000..73d4d8e
--- /dev/null
+++ b/lint/libs/lint_api/.settings/org.moreunit.prefs
@@ -0,0 +1,6 @@
+#Tue Oct 18 10:20:08 PDT 2011
+eclipse.preferences.version=1
+org.moreunit.extendedTestMethodSearch=true
+org.moreunit.prefixes=
+org.moreunit.unitsourcefolder=lint-api\:src\:lint_check-tests\:src
+org.moreunit.useprojectsettings=true
diff --git a/lint/libs/lint_api/Android.mk b/lint/libs/lint_api/Android.mk
new file mode 100644
index 0000000..847f52e
--- /dev/null
+++ b/lint/libs/lint_api/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+LOCAL_JAVA_LIBRARIES := \
+ common
+
+LOCAL_MODULE := lint_api
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/lint/libs/lint_api/NOTICE b/lint/libs/lint_api/NOTICE
new file mode 100644
index 0000000..becc120
--- /dev/null
+++ b/lint/libs/lint_api/NOTICE
@@ -0,0 +1,190 @@
+
+ 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/DetectorRegistry.java b/lint/libs/lint_api/src/com/android/tools/lint/api/DetectorRegistry.java
new file mode 100644
index 0000000..591114a
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/DetectorRegistry.java
@@ -0,0 +1,100 @@
+/*
+ * 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.api;
+
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Registry which provides a list of checks to be performed on an Android project */
+public abstract class DetectorRegistry {
+ private static List<Issue> sIssues;
+
+ /**
+ * Returns the list of detectors to be run.
+ *
+ * @return the list of checks to be performed (including those that may be
+ * disabled!)
+ */
+ public abstract List<? extends Detector> getDetectors();
+
+ /**
+ * Returns true if the given id represents a valid issue id
+ *
+ * @param id the id to be checked
+ * @return true if the given id is valid
+ */
+ public boolean isIssueId(String id) {
+ return getIssue(id) != null;
+ }
+
+
+ /**
+ * Returns the issue for the given id, or null if it's not a valid id
+ *
+ * @param id the id to be checked
+ * @return the corresponding issue, or null
+ */
+ public Issue getIssue(String id) {
+ for (Issue issue : getIssues()) {
+ if (issue.getId().equals(id)) {
+ return issue;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the list of issues that can be found by all known detectors.
+ *
+ * @return the list of issues to be checked (including those that may be
+ * disabled!)
+ */
+ @SuppressWarnings("all") // Turn off warnings for the intentional assertion side effect below
+ public List<Issue> getIssues() {
+ if (sIssues == null) {
+ List<Issue> issues = new ArrayList<Issue>();
+ for (Detector detector : getDetectors()) {
+ for (Issue issue : detector.getIssues()) {
+ issues.add(issue);
+ }
+ }
+
+ sIssues = Collections.unmodifiableList(issues);
+
+ // Check that ids are unique
+ boolean assertionsEnabled = false;
+ assert assertionsEnabled = true; // Intentional side-effect
+ if (assertionsEnabled) {
+ Set<String> ids = new HashSet<String>();
+ for (Issue issue : sIssues) {
+ String id = issue.getId();
+ assert !ids.contains(id) : "Duplicate id " + id; //$NON-NLS-1$
+ ids.add(id);
+ }
+ }
+ }
+
+ return sIssues;
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/IDomParser.java b/lint/libs/lint_api/src/com/android/tools/lint/api/IDomParser.java
new file mode 100644
index 0000000..12861c5
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/IDomParser.java
@@ -0,0 +1,65 @@
+/*
+ * 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.api;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Position;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.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 public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public interface IDomParser {
+ /**
+ * Parse the file pointed to by the given context and return as a Document
+ *
+ * @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 parsed DOM document, or null if parsing fails
+ */
+ public Document parse(Context context);
+
+ /**
+ * Returns the starting position of the given DOM node (which may not be
+ * just an element but can for example also be an {@link Attr} node). The
+ * node will *always* be from the same DOM document that was returned by
+ * this parser.
+ *
+ * @param context information about the file being parsed
+ * @param node the node to look up a starting position for
+ * @return the position of the beginning of the node
+ */
+ public Position getStartPosition(Context context, Node node);
+
+ /**
+ * Returns the ending position of the given DOM node.
+ *
+ * @param context information about the file being parsed
+ * @param node the node to look up a ending position for
+ * @return the position of the end of the node
+ */
+ public Position getEndPosition(Context context, Node node);
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/Lint.java b/lint/libs/lint_api/src/com/android/tools/lint/api/Lint.java
new file mode 100644
index 0000000..7cbbb79
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/Lint.java
@@ -0,0 +1,266 @@
+/*
+ * 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.api;
+
+import com.android.resources.ResourceFolderType;
+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.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Analyzes Android projects and files */
+public class Lint {
+ private static final String RES_FOLDER_NAME = "res"; //$NON-NLS-1$
+ private final ToolContext mToolContext;
+ private volatile boolean mCanceled;
+ private DetectorRegistry mRegistry;
+ private Scope mScope;
+
+ /**
+ * Creates a new {@link Lint}
+ *
+ * @param registry The registry containing rules to be run
+ * @param toolContext a context for the tool wrapping the analyzer, such as
+ * an IDE or a CLI
+ * @param scope the scope of the analysis; detectors with a wider scope will
+ * not be run
+ */
+ public Lint(DetectorRegistry registry, ToolContext toolContext, Scope scope) {
+ assert toolContext != null;
+ mRegistry = registry;
+ mToolContext = toolContext;
+ mScope = scope;
+ }
+
+ /** Cancels the current lint run as soon as possible */
+ public void cancel() {
+ mCanceled = true;
+ }
+
+ /**
+ * Analyze the given file (which can point to an Android project). Issues found
+ * are reported to the associated {@link ToolContext}.
+ *
+ * @param files the files and directories to be analyzed
+ */
+ public void analyze(List<File> files) {
+ List<? extends Detector> availableChecks = mRegistry.getDetectors();
+
+ // Filter out disabled checks
+ List<Detector> checks = new ArrayList<Detector>(availableChecks.size());
+ for (Detector detector : availableChecks) {
+ if (!detector.getScope().within(mScope)) {
+ continue;
+ }
+ // A detector is enabled if at least one of its issues is enabled
+ for (Issue issue : detector.getIssues()) {
+ if (mToolContext.isEnabled(issue)) {
+ checks.add(detector);
+ break;
+ }
+ }
+ }
+
+ // Process XML files in a single pass
+ List<ResourceXmlDetector> xmlChecks = new ArrayList<ResourceXmlDetector>(checks.size());
+ List<Detector> other = new ArrayList<Detector>(checks.size());
+ for (Detector check : checks) {
+ if (check instanceof ResourceXmlDetector) {
+ xmlChecks.add((ResourceXmlDetector) check);
+ } else {
+ // TODO:
+ other.add(check);
+ }
+ }
+
+ Context projectContext = new Context(mToolContext, null);
+ for (Detector check : checks) {
+ check.beforeCheckProject(projectContext);
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ for (File file : files) {
+ if (file.isDirectory()) {
+ // Is it a resource folder?
+ ResourceFolderType type = ResourceFolderType.getFolderType(file.getName());
+ if (type != null && new File(file.getParentFile(), RES_FOLDER_NAME).exists()) {
+ // Yes.
+ checkResourceFolder(file, type, xmlChecks);
+ } else if (file.getName().equals(RES_FOLDER_NAME)) { // Is it the "res" folder?
+ // Yes
+ checkResFolder(file, xmlChecks);
+ } else {
+ // It must be a project
+ File res = new File(file, RES_FOLDER_NAME);
+ if (res.exists()) {
+ checkResFolder(res, xmlChecks);
+ } else {
+ mToolContext.log(null, "Unexpected folder %1$s; should be project, " +
+ "\"res\" folder or resource folder", file.getPath());
+ }
+ }
+ } else if (file.isFile()) {
+ // Did we point to an XML resource?
+ if (ResourceXmlDetector.isXmlFile(file)) {
+ // Yes, find out its resource type
+ String folderName = file.getParentFile().getName();
+ ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
+ if (type != null) {
+ XmlVisitor visitor = getVisitor(xmlChecks, type);
+ if (visitor != null) {
+ Context context = new Context(mToolContext, file);
+ visitor.visitFile(context, file);
+ }
+ }
+ } else {
+ if (other.size() > 0) {
+ Context context = new Context(mToolContext, file);
+ context.location = new Location(file, null, null);
+ for (Detector detector : other) {
+ if (detector.appliesTo(context, file)) {
+ detector.beforeCheckFile(context);
+ detector.run(context);
+ detector.afterCheckFile(context);
+ }
+ }
+ }
+ }
+ }
+
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ for (Detector check : checks) {
+ check.afterCheckProject(projectContext);
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ if (mCanceled) {
+ mToolContext.report(
+ // Must provide an issue since API guarantees that the issue parameter
+ // is valid
+ Issue.create("dummy", "", "", "", 0, Severity.INFORMATIONAL, null), //$NON-NLS-1$
+ null /*range*/,
+ "Lint canceled by user");
+ }
+ }
+
+ private ResourceFolderType mCurrentFolderType;
+ private List<ResourceXmlDetector> mCurrentXmlDetectors;
+ private XmlVisitor mCurrentVisitor;
+
+ private XmlVisitor getVisitor(List<ResourceXmlDetector> checks, ResourceFolderType type) {
+ if (type != mCurrentFolderType) {
+ mCurrentFolderType = type;
+
+ // Determine which XML resource detectors apply to the given folder type
+ List<ResourceXmlDetector> applicableChecks =
+ new ArrayList<ResourceXmlDetector>(checks.size());
+ for (ResourceXmlDetector check : checks) {
+ if (check.appliesTo(type)) {
+ applicableChecks.add(check);
+ }
+ }
+
+ // If the list of detectors hasn't changed, then just use the current visitor!
+ if (mCurrentXmlDetectors != null && mCurrentXmlDetectors.equals(applicableChecks)) {
+ return mCurrentVisitor;
+ }
+
+ if (applicableChecks.size() == 0) {
+ mCurrentVisitor = null;
+ return null;
+ }
+
+ mCurrentVisitor = new XmlVisitor(mToolContext.getParser(), applicableChecks);
+ }
+
+ return mCurrentVisitor;
+ }
+
+ private void checkResFolder(File res, List<ResourceXmlDetector> xmlChecks) {
+ assert res.isDirectory();
+ File[] resourceDirs = res.listFiles();
+ if (resourceDirs == null) {
+ return;
+ }
+
+ // Sort alphabetically such that we can process related folder types at the
+ // same time
+
+ Arrays.sort(resourceDirs);
+ ResourceFolderType type = null;
+ for (File dir : resourceDirs) {
+ if (!dir.isDirectory()) {
+ continue;
+ }
+
+ type = ResourceFolderType.getFolderType(dir.getName());
+ if (type != null) {
+ checkResourceFolder(dir, type, xmlChecks);
+ }
+
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+
+ private void checkResourceFolder(File dir, ResourceFolderType type,
+ List<ResourceXmlDetector> xmlChecks) {
+ // Process the resource folder
+ File[] xmlFiles = dir.listFiles();
+ if (xmlFiles != null && xmlFiles.length > 0) {
+ XmlVisitor visitor = getVisitor(xmlChecks, type);
+ if (visitor != null) { // if not, there are no applicable rules in this folder
+ for (File xmlFile : xmlFiles) {
+ if (ResourceXmlDetector.isXmlFile(xmlFile)) {
+ Context context = new Context(mToolContext, xmlFile);
+ visitor.visitFile(context, xmlFile);
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the associated tool context for the surrounding tool that is
+ * embedding lint analysis
+ *
+ * @return the surrounding tool context
+ */
+ public ToolContext getToolContext() {
+ return mToolContext;
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/ToolContext.java b/lint/libs/lint_api/src/com/android/tools/lint/api/ToolContext.java
new file mode 100644
index 0000000..cc3592f
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/ToolContext.java
@@ -0,0 +1,87 @@
+/*
+ * 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.api;
+
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Severity;
+
+/**
+ * Information about the tool embedding the lint analyzer
+ * <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 interface ToolContext {
+ /**
+ * Report the given issue.
+ *
+ * @param issue the issue that was found
+ * @param location the location of the issue
+ * @param message the associated user message
+ */
+ public void report(Issue issue, Location location, String message);
+
+ /**
+ * Checks whether this issue should be ignored because the user has already
+ * suppressed the error? Note that this refers to individual issues being
+ * suppressed/ignored, not a whole detector being disabled via something
+ * like {@link #isEnabled(Issue)}.
+ *
+ * @param issue the issue that was found
+ * @param location the location of the issue
+ * @param message the associated user message
+ * @param severity the severity of the issue
+ * @return true if this issue should be suppressed
+ */
+ public boolean isSuppressed(Issue issue, Location location, String message, Severity severity);
+
+
+ /**
+ * Send an exception to the log
+ *
+ * @param exception the exception, possibly null
+ * @param format the error message using {@link String#format} syntax
+ * @param args any arguments for the format string
+ */
+ public void log(Throwable exception, String format, Object... args);
+
+ /**
+ * Returns a {@link IDomParser} to use to parse XML
+ *
+ * @return a new {@link IDomParser}
+ */
+ public IDomParser getParser();
+
+ /**
+ * Returns false if the given issue has been disabled
+ *
+ * @param issue the issue to check
+ * @return false if the issue has been disabled
+ */
+ public boolean isEnabled(Issue issue);
+
+ /**
+ * Returns the severity for a given issue. This is the same as the
+ * {@link Issue#getDefaultSeverity()} unless the user has selected a custom
+ * severity (which is tool context dependent).
+ *
+ * @param issue the issue to look up the severity from
+ * @return the severity use for issues for the given detector
+ */
+ public Severity getSeverity(Issue issue);
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/api/XmlVisitor.java b/lint/libs/lint_api/src/com/android/tools/lint/api/XmlVisitor.java
new file mode 100644
index 0000000..1908682
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/api/XmlVisitor.java
@@ -0,0 +1,213 @@
+/*
+ * 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.api;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.RandomAccess;
+
+/**
+ * Specialized visitor for running detectors on an XML document.
+ * It operates in two phases:
+ * <ol>
+ * <li> First, it computes a set of maps where it generates a map from each
+ * significant element name, and each significant attribute name, to a list
+ * of detectors to consult for that element or attribute name.
+ * The set of element names or attribute names (or both) that a detector
+ * is interested in is provided by the detectors themselves.
+ * <li> Second, it iterates over the document a single time. For each element and
+ * attribute it looks up the list of interested detectors, and runs them.
+ * </ol>
+ * It also notifies all the detectors before and after the document is processed
+ * such that they can do pre- and post-processing.
+ */
+class XmlVisitor {
+ private final Map<String, List<ResourceXmlDetector>> mElementToCheck =
+ new HashMap<String, List<ResourceXmlDetector>>();
+ private final Map<String, List<ResourceXmlDetector>> mAttributeToCheck =
+ new HashMap<String, List<ResourceXmlDetector>>();
+ private final List<ResourceXmlDetector> mDocumentDetectors =
+ new ArrayList<ResourceXmlDetector>();
+ private final List<ResourceXmlDetector> mAllElementDetectors =
+ new ArrayList<ResourceXmlDetector>();
+ private final List<ResourceXmlDetector> mAllAttributeDetectors =
+ new ArrayList<ResourceXmlDetector>();
+ private final List<ResourceXmlDetector> mAllDetectors;
+ private final IDomParser mParser;
+
+ XmlVisitor(IDomParser parser, List<ResourceXmlDetector> detectors) {
+ mParser = parser;
+ mAllDetectors = detectors;
+
+ // TODO: Check appliesTo() for files, and find a quick way to enable/disable
+ // rules when running through a full project!
+ for (ResourceXmlDetector detector : detectors) {
+ Collection<String> attributes = detector.getApplicableAttributes();
+ if (attributes == ResourceXmlDetector.ALL) {
+ mAllAttributeDetectors.add(detector);
+ } else if (attributes != null) {
+ for (String attribute : attributes) {
+ List<ResourceXmlDetector> list = mAttributeToCheck.get(attribute);
+ if (list == null) {
+ list = new ArrayList<ResourceXmlDetector>();
+ mAttributeToCheck.put(attribute, list);
+ }
+ list.add(detector);
+ }
+ }
+ Collection<String> elements = detector.getApplicableElements();
+ if (elements == ResourceXmlDetector.ALL) {
+ mAllElementDetectors.add(detector);
+ } else if (elements != null) {
+ for (String element : elements) {
+ List<ResourceXmlDetector> list = mElementToCheck.get(element);
+ if (list == null) {
+ list = new ArrayList<ResourceXmlDetector>();
+ mElementToCheck.put(element, list);
+ }
+ list.add(detector);
+ }
+ }
+
+ if ((attributes == null || (attributes.size() == 0
+ && attributes != ResourceXmlDetector.ALL))
+ && (elements == null || (elements.size() == 0
+ && elements != ResourceXmlDetector.ALL))) {
+ mDocumentDetectors.add(detector);
+ }
+ }
+ }
+
+ void visitFile(Context context, File file) {
+ assert ResourceXmlDetector.isXmlFile(file);
+
+ context.location = null;
+ context.parser = mParser;
+
+ if (context.document == null) {
+ context.document = mParser.parse(context);
+ if (context.document == null) {
+ context.toolContext.report(
+ // Must provide an issue since API guarantees that the issue parameter
+ // is valid
+ Issue.create("dummy", "", "", "", 0, Severity.ERROR, null), //$NON-NLS-1$
+ new Location(file, null, null),
+ "Skipped file because it contains parsing errors");
+ return;
+ }
+ if (context.document.getDocumentElement() == null) {
+ // Ignore empty documents
+ return;
+ }
+ }
+
+ for (ResourceXmlDetector check : mAllDetectors) {
+ check.beforeCheckFile(context);
+ }
+
+ for (ResourceXmlDetector check : mDocumentDetectors) {
+ check.visitDocument(context, context.document);
+ }
+
+ if (mElementToCheck.size() > 0 || mAttributeToCheck.size() > 0
+ || mAllAttributeDetectors.size() > 0 || mAllElementDetectors.size() > 0) {
+ visitElement(context, context.document.getDocumentElement());
+ }
+
+ for (ResourceXmlDetector check : mAllDetectors) {
+ check.afterCheckFile(context);
+ }
+ }
+
+ private void visitElement(Context context, Element element) {
+ context.element = element;
+
+ List<ResourceXmlDetector> elementChecks = mElementToCheck.get(element.getTagName());
+ if (elementChecks != null) {
+ assert elementChecks instanceof RandomAccess;
+ for (int i = 0, n = elementChecks.size(); i < n; i++) {
+ ResourceXmlDetector check = elementChecks.get(i);
+ check.visitElement(context, element);
+ }
+ }
+ if (mAllElementDetectors.size() > 0) {
+ for (int i = 0, n = mAllElementDetectors.size(); i < n; i++) {
+ ResourceXmlDetector check = mAllElementDetectors.get(i);
+ check.visitElement(context, element);
+ }
+ }
+
+ if (mAttributeToCheck.size() > 0 || mAllAttributeDetectors.size() > 0) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ List<ResourceXmlDetector> list = mAttributeToCheck.get(attribute.getLocalName());
+ if (list != null) {
+ for (int j = 0, max = list.size(); j < max; j++) {
+ ResourceXmlDetector check = list.get(j);
+ check.visitAttribute(context, attribute);
+ }
+ }
+ if (mAllAttributeDetectors.size() > 0) {
+ for (int j = 0, max = mAllAttributeDetectors.size(); j < max; j++) {
+ ResourceXmlDetector check = mAllAttributeDetectors.get(j);
+ check.visitAttribute(context, attribute);
+ }
+ }
+ }
+ }
+
+ // Visit children
+ NodeList childNodes = element.getChildNodes();
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ visitElement(context, (Element) child);
+ }
+ }
+
+ // Post hooks
+ if (elementChecks != null) {
+ for (int i = 0, n = elementChecks.size(); i < n; i++) {
+ ResourceXmlDetector check = elementChecks.get(i);
+ check.visitElementAfter(context, element);
+ }
+ }
+ if (mAllElementDetectors.size() > 0) {
+ for (int i = 0, n = mAllElementDetectors.size(); i < n; i++) {
+ ResourceXmlDetector check = mAllElementDetectors.get(i);
+ check.visitElementAfter(context, element);
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
new file mode 100644
index 0000000..8f4ff4e
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Context.java
@@ -0,0 +1,126 @@
+/*
+ * 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.api.IDomParser;
+import com.android.tools.lint.api.ToolContext;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Context passed to the detectors during an analysis run. It provides
+ * information about the file being analyzed, it allows shared properties (so
+ * the detectors can share results), it contains the current location in the
+ * document, etc.
+ * <p>
+ * TODO: This needs some cleanup. Perhaps we should split up into a FileContext
+ * and a ProjectContext.
+ * <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 Context {
+ public final File file;
+ public final ToolContext toolContext;
+ public Document document;
+ public Location location;
+ public Element element;
+ public IDomParser parser;
+ private String contents;
+ private Map<String, Object> properties;
+
+ public Context(ToolContext toolContext, File file) {
+ super();
+ this.toolContext = toolContext;
+ this.file = file;
+ }
+
+ public Location getLocation(Node node) {
+ if (parser != null) {
+ return new Location(file,
+ parser.getStartPosition(this, node),
+ parser.getEndPosition(this, node));
+ }
+
+ return location;
+ }
+
+ public Location getLocation(Context context) {
+ if (location == null && element != null && parser != null) {
+ return getLocation(element);
+ }
+ return location;
+ }
+
+ public String getContents() {
+ if (contents == null) {
+ contents = ""; //$NON-NLS-1$
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ StringBuilder sb = new StringBuilder((int) file.length());
+ while (true) {
+ int c = reader.read();
+ if (c == -1) {
+ contents = sb.toString();
+ break;
+ } else {
+ sb.append((char)c);
+ }
+ }
+ } catch (IOException e) {
+ // pass -- ignore files we can't read
+ } finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (IOException e) {
+ toolContext.log(e, null);
+ }
+ }
+ }
+
+ return contents;
+ }
+
+ public Object getProperty(String name) {
+ if (properties == null) {
+ return null;
+ }
+
+ return properties.get(name);
+ }
+
+ public void setProperty(String name, Object value) {
+ if (properties == null) {
+ properties = new HashMap<String, Object>();
+ }
+
+ properties.put(name, value);
+ }
+}
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
new file mode 100644
index 0000000..12696a4
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
@@ -0,0 +1,105 @@
+/*
+ * 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 java.io.File;
+
+/**
+ * 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
+ * the Rules API to allow views to specify designtime behavior in the graphical layout editor.
+ * <p>
+ * Each detector provides information about the issues it can find, such as an explanation
+ * of how to fix the issue, the priority, the category, etc. It also has an id which is
+ * used to persistently identify a particular type of error.
+ * <p/>
+ * NOTE: Detectors might be constructed just once and shared between lint runs, so
+ * any per-detector state should be initialized and reset via the before/after
+ * methods.
+ * <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 abstract class Detector /*implements Comparable<Detector>*/ {
+ /**
+ * Returns a list of issues detected by this detector.
+ *
+ * @return a list of issues detected by this detector, never null.
+ */
+ public abstract Issue[] getIssues();
+
+// /**
+// * Returns the id of this detector. These should not change over time since
+// * they are used to persist the names of disabled detectors etc. It is
+// * typically a single camel-cased word.
+// *
+// * @return the associated fixed id
+// */
+// public abstract String getId();
+
+ /**
+ * Runs the detector
+ * @param context the context describing the work to be done
+ */
+ public abstract void run(Context context);
+
+
+ public abstract boolean appliesTo(Context context, File file);
+
+ /** Analysis is about to begin, perform any setup steps. */
+ public void beforeCheckProject(Context context) {
+ }
+
+ /**
+ * Analysis has just been finished for the whole project, perform any
+ * cleanup or report issues found
+ */
+ public void afterCheckProject(Context context) {
+ }
+
+ /** Analysis is about to be performed on a specific file, perform any setup steps. */
+ public void beforeCheckFile(Context context) {
+ }
+
+ /**
+ * Analysis has just been finished for a specific file, perform any cleanup
+ * or report issues found
+ */
+ public void afterCheckFile(Context context) {
+ }
+
+ /**
+ * Returns the expected speed of this detector
+ *
+ * @return the expected speed of this detector
+ */
+ public abstract Speed getSpeed();
+
+ /**
+ * Returns the scope of this detector
+ *
+ * @return the scope of this detector
+ */
+ public abstract Scope getScope();
+
+ protected static final String CATEGORY_CORRECTNESS = "Correctness";
+ protected static final String CATEGORY_PERFORMANCE = "Performance";
+ protected static final String CATEGORY_USABILITY = "Usability";
+ protected static final String CATEGORY_I18N = "Internationalization";
+ protected static final String CATEGORY_A11Y = "Accessibility";
+ protected static final String CATEGORY_LAYOUT = "Layout";
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
new file mode 100644
index 0000000..5ce9ecc
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
@@ -0,0 +1,157 @@
+/*
+ * 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;
+
+
+
+/**
+ * An issue is a potential bug in an Android application. An issue is discovered
+ * by a {@link Detector}, and has an associated {@link Severity}.
+ * <p>
+ * Issues and detectors are separate classes because a detector can discover
+ * multiple different issues as it's analyzing code, and we want to be able to
+ * different severities for different issues, the ability to suppress one but
+ * not other issues from the same detector, and so on.
+ * <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 final class Issue implements Comparable<Issue> {
+ private final String mId;
+ private final String mDescription;
+ private final String mExplanation;
+ private final String mCategory;
+ private final int mPriority;
+ private final Severity mSeverity;
+ private final String mMoreInfoUrl;
+
+ // Use factory methods
+ private Issue(String id, String description, String explanation, String category, int priority,
+ Severity severity, String moreInfoUrl) {
+ super();
+ mId = id;
+ mDescription = description;
+ mExplanation = explanation;
+ mCategory = category;
+ mPriority = priority;
+ mSeverity = severity;
+ mMoreInfoUrl = moreInfoUrl;
+ }
+
+ /**
+ * Creates a new issue
+ *
+ * @param id the fixed id of the issue
+ * @param description the quick summary of the issue (one line)
+ * @param explanation a full explanation of the issue, with suggestions for
+ * how to fix it
+ * @param category the associated category, if any
+ * @param priority the priority, a number from 1 to 10 with 10 being most
+ * important/severe
+ * @param severity the default severity of the issue
+ * @param moreInfo an (optional) URL string to a resource which provides more
+ * information
+ * @return a new {@link Issue}
+ */
+ public static Issue create(String id, String description, String explanation, String category,
+ int priority, Severity severity, String moreInfo) {
+ return new Issue(id, description, explanation, category, priority, severity, moreInfo);
+ }
+
+ /**
+ * Returns the unique id of this issue. These should not change over time
+ * since they are used to persist the names of issues suppressed by the user
+ * etc. It is typically a single camel-cased word.
+ *
+ * @return the associated fixed id, never null and always unique
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Briefly (one line) describes the kinds of checks performed by this rule
+ *
+ * @return a quick summary of the issue, never null
+ */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Describes the error found by this rule, e.g.
+ * "Buttons must define contentDescriptions". Preferably the explanation
+ * should also contain a description of how the problem should be solved.
+ * Additional info can be provided via {@link #getMoreInfo()}.
+ *
+ * @return an explanation of the issue, never null.
+ */
+ public String getExplanation() {
+ return mExplanation;
+ }
+
+ /**
+ * The category, or null if no category has been assigned
+ *
+ * @return the category, or null if no category has been assigned
+ */
+ public String getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * Returns a priority, in the range 1-10, with 10 being the most severe and
+ * 1 the least
+ *
+ * @return a priority from 1 to 10
+ */
+ public int getPriority() {
+ return mPriority;
+ }
+
+ /**
+ * Returns the default severity of the issues found by this detector (some
+ * tools may allow the user to specify custom severities for detectors).
+ *
+ * @return the severity of the issues found by this detector
+ */
+ public Severity getDefaultSeverity() {
+ return mSeverity;
+ }
+
+ /**
+ * Returns a link (a URL string) to more information, or null
+ *
+ * @return a link to more information, or null
+ */
+ public String getMoreInfo() {
+ return mMoreInfoUrl;
+ }
+
+ /**
+ * Sorts the detectors alphabetically by id. This is intended to make it
+ * convenient to store settings for detectors in a fixed order. It is not
+ * intended as the order to be shown to the user; for that, a tool embedding
+ * lint might consider the priorities, categories, severities etc of the
+ * various detectors.
+ *
+ * @param other the {@link Issue} to compare this issue to
+ */
+ public int compareTo(Issue other) {
+ return getId().compareTo(other.getId());
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LayoutDetector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LayoutDetector.java
new file mode 100644
index 0000000..967d3a1
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LayoutDetector.java
@@ -0,0 +1,91 @@
+/*
+ * 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.resources.ResourceFolderType;
+
+import org.w3c.dom.Element;
+
+/**
+ * Abstract class specifically intended for layout detectors which provides some
+ * common utility methods shared by layout detectors.
+ * <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 abstract class LayoutDetector extends ResourceXmlDetector {
+ // Layouts
+ protected static final String FRAME_LAYOUT = "FrameLayout"; //$NON-NLS-1$
+ protected static final String LINEAR_LAYOUT = "LinearLayout"; //$NON-NLS-1$
+ protected static final String SCROLL_VIEW = "ScrollView"; //$NON-NLS-1$
+ protected static final String GALLERY = "Gallery"; //$NON-NLS-1$
+ protected static final String GRID_VIEW = "GridView"; //$NON-NLS-1$
+ protected static final String LIST_VIEW = "ListView"; //$NON-NLS-1$
+ protected static final String TEXT_VIEW = "TextView"; //$NON-NLS-1$
+ protected static final String IMAGE_VIEW = "ImageView"; //$NON-NLS-1$
+ protected static final String INCLUDE = "include"; //$NON-NLS-1$
+ protected static final String MERGE = "merge"; //$NON-NLS-1$
+ protected static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; //$NON-NLS-1$
+
+ // Attributes
+ protected static final String ATTR_ID = "id"; //$NON-NLS-1$
+ protected static final String ATTR_LAYOUT_GRAVITY = "layout_gravity"; //$NON-NLS-1$
+ protected static final String ATTR_LAYOUT_WIDTH = "layout_width"; //$NON-NLS-1$
+ protected static final String ATTR_LAYOUT_HEIGHT = "layout_height"; //$NON-NLS-1$
+ protected static final String ATTR_LAYOUT_WEIGHT = "layout_weight"; //$NON-NLS-1$
+ protected static final String ATTR_PADDING = "padding"; //$NON-NLS-1$
+ protected static final String ATTR_PADDING_BOTTOM = "paddingBottom"; //$NON-NLS-1$
+ protected static final String ATTR_PADDING_TOP = "paddingTop"; //$NON-NLS-1$
+ protected static final String ATTR_PADDING_RIGHT = "paddingRight"; //$NON-NLS-1$
+ protected static final String ATTR_PADDING_LEFT = "paddingLeft"; //$NON-NLS-1$
+ protected static final String ATTR_FOREGROUND = "foreground"; //$NON-NLS-1$
+ protected static final String ATTR_BACKGROUND = "background"; //$NON-NLS-1$
+ protected static final String ATTR_ORIENTATION = "orientation"; //$NON-NLS-1$
+ protected static final String ATTR_LAYOUT = "layout"; //$NON-NLS-1$
+
+ // Attribute values
+ protected static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$
+ protected static final String VALUE_MATCH_PARENT = "match_parent"; //$NON-NLS-1$
+ protected static final String VALUE_VERTICAL = "vertical"; //$NON-NLS-1$
+ protected static final String VALUE_LAYOUT_PREFIX = "@layout/"; //$NON-NLS-1$
+
+ @Override
+ public boolean appliesTo(ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.LAYOUT;
+ }
+
+ private static boolean isFillParent(Element element, String dimension) {
+ String width = element.getAttributeNS(ANDROID_URI, dimension);
+ return width.equals(VALUE_MATCH_PARENT) || width.equals(VALUE_FILL_PARENT);
+ }
+
+ protected static boolean isWidthFillParent(Element element) {
+ return isFillParent(element, ATTR_LAYOUT_WIDTH);
+ }
+
+ protected static boolean isHeightFillParent(Element element) {
+ return isFillParent(element, ATTR_LAYOUT_HEIGHT);
+ }
+
+ protected boolean hasPadding(Element root) {
+ return root.hasAttributeNS(ANDROID_URI, ATTR_PADDING)
+ || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_LEFT)
+ || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_RIGHT)
+ || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_TOP)
+ || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_BOTTOM);
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java
new file mode 100644
index 0000000..feac79e
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java
@@ -0,0 +1,100 @@
+/*
+ * 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 java.io.File;
+
+/**
+ * Location information for a warning
+ * <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 Location {
+ private final File mFile;
+ private final Position mStart;
+ private final Position mEnd;
+ private Location mSecondary;
+
+ /**
+ * Constructs a new location range for the given file, from start to end. If
+ * the length of the range is not known, end may be null.
+ *
+ * @param file the associated file (but see the documentation for
+ * {@link #getFile()} for more information on what the file
+ * represents)
+ * @param start the starting position, never null
+ * @param end the ending position, or null
+ */
+ public Location(File file, Position start, Position end) {
+ super();
+ this.mFile = file;
+ this.mStart = start;
+ this.mEnd = end;
+ }
+
+ /**
+ * Returns the file containing the warning. Note that the file *itself* may
+ * not yet contain the error. When editing a file in the IDE for example,
+ * the tool could generate warnings in the background even before the
+ * document is saved. However, the file is used as a identifying token for
+ * the document being edited, and the IDE integration can map this back to
+ * error locations in the editor source code.
+ *
+ * @return the file handle for the location
+ */
+ public File getFile() {
+ return mFile;
+ }
+
+ /**
+ * The start position of the range
+ *
+ * @return the start position of the range, never null
+ */
+ public Position getStart() {
+ return mStart;
+ }
+
+ /**
+ * The end position of the range
+ *
+ * @return the start position of the range, may be null for an empty range
+ */
+ public Position getEnd() {
+ return mEnd;
+ }
+
+ /**
+ * Returns a secondary location associated with this location (if
+ * applicable), or null.
+ *
+ * @return a secondary location or null
+ */
+ public Location getSecondary() {
+ return mSecondary;
+ }
+
+ /**
+ * Sets a secondary location for this location.
+ *
+ * @param secondary a secondary location associated with this location
+ */
+ public void setSecondary(Location secondary) {
+ this.mSecondary = secondary;
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Position.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Position.java
new file mode 100644
index 0000000..e5c1a9a
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Position.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/**
+ * Information about a position in a file/document.
+ * <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 abstract class Position {
+ /**
+ * Returns the line number (0-based where the first line is line 0)
+ *
+ * @return the 0-based line number
+ */
+ public abstract int getLine();
+
+ /**
+ * The character offset
+ *
+ * @return the 0-based character offset
+ */
+ public abstract int getOffset();
+
+ /**
+ * Returns the column number (where the first character on the line is 0),
+ * or -1 if unknown
+ *
+ * @return the 0-based column number
+ */
+ public abstract int getColumn();
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ResourceXmlDetector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ResourceXmlDetector.java
new file mode 100644
index 0000000..818d53f
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ResourceXmlDetector.java
@@ -0,0 +1,200 @@
+/*
+ * 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.resources.ResourceFolderType;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Specialized detector intended for XML resources. Detectors that apply to XML
+ * resources should extend this detector instead since it provides special
+ * iteration hooks that are more efficient.
+ * <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 abstract class ResourceXmlDetector extends Detector {
+ private static final String XML_SUFFIX = ".xml"; //$NON-NLS-1$
+
+ /**
+ * Special marker collection returned by {@link #getApplicableElements()} or
+ * {@link #getApplicableAttributes()} to indicate that the check should be
+ * invoked on all elements or all attributes
+ */
+ public static final List<String> ALL = new ArrayList<String>(0);
+
+ protected final static String ANDROID_URI =
+ "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
+
+ @Override
+ public boolean appliesTo(Context context, File file) {
+ return isXmlFile(file);
+ }
+
+ /**
+ * Returns whether this detector applies to the given folder type. This
+ * allows the detectors to be pruned from iteration, so for example when we
+ * are analyzing a string value file we don't need to look up detectors
+ * related to layout.
+ *
+ * @param folderType the folder type to be visited
+ * @return true if this detector can apply to resources in folders of the
+ * given type
+ */
+ public boolean appliesTo(ResourceFolderType folderType) {
+ return true;
+ }
+
+ @Override
+ public void run(Context context) {
+ // The infrastructure should never call this method on an xml detector since
+ // it will run the various visitors instead
+ assert false;
+ }
+
+ /**
+ * Visit the given document. The detector is responsible for its own iteration
+ * through the document.
+ * @param context information about the document being analyzed
+ * @param document the document to examine
+ */
+ public void visitDocument(Context context, Document document) {
+ // Only called if getApplicableElements() *and* getApplicableAttributes() returned null
+ throw new IllegalArgumentException(this.getClass() + " must override visitDocument");
+ }
+
+ /**
+ * Visit the given element.
+ * @param context information about the document being analyzed
+ * @param element the element to examine
+ */
+ public void visitElement(Context context, Element element) {
+ // Only called if getApplicableElements() returned non-null
+ throw new IllegalArgumentException(this.getClass() + " must override visitElement");
+ }
+
+ /**
+ * Visit the given element after its children have been analyzed.
+ * @param context information about the document being analyzed
+ * @param element the element to examine
+ */
+ public void visitElementAfter(Context context, Element element) {
+ // Optional
+ }
+
+ /**
+ * Visit the given attribute.
+ * @param context information about the document being analyzed
+ * @param attribute the attribute node to examine
+ */
+ public void visitAttribute(Context context, Attr attribute) {
+ // Only called if getApplicableAttributes() returned non-null
+ throw new IllegalArgumentException(this.getClass() + " must override visitAttribute");
+ }
+
+ /**
+ * Returns the list of elements that this detector wants to analyze. If non
+ * null, this detector will be called (specifically, the
+ * {@link #visitElement} method) for each matching element in the document.
+ * <p>
+ * If this method returns null, and {@link #getApplicableAttributes()} also returns
+ * null, then the {@link #visitDocument} method will be called instead.
+ *
+ * @return a collection of elements, or null, or the special
+ * {@link ResourceXmlDetector#ALL} marker to indicate that every single
+ * element should be analyzed.
+ */
+ public Collection<String> getApplicableElements() {
+ return null;
+ }
+
+ /**
+ * Returns the list of attributes that this detector wants to analyze. If non
+ * null, this detector will be called (specifically, the
+ * {@link #visitAttribute} method) for each matching attribute in the document.
+ * <p>
+ * If this method returns null, and {@link #getApplicableElements()} also returns
+ * null, then the {@link #visitDocument} method will be called instead.
+ *
+ * @return a collection of attributes, or null, or the special
+ * {@link ResourceXmlDetector#ALL} marker to indicate that every single
+ * attribute should be analyzed.
+ */
+ public Collection<String> getApplicableAttributes() {
+ return null;
+ }
+
+ /**
+ * Returns true if the given file represents an XML file
+ *
+ * @param file the file to be checked
+ * @return true if the given file is an xml file
+ */
+ public static boolean isXmlFile(File file) {
+ String string = file.getName();
+ return string.regionMatches(true, string.length() - XML_SUFFIX.length(),
+ XML_SUFFIX, 0, XML_SUFFIX.length());
+ }
+
+ /**
+ * Returns the children elements of the given node
+ *
+ * @param node the parent node
+ * @return a list of element children, never null
+ */
+ public static List<Element> getChildren(Node node) {
+ NodeList childNodes = node.getChildNodes();
+ List<Element> children = new ArrayList<Element>(childNodes.getLength());
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ children.add((Element) child);
+ }
+ }
+
+ return children;
+ }
+
+ /**
+ * Returns the <b>number</b> of children of the given node
+ *
+ * @param node the parent node
+ * @return the count of element children
+ */
+ public static int getChildCount(Node node) {
+ NodeList childNodes = node.getChildNodes();
+ int childCount = 0;
+ for (int i = 0, n = childNodes.getLength(); i < n; i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ childCount++;
+ }
+ }
+
+ return childCount;
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
new file mode 100644
index 0000000..e9382fb
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.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;
+
+/**
+ * The scope of a detector is the set of files a detector must consider when
+ * performing its analysis. This can be used to determine when issues are
+ * potentially obsolete, whether a detector should re-run on a file save, etc.
+ */
+public enum Scope {
+ /** The analysis only considers a single file at a time */
+ SINGLE_FILE,
+
+ /** The analysis considers more than one file but only resource files */
+ RESOURCES,
+
+ /** The analysis considers more than one file but only the Java code */
+ JAVA_CODE,
+
+ /**
+ * The analysis considers both the Java code in the project and any
+ * libraries
+ */
+ JAVA,
+
+ /** The analysis considers the full project */
+ PROJECT;
+
+ /**
+ * Returns true if this scope is within the given scope. For example a file
+ * scope is within a project scope, but a project scope is not within a file
+ * scope.
+ *
+ * @param scope the scope to compare with
+ * @return true if this scope is within the other scope
+ */
+ public boolean within(Scope scope) {
+ if (this == scope) {
+ return true;
+ }
+ if (scope == PROJECT) {
+ // Everything is within a project
+ return true;
+ }
+
+ if (this == SINGLE_FILE) {
+ // A single file is within everything else
+ return true;
+ }
+
+ if (this == JAVA_CODE) {
+ return scope == JAVA; // or scope == PROJECT but that's handled above
+ }
+
+ return false;
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java
new file mode 100644
index 0000000..3161cd2
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+/**
+ * Severity of an issue found by lint
+ * <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 enum Severity {
+ /**
+ * Errors: Use sparingly because a warning marked as an error will be
+ * considered fatal and will abort Export APK etc in ADT
+ */
+ ERROR("Error"),
+
+ /**
+ * Warning: Probably a problem.
+ */
+ WARNING("Warning"),
+
+ /**
+ * Information only: Might not be a problem, but the check has found
+ * something interesting to say about the code.
+ */
+ INFORMATIONAL("Information"),
+
+ /**
+ * Ignore: The user doesn't want to see this issue
+ */
+ IGNORE("Ignore");
+
+ private String mDisplay;
+
+ private Severity(String display) {
+ mDisplay = display;
+ }
+
+ /**
+ * Returns a description of this severity suitable for display to the user
+ *
+ * @return a description of the severity
+ */
+ public String getDescription() {
+ return mDisplay;
+ }
+} \ No newline at end of file
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Speed.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Speed.java
new file mode 100644
index 0000000..2f7f17a
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Speed.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/** Enum which describes the different computation speeds of various detectors */
+public enum Speed {
+ /** The detector can run very quickly */
+ FAST("Fast"),
+
+ /** The detector runs reasonably fast */
+ NORMAL("Normal"),
+
+ /** The detector might take a long time to run */
+ SLOW("Slow");
+
+ private String mDisplayName;
+
+ Speed(String displayName) {
+ mDisplayName = displayName;
+ }
+
+ /**
+ * Returns the user-visible description of the speed of the given
+ * detector
+ *
+ * @return the description of the speed to display to the user
+ */
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+} \ No newline at end of file
diff --git a/lint/libs/lint_checks/.classpath b/lint/libs/lint_checks/.classpath
new file mode 100644
index 0000000..1172683
--- /dev/null
+++ b/lint/libs/lint_checks/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/common"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/lint/libs/lint_checks/.project b/lint/libs/lint_checks/.project
new file mode 100644
index 0000000..0dc9856
--- /dev/null
+++ b/lint/libs/lint_checks/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>lint-checks</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/lint/libs/lint_checks/.settings/org.eclipse.jdt.core.prefs b/lint/libs/lint_checks/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e755df2
--- /dev/null
+++ b/lint/libs/lint_checks/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,71 @@
+#Thu Jun 09 12:26:44 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/lint/libs/lint_checks/.settings/org.moreunit.prefs b/lint/libs/lint_checks/.settings/org.moreunit.prefs
new file mode 100644
index 0000000..1777680
--- /dev/null
+++ b/lint/libs/lint_checks/.settings/org.moreunit.prefs
@@ -0,0 +1,5 @@
+#Wed Oct 12 13:47:50 PDT 2011
+eclipse.preferences.version=1
+org.moreunit.prefixes=
+org.moreunit.unitsourcefolder=lint-checks\:src\:lint_check-tests\:src
+org.moreunit.useprojectsettings=true
diff --git a/lint/libs/lint_checks/Android.mk b/lint/libs/lint_checks/Android.mk
new file mode 100644
index 0000000..7970c1c
--- /dev/null
+++ b/lint/libs/lint_checks/Android.mk
@@ -0,0 +1,23 @@
+# Copyright 2011 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+
+LOCAL_JAR_MANIFEST := etc/manifest.txt
+
+# If the dependency list is changed, etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+ common \
+ lint_api
+
+LOCAL_MODULE := lint_checks
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/lint/libs/lint_checks/NOTICE b/lint/libs/lint_checks/NOTICE
new file mode 100644
index 0000000..becc120
--- /dev/null
+++ b/lint/libs/lint_checks/NOTICE
@@ -0,0 +1,190 @@
+
+ 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/lint/libs/lint_checks/etc/manifest.txt b/lint/libs/lint_checks/etc/manifest.txt
new file mode 100644
index 0000000..2fd2a3a
--- /dev/null
+++ b/lint/libs/lint_checks/etc/manifest.txt
@@ -0,0 +1 @@
+Class-Path: lint_api.jar common.jar
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java
new file mode 100644
index 0000000..0b0a527
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java
@@ -0,0 +1,92 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Check which looks for accessibility problems like missing content descriptions
+ * <p>
+ * TODO: Resolve styles and don't warn where styles are defining the content description
+ * (though this seems unusual; content descriptions are not typically generic enough to
+ * put in styles)
+ */
+public class AccessibilityDetector extends LayoutDetector {
+ /** The attribute for describing visual content */
+ public static final String ATTR_CONTENT_DESCRIPTION = "contentDescription"; //$NON-NLS-1$
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "ContentDescription", //$NON-NLS-1$
+ "Ensures that image widgets provide a contentDescription",
+ "Non-textual widgets like ImageViews and ImageButtons should use the " +
+ "contentDescription attribute to specify a textual description of " +
+ "the widget such that screen readers and other accessibility tools " +
+ "can adequately describe the user interface.",
+ CATEGORY_A11Y, 5, Severity.WARNING, null);
+
+ /** Constructs a new accessibility check */
+ public AccessibilityDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { ISSUE };
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(new String[] {
+ "ImageButton", //$NON-NLS-1$
+ "ImageView" //$NON-NLS-1$
+ });
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ if (!element.hasAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION)) {
+ context.toolContext.report(ISSUE, context.getLocation(context),
+ "[Accessibility] Missing contentDescription attribute on image");
+ } else {
+ String attribute = element.getAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION);
+ if (attribute.length() == 0 || attribute.equals("TODO")) { //$NON-NLS-1$
+ context.toolContext.report(ISSUE, context.getLocation(context),
+ "[Accessibility] Empty contentDescription attribute on image");
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinDetectorRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinDetectorRegistry.java
new file mode 100644
index 0000000..704d9f8
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinDetectorRegistry.java
@@ -0,0 +1,60 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Registry which provides a list of checks to be performed on an Android project */
+public class BuiltinDetectorRegistry extends com.android.tools.lint.api.DetectorRegistry {
+ private static final List<Detector> sDetectors;
+ static {
+ // TODO: Maybe just store class names here instead such that
+ // each invocation can have its own context with fields without
+ // worrying about having to clear state in beforeCheckProject or beforeCheckFile?
+ List<Detector> detectors = new ArrayList<Detector>();
+ detectors.add(new AccessibilityDetector());
+ detectors.add(new DuplicateIdDetector());
+ detectors.add(new StateListDetector());
+ detectors.add(new InefficientWeightDetector());
+ detectors.add(new ScrollViewChildDetector());
+ detectors.add(new MergeRootFrameLayoutDetector());
+ detectors.add(new NestedScrollingWidgetDetector());
+ detectors.add(new ChildCountDetector());
+ detectors.add(new UseCompoundDrawableDetector());
+ detectors.add(new UselessViewDetector());
+ detectors.add(new TooManyViewsDetector());
+
+ // TODO: Populate dynamically somehow?
+
+ sDetectors = Collections.unmodifiableList(detectors);
+ }
+
+ /**
+ * Constructs a new {@link BuiltinDetectorRegistry}
+ */
+ public BuiltinDetectorRegistry() {
+ }
+
+ @Override
+ public List<? extends Detector> getDetectors() {
+ return sDetectors;
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java
new file mode 100644
index 0000000..34aed38
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java
@@ -0,0 +1,101 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Check which makes sure that views have the expected number of declared
+ * children (e.g. at most one in ScrollViews and none in AdapterViews)
+ */
+public class ChildCountDetector extends LayoutDetector {
+
+ /** The main issue discovered by this detector */
+ public static final Issue SCROLLVIEW_ISSUE = Issue.create(
+ "ScrollViewCount", //$NON-NLS-1$
+ "Checks that ScrollViews have exactly one child widget",
+ "ScrollViews can only have one child widget. If you want more children, wrap them " +
+ "in a container layout.",
+ CATEGORY_LAYOUT, 8, Severity.WARNING, null);
+
+ /** The main issue discovered by this detector */
+ public static final Issue ADAPTERVIEW_ISSUE = Issue.create(
+ "AdapterViewChildren", //$NON-NLS-1$
+ "Checks that AdapterViews do not define their children in XML",
+ "AdapterViews such as ListViews must be configured with data from Java code, " +
+ "such as a ListAdapter.",
+ CATEGORY_LAYOUT, 8, Severity.WARNING,
+ "http://developer.android.com/reference/android/widget/AdapterView.html"); //$NON-NLS-1$
+
+ /** Constructs a new {@link ChildCountDetector} */
+ public ChildCountDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { SCROLLVIEW_ISSUE, ADAPTERVIEW_ISSUE };
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(new String[] {
+ SCROLL_VIEW,
+ HORIZONTAL_SCROLL_VIEW,
+ LIST_VIEW,
+ GRID_VIEW
+ // TODO: Shouldn't Spinner be in this list too? (Was not there in layoutopt)
+ });
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ int childCount = getChildCount(element);
+ String tagName = element.getTagName();
+ if (tagName.equals(SCROLL_VIEW) || tagName.equals(HORIZONTAL_SCROLL_VIEW)) {
+ if (childCount > 1) {
+ context.toolContext.report(SCROLLVIEW_ISSUE, context.getLocation(element),
+ "A scroll view can have only one child");
+ }
+ } else {
+ // Adapter view
+ if (childCount > 0) {
+ context.toolContext.report(ADAPTERVIEW_ISSUE, context.getLocation(element),
+ "A list/grid should have no children declared in XML");
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java
new file mode 100644
index 0000000..4c6e177
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java
@@ -0,0 +1,388 @@
+/*
+ * 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.checks;
+
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Checks for duplicate ids within a layout and within an included layout
+ */
+public class DuplicateIdDetector extends LayoutDetector {
+ private Set<String> mIds;
+ private Map<File, Set<String>> mFileToIds;
+ private Map<File, List<String>> mIncludes;
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "DuplicateIds", //$NON-NLS-1$
+ "Checks for duplicate ids within a single layout or within an include hierarchy",
+ "It's okay for two independent layouts to use the same ids. However, within " +
+ "a single layout (including the case where two separate layouts are fused " +
+ "together with an include tag) the ids should be unique such that the" +
+ "Activity#findViewById() method can work predictably.",
+ CATEGORY_LAYOUT, 7, Severity.WARNING, null);
+
+ /** Constructs a duplicate id check */
+ public DuplicateIdDetector() {
+ };
+
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { ISSUE };
+ }
+
+ @Override
+ public boolean appliesTo(ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.MENU;
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ // TODO: Split this detector in half, since single-layout duplicates can be checked
+ // quickly.
+ return Scope.RESOURCES;
+ }
+
+ @Override
+ public Collection<String> getApplicableAttributes() {
+ return Collections.singletonList(ATTR_ID);
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(INCLUDE);
+ }
+
+ @Override
+ public void beforeCheckFile(Context context) {
+ mIds = new HashSet<String>();
+ }
+
+ @Override
+ public void afterCheckFile(Context context) {
+ // Store this layout's set of ids for full project analysis in afterCheckProject
+ mFileToIds.put(context.file, mIds);
+
+ mIds = null;
+ }
+
+ @Override
+ public void beforeCheckProject(Context context) {
+ mFileToIds = new HashMap<File, Set<String>>();
+ mIncludes = new HashMap<File, List<String>>();
+ }
+
+ @Override
+ public void afterCheckProject(Context context) {
+ // Look for duplicates
+ if (mIncludes.size() > 0) {
+ // Traverse all the include chains and ensure that there are no duplicates
+ // across.
+ // First perform a topological sort such such
+ checkForIncludeDuplicates(context);
+ }
+
+ mFileToIds = null;
+ mIncludes = null;
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ // Record include graph such that we can look for inter-layout duplicates after the
+ // project has been fully checked
+
+ String layout = element.getAttribute(ATTR_LAYOUT); // NOTE: Not in android: namespace
+ if (layout.startsWith(VALUE_LAYOUT_PREFIX)) { // Ignore @android:layout/ layouts
+ layout = layout.substring(VALUE_LAYOUT_PREFIX.length());
+
+ List<String> to = mIncludes.get(context.file);
+ if (to == null) {
+ to = new ArrayList<String>();
+ mIncludes.put(context.file, to);
+ }
+ to.add(layout);
+ }
+ }
+
+ private void checkForIncludeDuplicates(Context context) {
+ // Consider this scenario:
+ // first/foo.xml: include @layout/second
+ // first-land/foo.xml: define @+id/foo
+ // second-land/bar.xml define @+id/bar
+ // second-port/bar.xml define @+id/foo
+ // Here there's no problem, because even though @layout/first includes @layout/second,
+ // the only duplicate is "foo" which appears only in the combination first-land and
+ // second-port which won't be matched up together.
+ // In this analysis we won't go that far; we'll just look at the OVERALL set of
+ // includes. In other words, we'll consider the set of ids defined by "first" to
+ // be {"foo"}, and the set of ids defined by "second" to be {"foo","bar"}, and
+ // so there is a potential conflict.
+
+ // Map from layout resource name (instead of file) to referenced layouts.
+ // Note: Unlike mIncludes, this merges all the configurations for a single layout
+ Map<String, Set<String>> resourceToLayouts =
+ new HashMap<String, Set<String>>(mIncludes.size());
+ Map<String, Set<String>> resourceToIds =
+ new HashMap<String, Set<String>>(mIncludes.size());
+
+ for (Entry<File, List<String>> entry : mIncludes.entrySet()) {
+ File file = entry.getKey();
+ String from = getLayoutName(file);
+
+ // Merge include lists
+ List<String> layouts = entry.getValue();
+ Set<String> set = resourceToLayouts.get(from);
+ if (set == null) {
+ resourceToLayouts.put(from, new HashSet<String>(layouts));
+ } else {
+ set.addAll(layouts);
+ }
+ }
+
+ // Merge id maps
+ for (Entry<File, Set<String>> entry : mFileToIds.entrySet()) {
+ File file = entry.getKey();
+ String from = getLayoutName(file);
+ Set<String> ids = entry.getValue();
+ if (ids != null) {
+ Set<String> set = resourceToIds.get(from);
+ if (set == null) {
+ // I might be able to just reuse the set instance here instead of duplicating
+ resourceToIds.put(from, new HashSet<String>(ids));
+ } else {
+ set.addAll(ids);
+ }
+ }
+ }
+
+ // Set of layouts that are included from somewhere else. We will use
+ Set<String> included = new HashSet<String>();
+ for (Set<String> s : resourceToLayouts.values()) {
+ included.addAll(s);
+ }
+
+ // Compute the set of layouts which include some other layouts, but which are not
+ // included themselves, meaning they are the "roots" to start searching through
+ // include chains from
+ Set<String> entryPoints = new HashSet<String>(resourceToLayouts.keySet());
+ entryPoints.removeAll(included);
+
+ // Perform DFS on the include graph and look for a cycle; if we find one, produce
+ // a chain of includes on the way back to show to the user
+ HashMap<String, Set<String>> mergedIds = new HashMap<String, Set<String>>();
+ Set<String> visiting = new HashSet<String>();
+ for (String from : entryPoints) {
+ visiting.clear();
+ getMergedIds(context, from, visiting, resourceToLayouts, resourceToIds, mergedIds);
+ }
+ }
+
+ private String getLayoutName(File file) {
+ String name = file.getName();
+ int dotIndex = name.indexOf('.');
+ if (dotIndex != -1) {
+ name = name.substring(0, dotIndex);
+ }
+ return name;
+ }
+
+ /**
+ * Computes the complete set of ids in a layout (including included layouts,
+ * transitively) and emits warnings when it detects that there is a
+ * duplication
+ */
+ private Set<String> getMergedIds(
+ Context context,
+ String from,
+ Set<String> visiting,
+ Map<String, Set<String>> resourceToLayouts,
+ Map<String, Set<String>> resourceToIds,
+ Map<String, Set<String>> mergedIds) {
+
+ Set<String> merged = mergedIds.get(from);
+ if (merged == null) {
+ visiting.add(from);
+
+ Set<String> currentIds = resourceToIds.get(from);
+ if (currentIds != null && currentIds.size() > 0) {
+ merged = new HashSet<String>(currentIds);
+ } else {
+ merged = new HashSet<String>();
+ }
+ Set<String> includes = resourceToLayouts.get(from);
+ if (includes != null && includes.size() > 0) {
+ for (String include : includes) {
+ if (!visiting.contains(include)) {
+ Set<String> otherIds = getMergedIds(context, include, visiting,
+ resourceToLayouts, resourceToIds, mergedIds);
+ // Look for overlap
+ for (String id : otherIds) {
+ if (merged.contains(id)) {
+ // Find the (first) file among the various configuration variations
+ // which defines the id
+ File first = null;
+ File second = null;
+ for (Map.Entry<File, Set<String>> entry : mFileToIds.entrySet()) {
+ File file = entry.getKey();
+ String name = getLayoutName(file);
+ if (name.equals(from)) {
+ Set<String> fileIds = entry.getValue();
+ if (fileIds.contains(id)) {
+ first = file;
+ }
+ }
+ if (name.equals(include)) {
+ Set<String> fileIds = entry.getValue();
+ if (fileIds.contains(id)) {
+ second = file;
+ }
+ }
+ }
+ if (first == null) {
+ for (Map.Entry<File, List<String>> entry
+ : mIncludes.entrySet()) {
+ File file = entry.getKey();
+ String name = getLayoutName(file);
+ if (name.equals(from)) {
+ first = file;
+ }
+ }
+ }
+
+ String includer = second != null ? second.getName() : include;
+ List<String> chain = new ArrayList<String>();
+ chain.add(from);
+ findOrigin(chain, from, id, new HashSet<String>(),
+ resourceToLayouts, resourceToIds);
+ String msg = null;
+ if (chain.size() > 2) { // < 2: it's a directly include & obvious
+ StringBuilder sb = new StringBuilder();
+ for (String layout : chain) {
+ if (sb.length() > 0) {
+ sb.append(" => ");
+ }
+ sb.append(layout);
+ }
+ msg = String.format(
+ "Duplicate id %1$s, already defined in layout %2$s which is included in this layout (%3$s)",
+ id, includer, sb.toString());
+ } else {
+ msg = String.format(
+ "Duplicate id %1$s, already defined in layout %2$s which is included in this layout",
+ id, includer);
+ }
+
+ Location location = new Location(first, null, null);
+ if (second != null) {
+ // Also record the secondary location
+ location.setSecondary(new Location(second, null, null));
+ }
+ context.toolContext.report(ISSUE, location, msg);
+ } else {
+ merged.add(id);
+ }
+ }
+ }
+ }
+ }
+ mergedIds.put(from, merged);
+ visiting.remove(from);
+ }
+
+ return merged;
+ }
+
+ /**
+ * Compute the include chain which provided id into this layout. We could
+ * have tracked this while we were already performing a depth first search,
+ * but we're choosing to be faster before we know there's an error and take
+ * ore time to produce diagnostics if an actual error is found.
+ */
+ private boolean findOrigin(
+ List<String> chain,
+ String from,
+ String id,
+ Set<String> visiting,
+ Map<String, Set<String>> resourceToLayouts,
+ Map<String, Set<String>> resourceToIds) {
+ visiting.add(from);
+
+ Set<String> includes = resourceToLayouts.get(from);
+ if (includes != null && includes.size() > 0) {
+ for (String include : includes) {
+ if (visiting.contains(include)) {
+ return false;
+ }
+
+ Set<String> ids = resourceToIds.get(include);
+ if (ids != null && ids.contains(id)) {
+ chain.add(include);
+ return true;
+ }
+
+ if (findOrigin(chain, include, id, visiting, resourceToLayouts, resourceToIds)) {
+ chain.add(include);
+ return true;
+ }
+ }
+ }
+
+ visiting.remove(from);
+
+ return false;
+ }
+
+ @Override
+ public void visitAttribute(Context context, Attr attribute) {
+ assert attribute.getLocalName().equals(ATTR_ID);
+ String id = attribute.getValue();
+ if (mIds.contains(id)) {
+ context.toolContext.report(ISSUE, context.getLocation(attribute),
+ String.format("Duplicate id %1$s, already defined earlier in this layout",
+ id));
+ } else if (id.startsWith("@+id/")) { //$NON-NLS-1$
+ mIds.add(id);
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
new file mode 100644
index 0000000..e29eaca
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
@@ -0,0 +1,108 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Checks whether a layout_weight is declared inefficiently.
+ */
+public class InefficientWeightDetector extends LayoutDetector {
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "InefficientWeight", //$NON-NLS-1$
+ "Looks for inefficient weight declarations in LinearLayouts",
+ "When only a single widget in a LinearLayout defines a weight, it is more " +
+ "efficient to assign a width/height of 0dp to it since it will absorb all " +
+ "the remaining space anyway. With a declared width/height of 0dp it " +
+ "does not have to measure its own size first.",
+ CATEGORY_LAYOUT, 5, Severity.WARNING, null);
+
+ /** Constructs a new {@link InefficientWeightDetector} */
+ public InefficientWeightDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { ISSUE };
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Collections.singletonList(LINEAR_LAYOUT);
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ List<Element> children = getChildren(element);
+ // See if there is exactly one child with a weight
+ Element weightChild = null;
+ for (Element child : children) {
+ if (child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) {
+ if (weightChild != null) {
+ // More than one child defining a weight!
+ return;
+ } else {
+ weightChild = child;
+ }
+ }
+ }
+
+ if (weightChild != null) {
+ String dimension;
+ if (VALUE_VERTICAL.equals(element.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION))) {
+ dimension = ATTR_LAYOUT_HEIGHT;
+ } else {
+ dimension = ATTR_LAYOUT_WIDTH;
+ }
+ Attr sizeNode = weightChild.getAttributeNodeNS(ANDROID_URI, dimension);
+ String size = sizeNode != null ? sizeNode.getValue() : "(undefined)";
+ if (!size.startsWith("0")) { //$NON-NLS-1$
+ String msg = String.format(
+ "Use a %1$s of 0dip instead of %2$s for better performance",
+ dimension, size);
+ context.toolContext.report(ISSUE,
+ context.getLocation(sizeNode != null ? sizeNode : weightChild),
+ msg);
+
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
new file mode 100644
index 0000000..54d795e
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
@@ -0,0 +1,76 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
+ */
+public class MergeRootFrameLayoutDetector extends LayoutDetector {
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "MergeRootFrame", //$NON-NLS-1$
+ "Checks whether a root <FrameLayout> can be replaced with a <merge> tag",
+ "If a <FrameLayout> is the root of a layout and does not provide background " +
+ "or padding etc, it can be replaced with a <merge> tag which is slightly " +
+ "more efficient.",
+ CATEGORY_LAYOUT, 4, Severity.WARNING, null);
+
+ /** Constructs a new {@link MergeRootFrameLayoutDetector} */
+ public MergeRootFrameLayoutDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { ISSUE };
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public void visitDocument(Context context, Document document) {
+ Element root = document.getDocumentElement();
+ if (root.getTagName().equals(FRAME_LAYOUT) &&
+ ((isWidthFillParent(root) && isHeightFillParent(root)) ||
+ !root.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY))
+ && !root.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)
+ && !root.hasAttributeNS(ANDROID_URI, ATTR_FOREGROUND)
+ && !hasPadding(root)) {
+ context.toolContext.report(ISSUE,
+ context.getLocation(root),
+ "This <FrameLayout> can be replaced with a <merge> tag");
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
new file mode 100644
index 0000000..d719fcc
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
@@ -0,0 +1,152 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
+ */
+public class NestedScrollingWidgetDetector extends LayoutDetector {
+ private int mVisitingHorizontalScroll;
+ private int mVisitingVerticalScroll;
+
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "NestedScrolling", //$NON-NLS-1$
+ "Checks whether a scrolling widget has any nested scrolling widgets within",
+ // TODO: Better description!
+ "A scrolling widget such as a ScrollView should not contain any nested " +
+ "scrolling widgets since this has various usability issues",
+ CATEGORY_LAYOUT, 7, Severity.WARNING, null);
+
+ /** Constructs a new {@link NestedScrollingWidgetDetector} */
+ public NestedScrollingWidgetDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { ISSUE };
+ }
+
+ @Override
+ public void beforeCheckFile(Context context) {
+ mVisitingHorizontalScroll = 0;
+ mVisitingVerticalScroll = 0;
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(new String[] {
+ SCROLL_VIEW,
+ LIST_VIEW,
+ GRID_VIEW,
+ // Horizontal
+ GALLERY,
+ HORIZONTAL_SCROLL_VIEW
+ });
+ }
+
+ private Element findOuterScrollingWidget(Node node, boolean vertical) {
+ Collection<String> applicableElements = getApplicableElements();
+ while (node != null) {
+ if (node instanceof Element) {
+ Element element = (Element) node;
+ String tagName = element.getTagName();
+ if (applicableElements.contains(tagName)
+ && vertical == isVerticalScroll(element)) {
+ return element;
+ }
+ }
+
+ node = node.getParentNode();
+ }
+
+ return null;
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ boolean vertical = isVerticalScroll(element);
+ if (vertical) {
+ mVisitingVerticalScroll++;
+ } else {
+ mVisitingHorizontalScroll++;
+ }
+
+ if (mVisitingHorizontalScroll > 1 || mVisitingVerticalScroll > 1) {
+ Element parent = findOuterScrollingWidget(element.getParentNode(), vertical);
+ if (parent != null) {
+ String format;
+ if (mVisitingVerticalScroll > 1) {
+ format = "The vertically scrolling %1$s should not contain another " +
+ "vertically scrolling widget (%2$s)";
+ } else {
+ format = "The horizontally scrolling %1$s should not contain another " +
+ "horizontally scrolling widget (%2$s)";
+ }
+ String msg = String.format(format, parent.getTagName(), element.getTagName());
+ context.toolContext.report(ISSUE, context.getLocation(element),
+ msg);
+ }
+ }
+ }
+
+ @Override
+ public void visitElementAfter(Context context, Element element) {
+ if (isVerticalScroll(element)) {
+ mVisitingVerticalScroll--;
+ assert mVisitingVerticalScroll >= 0;
+ } else {
+ mVisitingHorizontalScroll--;
+ assert mVisitingHorizontalScroll >= 0;
+ }
+ }
+
+ private boolean isVerticalScroll(Element element) {
+ String view = element.getTagName();
+ if (view.equals(GALLERY) || view.equals(HORIZONTAL_SCROLL_VIEW)) {
+ return false;
+ } else {
+ // This method should only be called with one of the 5 widget types
+ // listed in getApplicableElements
+ assert view.equals(SCROLL_VIEW) || view.equals(LIST_VIEW) || view.equals(GRID_VIEW);
+ return true;
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java
new file mode 100644
index 0000000..d2ec187
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java
@@ -0,0 +1,91 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Check which looks at the children of ScrollViews and ensures that they fill/match
+ * the parent width instead of setting wrap_content.
+ */
+public class ScrollViewChildDetector extends LayoutDetector {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "ScrollViewSize", //$NON-NLS-1$
+ "Checks that ScrollViews use wrap_content in scrolling dimension",
+ // TODO add a better explanation here!
+ "ScrollView children must set their layout_width or layout_height attributes " +
+ "to wrap_content rather than fill_parent or match_parent in the scrolling " +
+ "dimension",
+ CATEGORY_LAYOUT, 7, Severity.WARNING, null);
+
+ /** Constructs a new {@link ScrollViewChildDetector} */
+ public ScrollViewChildDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { ISSUE };
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(new String[] {
+ SCROLL_VIEW,
+ HORIZONTAL_SCROLL_VIEW
+ });
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ List<Element> children = getChildren(element);
+ boolean isHorizontal = HORIZONTAL_SCROLL_VIEW.equals(element.getTagName());
+ String attributeName = isHorizontal ? ATTR_LAYOUT_WIDTH : ATTR_LAYOUT_HEIGHT;
+ for (Element child : children) {
+ Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, attributeName);
+ String value = sizeNode != null ? sizeNode.getValue() : null;
+ if (VALUE_FILL_PARENT.equals(value) || VALUE_MATCH_PARENT.equals(value)) {
+ String msg = String.format("This %1$s should use android:%2$s=\"wrap_content\"",
+ child.getTagName(), attributeName);
+ context.toolContext.report(ISSUE, context.getLocation(sizeNode),
+ msg);
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java
new file mode 100644
index 0000000..9bb702e
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java
@@ -0,0 +1,100 @@
+/*
+ * 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.checks;
+
+import com.android.resources.ResourceFolderType;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+import java.util.List;
+
+/**
+ * Checks for unreachable states in an Android state list definition
+ */
+public class StateListDetector extends ResourceXmlDetector {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "StateListReachable", //$NON-NLS-1$
+ "Looks for unreachable states in a <selector>",
+ "In a selector, only the last child in the state list should omit a " +
+ "state qualifier. If not, all subsequent items in the list will be ignored " +
+ "since the given item will match all.",
+ CATEGORY_CORRECTNESS, 7, Severity.WARNING, null);
+
+ /** Constructs a new {@link StateListDetector} */
+ public StateListDetector() {
+ };
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { ISSUE };
+ }
+
+ @Override
+ public boolean appliesTo(ResourceFolderType folderType) {
+ return folderType == ResourceFolderType.DRAWABLE;
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public void visitDocument(Context context, Document document) {
+ // TODO: Look for views that don't specify
+ // Display the error token somewhere so it can be suppressed
+ // Emit warning at the end "run with --help to learn how to suppress types of errors/checks";
+ // ("...and this message.")
+
+ Element root = document.getDocumentElement();
+ if (root != null && root.getTagName().equals("selector")) { //$NON-NLS-1$
+ List<Element> children = getChildren(root);
+ for (int i = 0; i < children.size() - 1; i++) {
+ Element child = children.get(i);
+ boolean hasState = false;
+ NamedNodeMap attributes = child.getAttributes();
+ for (int j = 0; j < attributes.getLength(); j++) {
+ Attr attribute = (Attr) attributes.item(j);
+ if (attribute.getLocalName().startsWith("state_")) {
+ hasState = true;
+ break;
+ }
+ }
+ if (!hasState) {
+ context.toolContext.report(ISSUE, context.getLocation(child),
+ String.format("No android:state_ attribute found on <item> %1$d, later states not reachable",
+ i));
+ }
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java
new file mode 100644
index 0000000..c95dbea
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java
@@ -0,0 +1,147 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.ResourceXmlDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+
+/**
+ * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
+ */
+public class TooManyViewsDetector extends LayoutDetector {
+ /** Issue of having too many views in a single layout */
+ public static final Issue TOO_MANY = Issue.create(
+ "TooManyViews", //$NON-NLS-1$
+ "Checks whether a layout has too many views",
+ "Using too many views in a single layout in a layout is bad for " +
+ "performance. Consider using compound drawables or other tricks for " +
+ "reducing the number of views in this layout.\n\n" +
+ "The maximum view count defaults to 80 but can be configured with the " +
+ "environment variable ANDROID_LINT_MAX_VIEW_COUNT.",
+ CATEGORY_PERFORMANCE, 1, Severity.WARNING, null);
+
+ /** Issue of having too deep hierarchies in layouts */
+ public static final Issue TOO_DEEP = Issue.create(
+ "TooDeepLayout", //$NON-NLS-1$
+ "Checks whether a layout hierarchy is too deep",
+ "Layouts with too much nesting is bad for performance. " +
+ "Consider using a flatter layout (such as RelativeLayout or GridLayout)." +
+ "The default maximum depth is 10 but can be configured with the environment " +
+ "variable ANDROID_LINT_MAX_DEPTH.",
+ CATEGORY_PERFORMANCE, 1, Severity.WARNING, null);
+
+ private static final int MAX_VIEW_COUNT;
+ private static final int MAX_DEPTH;
+ static {
+ int maxViewCount = 0;
+ int maxDepth = 0;
+
+ String countValue = System.getenv("ANDROID_LINT_MAX_VIEW_COUNT"); //$NON-NLS-1$
+ if (countValue != null) {
+ try {
+ maxViewCount = Integer.parseInt(countValue);
+ } catch (NumberFormatException nufe) {
+ }
+ }
+ String depthValue = System.getenv("ANDROID_LINT_MAX_DEPTH"); //$NON-NLS-1$
+ if (depthValue != null) {
+ try {
+ maxDepth = Integer.parseInt(depthValue);
+ } catch (NumberFormatException nufe) {
+ }
+ }
+ if (maxViewCount == 0) {
+ maxViewCount = 80;
+ }
+ if (maxDepth == 0) {
+ maxDepth = 10;
+ }
+
+ MAX_VIEW_COUNT = maxViewCount;
+ MAX_DEPTH = maxDepth;
+ }
+
+ private int mViewCount;
+ private int mDepth;
+ private boolean mWarnedAboutDepth;
+
+ /** Constructs a new {@link TooManyViewsDetector} */
+ public TooManyViewsDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { TOO_DEEP, TOO_MANY };
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public void beforeCheckFile(Context context) {
+ mViewCount = mDepth = 0;
+ mWarnedAboutDepth = false;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return ResourceXmlDetector.ALL;
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ mViewCount++;
+ mDepth++;
+
+ if (mDepth == MAX_DEPTH && !mWarnedAboutDepth) {
+ // Have to record whether or not we've warned since we could have many siblings
+ // at the max level and we'd warn for each one. No need to do the same thing
+ // for the view count error since we'll only have view count exactly equal the
+ // max just once.
+ mWarnedAboutDepth = true;
+ String msg = String.format("%1$s has more than %2$d levels, bad for performance",
+ context.file.getName(), MAX_DEPTH);
+ context.toolContext.report(TOO_DEEP, context.getLocation(element), msg);
+ }
+ if (mViewCount == MAX_VIEW_COUNT) {
+ String msg = String.format("%1$s has more than %2$d views, bad for performance",
+ context.file.getName(), MAX_VIEW_COUNT);
+ context.toolContext.report(TOO_MANY, context.getLocation(element), msg);
+ }
+ }
+
+ @Override
+ public void visitElementAfter(Context context, Element element) {
+ mDepth--;
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
new file mode 100644
index 0000000..650e339
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
@@ -0,0 +1,91 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Checks whether the current node can be replaced by a TextView using compound
+ * drawables.
+ */
+public class UseCompoundDrawableDetector extends LayoutDetector {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "UseCompoundDrawables", //$NON-NLS-1$
+ "Checks whether the current node can be replaced by a TextView using compound drawables.",
+ // TODO: OFFER MORE HELP!
+ "A LinearLayout which contains an ImageView and a TextView can be more efficiently " +
+ "handled as a compound drawable",
+ CATEGORY_PERFORMANCE, 6, Severity.WARNING, null);
+
+ /** Constructs a new {@link UseCompoundDrawableDetector} */
+ public UseCompoundDrawableDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { ISSUE };
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ @Override
+ public Collection<String> getApplicableElements() {
+ return Arrays.asList(new String[] {
+ LINEAR_LAYOUT
+ });
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ int childCount = getChildCount(element);
+ if (childCount == 2) {
+ List<Element> children = getChildren(element);
+ Element first = children.get(0);
+ Element second = children.get(1);
+ if ((first.getTagName().equals(IMAGE_VIEW) &&
+ second.getTagName().equals(TEXT_VIEW) &&
+ !first.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) ||
+ ((second.getTagName().equals(IMAGE_VIEW) &&
+ first.getTagName().equals(TEXT_VIEW) &&
+ !second.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)))) {
+ context.toolContext.report(ISSUE, context.getLocation(element),
+ "This tag and its children can be replaced by one <TextView/> and " +
+ "a compound drawable");
+ }
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java
new file mode 100644
index 0000000..6de2264
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java
@@ -0,0 +1,202 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Checks whether the current node can be removed without affecting the layout.
+ */
+public class UselessViewDetector extends LayoutDetector {
+ /** Issue of including a parent that has no value on its own */
+ public static final Issue USELESS_PARENT = Issue.create(
+ "UselessParent", //$NON-NLS-1$
+ "Checks whether a parent layout can be removed.",
+ "A layout with children that has no siblings, is not a scrollview or " +
+ "a root layout, and does not have a background, can be removed and have " +
+ "its children moved directly into the parent for a flatter and more " +
+ "efficient layout hierarchy.",
+ CATEGORY_LAYOUT, 2, Severity.WARNING, null);
+
+ /** Issue of including a leaf that isn't shown */
+ public static final Issue USELESS_LEAF = Issue.create(
+ "UselessLeaf", //$NON-NLS-1$
+ "Checks whether a leaf layout can be removed.",
+ "A layout that has no children or no background can often be removed (since it " +
+ "is invisible) for a flatter and more efficient layout hierarchy.",
+ CATEGORY_LAYOUT, 2, Severity.WARNING, null);
+
+ /** Constructs a new {@link UselessViewDetector} */
+ public UselessViewDetector() {
+ }
+
+ @Override
+ public Issue[] getIssues() {
+ return new Issue[] { USELESS_PARENT, USELESS_LEAF };
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ @Override
+ public Scope getScope() {
+ return Scope.SINGLE_FILE;
+ }
+
+ private static final List<String> CONTAINERS = new ArrayList<String>(20);
+ static {
+ CONTAINERS.add("android.gesture.GestureOverlayView"); //$NON-NLS-1$
+ CONTAINERS.add("AbsoluteLayout"); //$NON-NLS-1$
+ CONTAINERS.add(FRAME_LAYOUT);
+ CONTAINERS.add("GridLayout"); //$NON-NLS-1$
+ CONTAINERS.add(GRID_VIEW);
+ CONTAINERS.add(HORIZONTAL_SCROLL_VIEW);
+ CONTAINERS.add("ImageSwitcher"); //$NON-NLS-1$
+ CONTAINERS.add(LINEAR_LAYOUT);
+ CONTAINERS.add("RadioGroup"); //$NON-NLS-1$
+ CONTAINERS.add("RelativeLayout"); //$NON-NLS-1$
+ CONTAINERS.add(SCROLL_VIEW);
+ CONTAINERS.add("SlidingDrawer"); //$NON-NLS-1$
+ CONTAINERS.add("StackView"); //$NON-NLS-1$
+ CONTAINERS.add("TabHost"); //$NON-NLS-1$
+ CONTAINERS.add("TableLayout"); //$NON-NLS-1$
+ CONTAINERS.add("TableRow"); //$NON-NLS-1$
+ CONTAINERS.add("TextSwitcher"); //$NON-NLS-1$
+ CONTAINERS.add("ViewAnimator"); //$NON-NLS-1$
+ CONTAINERS.add("ViewFlipper"); //$NON-NLS-1$
+ CONTAINERS.add("ViewSwitcher"); //$NON-NLS-1$
+ // Available ViewGroups that are not included by this check:
+ // CONTAINERS.add("AdapterViewFlipper");
+ // CONTAINERS.add("DialerFilter");
+ // CONTAINERS.add("ExpandableListView");
+ // CONTAINERS.add("ListView");
+ // CONTAINERS.add("MediaController");
+ // CONTAINERS.add("merge");
+ // CONTAINERS.add("SearchView");
+ // CONTAINERS.add("TabWidget");
+ }
+ @Override
+ public Collection<String> getApplicableElements() {
+ return CONTAINERS;
+ }
+
+ @Override
+ public void visitElement(Context context, Element element) {
+ int childCount = getChildCount(element);
+ if (childCount == 0) {
+ // Check to see if this is a leaf layout that can be removed
+ checkUselessLeaf(context, element);
+ } else {
+ // Check to see if this is a middle-man layout which can be removed
+ checkUselessMiddleLayout(context, element);
+ }
+ }
+
+ // This is the old UselessLayoutCheck from layoutopt
+ private void checkUselessMiddleLayout(Context context, Element element) {
+ // Conditions:
+ // - The node has children
+ // - The node does not have siblings
+ // - The node's parent is not a scroll view (horizontal or vertical)
+ // - The node does not have a background or its parent does not have a
+ // background or neither the node and its parent have a background
+ // - The parent is not a <merge/>
+
+ Node parentNode = element.getParentNode();
+ if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
+ // Can't remove root
+ return;
+ }
+
+ Element parent = (Element) parentNode;
+ String parentTag = parent.getTagName();
+ if (parentTag.equals(SCROLL_VIEW) || parentTag.equals(HORIZONTAL_SCROLL_VIEW) ||
+ parentTag.equals(MERGE)) {
+ // Can't remove if the parent is a scroll view or a merge
+ return;
+ }
+
+ // This method is only called when we've already ensured that it has children
+ assert getChildCount(element) > 0;
+
+ int parentChildCount = getChildCount(parent);
+ if (parentChildCount != 1) {
+ // Don't remove if the node has siblings
+ return;
+ }
+
+ boolean nodeHasBackground = element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
+ boolean parentHasBackground = parent.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
+ // TODO: The logic on this has background stuff is a bit unclear to me; this is
+ // a literal translation of the Groovy code in layoutopt
+ // TODO: Get clarification on what the criteria are.
+ if (nodeHasBackground || parentHasBackground ||
+ (!nodeHasBackground && !parentHasBackground)) {
+ boolean hasId = element.hasAttributeNS(ANDROID_URI, ATTR_ID);
+ Location location = context.getLocation(element);
+ String tag = element.getTagName();
+ String format;
+ if (hasId) {
+ format = "This %1$s layout or its %2$s parent is possibly useless";
+ } else {
+ format = "This %1$s layout or its %2$s parent is useless";
+ }
+ String message = String.format(format, tag, parentTag);
+ context.toolContext.report(USELESS_PARENT, location, message);
+ }
+ }
+
+ // This is the old UselessView check from layoutopt
+ private void checkUselessLeaf(Context context, Element element) {
+ assert getChildCount(element) == 0;
+
+ // Conditions:
+ // - The node is a container view (LinearLayout, etc.)
+ // - The node has no id
+ // - The node has no background
+ // - The node has no children
+
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_ID)) {
+ return;
+ }
+
+ if (element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)) {
+ return;
+ }
+
+ Location location = context.getLocation(element);
+ String tag = element.getTagName();
+ String message = String.format(
+ "This %1$s view is useless (no children, no background, no id)", tag);
+ context.toolContext.report(USELESS_LEAF, location, message);
+ }
+}
diff --git a/lint/libs/lint_checks/tests/.classpath b/lint/libs/lint_checks/tests/.classpath
new file mode 100644
index 0000000..73067c0
--- /dev/null
+++ b/lint/libs/lint_checks/tests/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+ <classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/easymock.jar"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/lint-checks"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/lint-cli"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/lint/libs/lint_checks/tests/.project b/lint/libs/lint_checks/tests/.project
new file mode 100644
index 0000000..5713c07
--- /dev/null
+++ b/lint/libs/lint_checks/tests/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>lint_check-tests</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/lint/libs/lint_checks/tests/Android.mk b/lint/libs/lint_checks/tests/Android.mk
new file mode 100644
index 0000000..fda6d7f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2010 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := lint_checks-tests
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := lint_api lint_checks lint junit easymock
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
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
new file mode 100644
index 0000000..3cfc8e8
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.PositionXmlParser;
+import com.android.tools.lint.api.DetectorRegistry;
+import com.android.tools.lint.api.IDomParser;
+import com.android.tools.lint.api.Lint;
+import com.android.tools.lint.api.ToolContext;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Position;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/** Common utility methods for the various lint check tests */
+abstract class AbstractCheckTest extends TestCase implements ToolContext {
+ protected abstract Detector getDetector();
+
+ private class CustomDetectorRegistry extends DetectorRegistry {
+ @Override
+ public List<? extends Detector> getDetectors() {
+ List<Detector> detectors = new ArrayList<Detector>(1);
+ detectors.add(AbstractCheckTest.this.getDetector());
+ return detectors;
+ }
+ }
+
+ protected String lint(String... relativePaths) throws Exception {
+ List<File> files = new ArrayList<File>();
+ for (String relativePath : relativePaths) {
+ File file = getTestfile(relativePath);
+ assertNotNull(file);
+ files.add(file);
+ }
+
+ mOutput = new StringBuilder();
+ Lint analyzer = new Lint(new CustomDetectorRegistry(), this, Scope.PROJECT);
+ analyzer.analyze(files);
+ if (mOutput.length() == 0) {
+ mOutput.append("No warnings.");
+ }
+
+ return mOutput.toString();
+ }
+
+ public void report(Issue issue, Location location, String message) {
+ if (mOutput.length() > 0) {
+ mOutput.append('\n');
+ }
+
+ mOutput.append(location.getFile().getName());
+ mOutput.append(':');
+
+ Position startPosition = location.getStart();
+ if (startPosition != null) {
+ int line = startPosition.getLine();
+ if (line >= 0) {
+ // line is 0-based, should display 1-based
+ mOutput.append(Integer.toString(line + 1));
+ mOutput.append(':');
+ }
+ }
+
+ mOutput.append(' ');
+ Severity severity = getSeverity(issue);
+ mOutput.append(severity.getDescription());
+ mOutput.append(": ");
+
+ mOutput.append(message);
+ }
+
+ private StringBuilder mOutput = null;
+
+ private static File sTempDir = null;
+ private File getTempDir() {
+ if (sTempDir == null) {
+ File base = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
+ String os = System.getProperty("os.name"); //$NON-NLS-1$
+ if (os.startsWith("Mac OS")) { //$NON-NLS-1$
+ base = new File("/tmp");
+ }
+ Calendar c = Calendar.getInstance();
+ String name = String.format("lintTests_%1$tF_%1$tT", c).replace(':', '-'); //$NON-NLS-1$
+ File tmpDir = new File(base, name);
+ if (!tmpDir.exists() && tmpDir.mkdir()) {
+ sTempDir = tmpDir;
+ } else {
+ sTempDir = base;
+ }
+ }
+
+ return sTempDir;
+ }
+
+ private File makeTestFile(String name, String relative,
+ String contents) throws IOException {
+ File dir = getTempDir();
+ if (relative != null) {
+ dir = new File(dir, relative);
+ if (!dir.exists()) {
+ boolean mkdir = dir.mkdirs();
+ assertTrue(dir.getPath(), mkdir);
+ }
+ }
+ File tempFile = new File(dir, name);
+ if (tempFile.exists()) {
+ tempFile.delete();
+ }
+
+ Writer writer = new BufferedWriter(new FileWriter(tempFile));
+ writer.write(contents);
+ writer.close();
+
+ return tempFile;
+ }
+
+ private File getTestfile(String relativePath) throws IOException {
+ String path = "data" + File.separator + relativePath; //$NON-NLS-1$
+ InputStream stream =
+ AbstractCheckTest.class.getResourceAsStream(path);
+ assertNotNull(relativePath + " does not exist", stream);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+ String xml = readFile(reader);
+ assertNotNull(xml);
+ assertTrue(xml.length() > 0);
+ int index = relativePath.lastIndexOf('/');
+ String relative = null;
+ String name = relativePath;
+ if (index != -1) {
+ name = relativePath.substring(index + 1);
+ relative = relativePath.substring(0, index);
+ }
+ return makeTestFile(name, relative, xml);
+ }
+
+ private String readFile(Reader reader) throws IOException {
+ try {
+ StringBuilder sb = new StringBuilder();
+ while (true) {
+ int c = reader.read();
+ if (c == -1) {
+ return sb.toString();
+ } else {
+ sb.append((char)c);
+ }
+ }
+ } finally {
+ reader.close();
+ }
+ }
+
+ public void log(Throwable exception, String format, Object... args) {
+ exception.printStackTrace();
+ fail(exception.toString());
+ }
+
+ public IDomParser getParser() {
+ return new PositionXmlParser();
+ }
+
+ public boolean isEnabled(Issue issue) {
+ for (Issue detectorIssue : getDetector().getIssues()) {
+ if (issue == detectorIssue) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isSuppressed(Issue issue, Location range, String message, Severity severity) {
+ return false;
+ }
+
+ public Severity getSeverity(Issue issue) {
+ return issue.getDefaultSeverity();
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AccessibilityDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AccessibilityDetectorTest.java
new file mode 100644
index 0000000..57754d6
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AccessibilityDetectorTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class AccessibilityDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new AccessibilityDetector();
+ }
+
+ public void testAccessibility() throws Exception {
+ assertEquals(
+ "accessibility.xml:5: Warning: [Accessibility] Missing contentDescription " +
+ "attribute on image\n" +
+ "accessibility.xml:6: Warning: [Accessibility] Missing contentDescription " +
+ "attribute on image",
+ lint("layout/accessibility.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ChildCountDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ChildCountDetectorTest.java
new file mode 100644
index 0000000..a31036c
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ChildCountDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class ChildCountDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ChildCountDetector();
+ }
+
+ public void testChildCount() throws Exception {
+ assertEquals(
+ "has_children.xml:8: Warning: A list/grid should have no children declared " +
+ "in XML",
+ lint("layout/has_children.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
new file mode 100644
index 0000000..b085fff
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class DuplicateIdDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new DuplicateIdDetector();
+ }
+
+ public void testDuplicate() throws Exception {
+ assertEquals(
+ "duplicate.xml:6: Warning: Duplicate id @+id/android_logo, already defined " +
+ "earlier in this layout",
+ lint("layout/duplicate.xml"));
+ }
+
+ public void testDuplicateChains() throws Exception {
+ assertEquals(
+ "layout2.xml: Warning: Duplicate id @+id/button1, already defined in layout " +
+ "layout4.xml which is included in this layout\n" +
+ "layout1.xml: Warning: Duplicate id @+id/button1, already defined in layout " +
+ "layout2 which is included in this layout (layout1 => layout3 => layout2)\n" +
+ "layout1.xml: Warning: Duplicate id @+id/button2, already defined in layout " +
+ "layout2 which is included in this layout (layout1 => layout4 => layout2)",
+ lint("layout/layout1.xml", "layout/layout2.xml",
+ "layout/layout3.xml", "layout/layout4.xml"));
+ }
+
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
new file mode 100644
index 0000000..1ab7696
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class InefficientWeightDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new InefficientWeightDetector();
+ }
+
+ public void testWeights() throws Exception {
+ assertEquals(
+ "inefficient_weight.xml:13: Warning: Use a layout_width of 0dip instead of " +
+ "match_parent for better performance\n" +
+ "inefficient_weight.xml:26: Warning: Use a layout_height of 0dip instead of " +
+ "wrap_content for better performance",
+ lint("layout/inefficient_weight.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
new file mode 100644
index 0000000..423a5bc
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetectorTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class MergeRootFrameLayoutDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new MergeRootFrameLayoutDetector();
+ }
+
+ public void testMerge() throws Exception {
+ assertEquals(
+ "simple.xml:8: Warning: This <FrameLayout> can be replaced with a <merge> tag",
+ lint("layout/simple.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java
new file mode 100644
index 0000000..9338731
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NestedScrollingWidgetDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class NestedScrollingWidgetDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new NestedScrollingWidgetDetector();
+ }
+
+ public void testNested() throws Exception {
+ assertEquals(
+ "scrolling.xml:16: Warning: The vertically scrolling ScrollView should not " +
+ "contain another vertically scrolling widget (ListView)",
+ lint("layout/scrolling.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java
new file mode 100644
index 0000000..2e763ea
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ScrollViewChildDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class ScrollViewChildDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new ScrollViewChildDetector();
+ }
+
+ public void testScrollView() throws Exception {
+ assertEquals(
+ "wrong_dimension.xml:12: Warning: This LinearLayout should use " +
+ "android:layout_width=\"wrap_content\"",
+ lint("layout/wrong_dimension.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/StateListDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/StateListDetectorTest.java
new file mode 100644
index 0000000..e7dfc74
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/StateListDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class StateListDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new StateListDetector();
+ }
+
+ public void testStates() throws Exception {
+ assertEquals(
+ "states.xml:3: Warning: No android:state_ attribute found on <item> 0, " +
+ "later states not reachable",
+ lint("drawable/states.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TooManyViewsDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TooManyViewsDetectorTest.java
new file mode 100644
index 0000000..8d1af2c
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TooManyViewsDetectorTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class TooManyViewsDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new TooManyViewsDetector();
+ }
+
+ public void testTooMany() throws Exception {
+ assertEquals(
+ "too_many.xml:403: Warning: too_many.xml has more than 80 views, bad for " +
+ "performance",
+ lint("layout/too_many.xml"));
+ }
+
+ public void testTooDeep() throws Exception {
+ assertEquals(
+ "too_deep.xml:49: Warning: too_deep.xml has more than 10 levels, bad for " +
+ "performance",
+ lint("layout/too_deep.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java
new file mode 100644
index 0000000..ce407e8
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UseCompoundDrawableDetectorTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class UseCompoundDrawableDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new UseCompoundDrawableDetector();
+ }
+
+ public void testCompound() throws Exception {
+ assertEquals(
+ "compound.xml:8: Warning: This tag and its children can be replaced by one " +
+ "<TextView/> and a compound drawable",
+ lint("layout/compound.xml"));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UselessViewDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UselessViewDetectorTest.java
new file mode 100644
index 0000000..d2ba0eb
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UselessViewDetectorTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class UselessViewDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new UselessViewDetector();
+ }
+
+ public void testUseless1() throws Exception {
+ assertEquals(
+ "useless.xml:12: Warning: This LinearLayout layout or its FrameLayout parent " +
+ "is useless",
+ lint("layout/useless.xml"));
+ }
+
+ // TODO: Test the other case as well
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/drawable/states.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/drawable/states.xml
new file mode 100644
index 0000000..3dedb64
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/drawable/states.xml
@@ -0,0 +1,7 @@
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="#ff000000"/> <!-- WRONG, SHOULD BE LAST -->
+ <item android:state_pressed="true"
+ android:color="#ffff0000"/> <!-- pressed -->
+ <item android:state_focused="true"
+ android:color="#ff0000ff"/> <!-- focused -->
+</selector>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/accessibility.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/accessibility.xml
new file mode 100644
index 0000000..9a49772
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/accessibility.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+ <ImageButton android:id="@+id/android_logo2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+ <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+</LinearLayout>
+
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/compound.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/compound.xml
new file mode 100644
index 0000000..f7b28ef
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/compound.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/duplicate.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/duplicate.xml
new file mode 100644
index 0000000..e142e9e
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/duplicate.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+ <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+ <ImageButton android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" />
+ <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
+</LinearLayout>
+
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/has_children.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/has_children.xml
new file mode 100644
index 0000000..cac27d4
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/has_children.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</ListView> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/inefficient_weight.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/inefficient_weight.xml
new file mode 100644
index 0000000..058fde1
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/inefficient_weight.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0" />
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+
+ android:orientation="vertical">
+
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+
+ android:orientation="vertical">
+
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0" />
+
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout1.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout1.xml
new file mode 100644
index 0000000..efd6be0
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout1.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <include
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ layout="@layout/layout2" />
+
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button" />
+
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout2.xml
new file mode 100644
index 0000000..9fc9c5f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout2.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <RadioButton
+ android:id="@+id/radioButton1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="RadioButton" />
+
+ <include
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ layout="@layout/layout3" />
+
+ <include
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ layout="@layout/layout4" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout3.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout3.xml
new file mode 100644
index 0000000..aa5a137
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout3.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button" />
+
+ <CheckBox
+ android:id="@+id/checkBox1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="CheckBox" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout4.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout4.xml
new file mode 100644
index 0000000..442efd4
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/layout4.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button" />
+
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button" />
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/scrolling.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/scrolling.xml
new file mode 100644
index 0000000..0bed702
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/scrolling.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/simple.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/simple.xml
new file mode 100644
index 0000000..d462c69
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/simple.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_deep.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_deep.xml
new file mode 100644
index 0000000..7e92008
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_deep.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_many.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_many.xml
new file mode 100644
index 0000000..e2dbd6b
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/too_many.xml
@@ -0,0 +1,413 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Ok" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</FrameLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/useless.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/useless.xml
new file mode 100644
index 0000000..b6d5ee7
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/useless.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</FrameLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/wrong_dimension.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/wrong_dimension.xml
new file mode 100644
index 0000000..79b922b
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/layout/wrong_dimension.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<HorizontalScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</HorizontalScrollView>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/ScopeTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/ScopeTest.java
new file mode 100644
index 0000000..83c032f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/ScopeTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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 junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class ScopeTest extends TestCase {
+ public void testWithin() throws Exception {
+ assertTrue(Scope.SINGLE_FILE.within(Scope.SINGLE_FILE));
+ assertTrue(Scope.SINGLE_FILE.within(Scope.JAVA_CODE));
+ assertTrue(Scope.SINGLE_FILE.within(Scope.JAVA));
+ assertTrue(Scope.SINGLE_FILE.within(Scope.RESOURCES));
+ assertTrue(Scope.SINGLE_FILE.within(Scope.PROJECT));
+ assertTrue(Scope.RESOURCES.within(Scope.RESOURCES));
+ assertTrue(Scope.RESOURCES.within(Scope.PROJECT));
+ assertTrue(Scope.JAVA_CODE.within(Scope.JAVA));
+ assertTrue(Scope.JAVA_CODE.within(Scope.PROJECT));
+ assertTrue(Scope.JAVA.within(Scope.JAVA));
+ assertTrue(Scope.JAVA.within(Scope.PROJECT));
+ assertTrue(Scope.PROJECT.within(Scope.PROJECT));
+
+ assertFalse(Scope.PROJECT.within(Scope.SINGLE_FILE));
+ assertFalse(Scope.RESOURCES.within(Scope.SINGLE_FILE));
+ assertFalse(Scope.JAVA.within(Scope.SINGLE_FILE));
+ assertFalse(Scope.JAVA_CODE.within(Scope.SINGLE_FILE));
+ assertFalse(Scope.PROJECT.within(Scope.RESOURCES));
+ assertFalse(Scope.JAVA.within(Scope.RESOURCES));
+ assertFalse(Scope.JAVA_CODE.within(Scope.RESOURCES));
+ assertFalse(Scope.PROJECT.within(Scope.JAVA));
+ assertFalse(Scope.PROJECT.within(Scope.JAVA_CODE));
+ assertFalse(Scope.JAVA.within(Scope.JAVA_CODE));
+ assertFalse(Scope.JAVA.within(Scope.JAVA_CODE));
+ }
+}