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