diff options
80 files changed, 3484 insertions, 1573 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.moreunit.prefs b/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.moreunit.prefs index a1c97ef..7400eff 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.moreunit.prefs +++ b/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.moreunit.prefs @@ -1,4 +1,5 @@ -#Mon Feb 28 13:14:31 PST 2011 +#Sat Nov 05 13:00:35 PDT 2011 eclipse.preferences.version=1 +org.moreunit.prefixes= org.moreunit.unitsourcefolder=adt\:src\:adt-tests\:unittests\#adt\:src\:adt-tests\:src org.moreunit.useprojectsettings=true 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 f5982a1..ae9f406 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 @@ -139,6 +139,9 @@ Export-Package: com.android, com.android.sdkuilib.internal.widgets;x-friends:="com.android.ide.eclipse.tests", com.android.sdkuilib.repository;x-friends:="com.android.ide.eclipse.tests", com.android.sdkuilib.ui;x-friends:="com.android.ide.eclipse.tests", + com.android.tools.lint.checks;x-friends:="com.android.ide.eclipse.tests", + com.android.tools.lint.client.api;x-friends:="com.android.ide.eclipse.tests", + com.android.tools.lint.detector.api;x-friends:="com.android.ide.eclipse.tests", com.android.util;x-friends:="com.android.ide.eclipse.tests", org.apache.commons.compress.archivers;x-friends:="com.android.ide.eclipse.tests", org.apache.commons.compress.archivers.ar;x-friends:="com.android.ide.eclipse.tests", diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index b5030e3..ed103f2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -666,6 +666,18 @@ args="com.android.ide.eclipse.adt.AndroidNature" /> </enabledWhen> </page> + <page + class="com.android.ide.eclipse.adt.internal.preferences.LintPreferencePage" + id="com.android.ide.eclipse.adt.internal.preferences.LintPreferencePage" + name="Android Lint Preferences"> + <filter + name="nature" + value="com.android.ide.eclipse.adt.AndroidNature"> + </filter> + <enabledWhen> + <adapt type="org.eclipse.core.resources.IProject" /> + </enabledWhen> + </page> </extension> <extension point="org.eclipse.ui.actionSets"> <actionSet @@ -825,6 +837,22 @@ tooltip="Opens the Android SDK Manager"> </action> </actionSet> + <actionSet + description="Android Lint" + id="adt.actionSet.lint" + label="Android Lint" + visible="true"> + <action + class="com.android.ide.eclipse.adt.internal.lint.RunLintAction" + icon="icons/lintrun.png" + id="com.android.ide.eclipse.adt.ui.lintrunner" + label="Run Android Lint" + menubarPath="Window/additions" + style="push" + toolbarPath="android_project" + tooltip="Runs Android Lint"> + </action> + </actionSet> </extension> <extension point="org.eclipse.debug.core.launchDelegates"> <launchDelegate 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 80c75e5..05b0853 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 @@ -19,6 +19,7 @@ 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.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; @@ -297,8 +298,9 @@ public class AdtUtils { * @return an absolute file system path to the resource */ public static IPath getAbsolutePath(IResource resource) { - IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); - IPath workspacePath = workspace.getLocation(); + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + IWorkspaceRoot root = workspace.getRoot(); + IPath workspacePath = root.getLocation(); return workspacePath.append(resource.getFullPath()); } 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 0753104..ddca596 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 @@ -900,7 +900,9 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * a lock first. */ protected void runEditHooks() { - runLint(); + if (!mIgnoreXmlUpdate) { + runLint(); + } } /** 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 13c7944..13a3fc4 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,7 +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.lint.EclipseLintClient; 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; @@ -342,7 +342,7 @@ public class GraphicalEditorPart extends EditorPart GridData detailsData = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1); mActionBar.setLayoutData(detailsData); if (file != null) { - mActionBar.updateErrorIndicator(LintEclipseContext.hasMarkers(file)); + mActionBar.updateErrorIndicator(EclipseLintClient.hasMarkers(file)); } mSashError = new SashForm(layoutBarAndCanvas, SWT.VERTICAL | SWT.BORDER); 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 5469d07..1c6c92a 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,7 +28,7 @@ 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.lint.EclipseLintClient; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog; @@ -460,7 +460,7 @@ public class LayoutActionBar extends Composite { @Override public void widgetSelected(SelectionEvent e) { IFile file = mEditor.getLayoutEditor().getInputFile(); - LintEclipseContext.showErrors(getShell(), file); + EclipseLintClient.showErrors(getShell(), file); } }); @@ -471,7 +471,7 @@ public class LayoutActionBar extends Composite { * Updates the lint indicator state in the given layout editor */ public void updateErrorIndicator() { - updateErrorIndicator(LintEclipseContext.hasMarkers(mEditor.getEditedFile())); + updateErrorIndicator(EclipseLintClient.hasMarkers(mEditor.getEditedFile())); } /** 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 index 21d90e6..26e5606 100644 --- 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 @@ -33,8 +33,8 @@ public class ClearLintMarkersAction implements IActionDelegate { public void run(IAction action) { IProject project = RunLintAction.getSelectedProject(mSelection); if (project != null) { - LintRunner.cancelCurrentJobs(); - LintEclipseContext.clearMarkers(project); + LintRunner.cancelCurrentJobs(false); + EclipseLintClient.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/EclipseLintClient.java index f4939c6..64d9780 100644 --- 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/EclipseLintClient.java @@ -21,17 +21,18 @@ 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.checks.BuiltinIssueRegistry; +import com.android.tools.lint.client.api.Configuration; +import com.android.tools.lint.client.api.IDomParser; +import com.android.tools.lint.client.api.IssueRegistry; +import com.android.tools.lint.client.api.LintClient; 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.Position; +import com.android.tools.lint.detector.api.Project; import com.android.tools.lint.detector.api.Severity; import com.android.util.Pair; @@ -42,7 +43,6 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; @@ -64,45 +64,39 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; 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 extends ToolContext implements IDomParser { +public class EclipseLintClient extends LintClient implements 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 IssueRegistry 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; - + private boolean mWasFatal; + private boolean mFatalOnly; + private Configuration mConfiguration; /** - * Creates a new {@link LintEclipseContext}. + * Creates a new {@link EclipseLintClient}. * * @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 + * @param fatalOnly whether only fatal issues should be reported (and therefore checked) */ - public LintEclipseContext(DetectorRegistry registry, IResource resource, IDocument document) { + public EclipseLintClient(IssueRegistry registry, IResource resource, IDocument document, + boolean fatalOnly) { mRegistry = registry; mResource = resource; mDocument = document; + mFatalOnly = fatalOnly; } - // ----- Extends ToolContext ----- + // ----- Extends LintClient ----- @Override public void log(Throwable exception, String format, Object... args) { @@ -118,33 +112,6 @@ public class LintEclipseContext extends ToolContext implements IDomParser { return this; } - @Override - 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(); - } - } - - if (mDisabledIds.contains(issue.getId())) { - return false; - } - - return issue.isEnabledByDefault(); - } - // ----- Implements IDomParser ----- public Document parse(Context context) { @@ -215,82 +182,24 @@ public class LintEclipseContext extends ToolContext implements IDomParser { } @Override - 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()); + public Configuration getConfiguration(Project project) { + if (mConfiguration == null) { + if (mResource != null) { + IProject eclipseProject = mResource.getProject(); + mConfiguration = ProjectLintConfiguration.get(this, eclipseProject, mFatalOnly); + } else { + mConfiguration = GlobalLintConfiguration.get(); } } - - if (sb.length() > 0) { - store.setValue(AdtPrefs.PREFS_LINT_SEVERITIES, sb.toString()); - } else { - store.setToDefault(AdtPrefs.PREFS_LINT_SEVERITIES); - } + return mConfiguration; } @Override public void report(Context context, Issue issue, Location location, String message, Object data) { - if (!isEnabled(issue)) { - return; - } - - Severity s = getSeverity(issue); - if (s == Severity.IGNORE) { - return; - } + assert context.configuration.isEnabled(issue); + assert context.configuration.getSeverity(issue) != Severity.IGNORE; + Severity s = context.configuration.getSeverity(issue); int severity = getMarkerSeverity(s); IMarker marker = null; @@ -335,7 +244,7 @@ public class LintEclipseContext extends ToolContext implements IDomParser { } if (s == Severity.ERROR) { - mFatal = true; + mWasFatal = true; } } @@ -501,8 +410,8 @@ public class LintEclipseContext extends ToolContext implements IDomParser { * * @return true if a fatal error was encountered */ - public boolean isFatal() { - return mFatal; + public boolean hasFatalErrors() { + return mWasFatal; } /** @@ -512,7 +421,7 @@ public class LintEclipseContext extends ToolContext implements IDomParser { * @return a full description of the corresponding issue, never null */ public static String describe(IMarker marker) { - DetectorRegistry registry = getRegistry(); + IssueRegistry registry = getRegistry(); Issue issue = registry.getIssue(getId(marker)); String summary = issue.getDescription(); String explanation = issue.getExplanation(); @@ -649,40 +558,23 @@ public class LintEclipseContext extends ToolContext implements IDomParser { } /** - * 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. + * Returns the registry of issues to check from within Eclipse. * - * @return the detector registry to use to access detectors and issues + * @return the issue registry to use to access detectors and issues */ - public static DetectorRegistry getRegistry() { - return new EclipseDetectorRegistry(); + public static IssueRegistry getRegistry() { + return new BuiltinIssueRegistry(); } - /** - * Custom Eclipse registry which replaces some builtin checks with - * Eclipse-optimized versions - */ - private static class EclipseDetectorRegistry extends BuiltinDetectorRegistry { - private static final List<Detector> sDetectors; - static { - List<Detector> detectors = new ArrayList<Detector>(20); - for (Detector detector : new BuiltinDetectorRegistry().getDetectors()) { - // Replace the generic UnusedResourceDetector with an Eclipse optimized one - // which uses the Java AST - if (detector instanceof com.android.tools.lint.checks.UnusedResourceDetector) { - detectors.add(new UnusedResourceDetector()); - } else { - detectors.add(detector); - } - } - sDetectors = Collections.unmodifiableList(detectors); - } - @Override - public List<? extends Detector> getDetectors() { - return sDetectors; + @Override + public Class<? extends Detector> replaceDetector(Class<? extends Detector> detectorClass) { + // Replace the generic UnusedResourceDetector with an Eclipse optimized one + // which uses the Java AST + if (detectorClass == com.android.tools.lint.checks.UnusedResourceDetector.class) { + return UnusedResourceDetector.class; } + + return detectorClass; } private static class OffsetPosition extends Position { @@ -768,15 +660,8 @@ public class LintEclipseContext extends ToolContext implements IDomParser { } } - /** Specialized context which only provides fatal issues as enabled */ - static class FatalContext extends LintEclipseContext { - public FatalContext(DetectorRegistry registry, IResource resource, IDocument document) { - super(registry, resource, document); - } - - @Override - public boolean isEnabled(Issue issue) { - return super.isEnabled(issue) && getSeverity(issue) == Severity.ERROR; - } + public void dispose(Context context) { + // TODO: Consider leaving read-lock on the document in parse() and freeing it here. } } + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java new file mode 100644 index 0000000..8da3465 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java @@ -0,0 +1,173 @@ +/* + * 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.internal.preferences.AdtPrefs; +import com.android.tools.lint.client.api.Configuration; +import com.android.tools.lint.client.api.IssueRegistry; +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.Severity; + +import org.eclipse.jface.preference.IPreferenceStore; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Global (non-project-specific) configuration for Lint in Eclipse */ +class GlobalLintConfiguration extends Configuration { + private static final GlobalLintConfiguration sInstance = new GlobalLintConfiguration(); + + private Map<Issue, Severity> mSeverities; + private boolean mBulkEditing; + + private GlobalLintConfiguration() { + } + + /** + * Obtain a reference to the singleton + * + * @return the singleton configuration + */ + public static GlobalLintConfiguration get() { + return sInstance; + } + + @Override + public Severity getSeverity(Issue issue) { + if (mSeverities == null) { + IssueRegistry registry = EclipseLintClient.getRegistry(); + mSeverities = new HashMap<Issue, Severity>(); + IPreferenceStore store = getStore(); + 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 = registry.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; + } + + if (!issue.isEnabledByDefault()) { + return Severity.IGNORE; + } + + return issue.getDefaultSeverity(); + } + + private IPreferenceStore getStore() { + IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); + return store; + } + + @Override + public void ignore(Context context, Issue issue, Location location, String message, + Object data) { + throw new UnsupportedOperationException( + "Can't ignore() in global configurations"); //$NON-NLS-1$ + } + + @Override + public void setSeverity(Issue issue, Severity severity) { + if (mSeverities == null) { + // Force initialization + getSeverity(issue); + } + + if (severity == null) { + mSeverities.remove(issue); + } else { + mSeverities.put(issue, severity); + } + + if (!mBulkEditing) { + setSeverities(mSeverities); + } + } + + /** + * Sets the custom severities for the given issues, in bulk. + * + * @param severities a map from detector to severity to use from now on + * @return true if something changed from the current settings + */ + private boolean setSeverities(Map<Issue, Severity> severities) { + mSeverities = severities; + + String value = ""; + if (severities.size() > 0) { + 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()); + } + } + + value = sb.toString(); + } + + IPreferenceStore store = getStore(); + String previous = store.getString(AdtPrefs.PREFS_LINT_SEVERITIES); + boolean changed = !value.equals(previous); + if (changed) { + if (value.length() == 0) { + store.setToDefault(AdtPrefs.PREFS_LINT_SEVERITIES); + } else { + store.setValue(AdtPrefs.PREFS_LINT_SEVERITIES, value); + } + } + + return changed; + } + + @Override + public void startBulkEditing() { + mBulkEditing = true; + } + + @Override + public void finishBulkEditing() { + mBulkEditing = false; + setSeverities(mSeverities); + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java index c1a7169..dcbb6d3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java @@ -24,7 +24,6 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; import static com.android.ide.common.layout.LayoutConstants.VALUE_N_DP; import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL; import static com.android.ide.common.layout.LayoutConstants.VALUE_ZERO_DP; -import static com.android.tools.lint.checks.LintConstants.ATTR_PERMISSION; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtUtils; @@ -36,7 +35,6 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; import com.android.tools.lint.checks.AccessibilityDetector; -import com.android.tools.lint.checks.BuiltinDetectorRegistry; import com.android.tools.lint.checks.ExportedServiceDetector; import com.android.tools.lint.checks.HardcodedValuesDetector; import com.android.tools.lint.checks.InefficientWeightDetector; @@ -44,6 +42,7 @@ import com.android.tools.lint.checks.PxUsageDetector; import com.android.tools.lint.checks.TextFieldDetector; import com.android.tools.lint.checks.UselessViewDetector; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LintConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; @@ -131,7 +130,7 @@ abstract class LintFix implements ICompletionProposal { } public String getAdditionalProposalInfo() { - Issue issue = new BuiltinDetectorRegistry().getIssue(mId); + Issue issue = EclipseLintClient.getRegistry().getIssue(mId); if (issue != null) { return issue.getExplanation().replace("\n", "<br>"); //$NON-NLS-1$ //$NON-NLS-2$ } @@ -340,7 +339,7 @@ abstract class LintFix implements ICompletionProposal { @Override protected String getAttribute() { - return ATTR_PERMISSION; + return LintConstants.ATTR_PERMISSION; } @Override 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 index 59e299c..9ac0b4d 100644 --- 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 @@ -20,14 +20,18 @@ 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.client.api.Configuration; +import com.android.tools.lint.client.api.DefaultConfiguration; +import com.android.tools.lint.client.api.IssueRegistry; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Severity; 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.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; @@ -43,6 +47,7 @@ import org.eclipse.ui.IMarkerResolutionGenerator2; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -103,10 +108,9 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi 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 SuppressProposal(resource, id, false), + new SuppressProposal(resource.getProject(), id, true /* all */), + new SuppressProposal(resource, id, true /* all */), new ClearMarkersProposal(resource, true /* all */), }; } @@ -148,11 +152,10 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi String message = marker.getAttribute(IMarker.MESSAGE, null); proposals.add(new MoreInfoProposal(id, message)); - proposals.add(new SuppressProposal(id, true /* all */)); - // Not yet implemented - //proposals.add(new SuppressProposal(id, false)); + proposals.add(new SuppressProposal(file, id, false)); + proposals.add(new SuppressProposal(file.getProject(), id, true /* all */)); + proposals.add(new SuppressProposal(file, id, true /* all */)); - proposals.add(new ClearMarkersProposal(file, false /* all */)); proposals.add(new ClearMarkersProposal(file, true /* all */)); } } @@ -169,40 +172,52 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi * * @param id the id of the detector to be suppressed, or null * @param updateMarkers if true, update all markers + * @param resource the resource associated with the markers + * @param thisFileOnly if true, only suppress this issue in this file */ - public static void suppressDetector(String id, boolean updateMarkers, IResource resource) { - // 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); + public static void suppressDetector(String id, boolean updateMarkers, IResource resource, + boolean thisFileOnly) { + IssueRegistry registry = EclipseLintClient.getRegistry(); + Issue issue = registry.getIssue(id); + if (issue != null) { + EclipseLintClient mClient = new EclipseLintClient(registry, resource, null, false); + Configuration configuration = mClient.getConfiguration(null); + if (thisFileOnly && configuration instanceof DefaultConfiguration) { + File file = AdtUtils.getAbsolutePath(resource).toFile(); + ((DefaultConfiguration) configuration).ignore(issue, file); + } else { + configuration.setSeverity(issue, Severity.IGNORE); + } + } if (updateMarkers) { - LintEclipseContext.removeMarkers(resource, id); + EclipseLintClient.removeMarkers(resource, id); } } private static class SuppressProposal implements ICompletionProposal, IMarkerResolution2 { private final String mId; private final boolean mGlobal; + private final IResource mResource; - public SuppressProposal(String check, boolean global) { - super(); + private SuppressProposal(IResource resource, String check, boolean global) { + mResource = resource; mId = check; mGlobal = global; } private void perform() { - suppressDetector(mId, true, null); + suppressDetector(mId, true, mResource, !mGlobal); } public String getDisplayString() { - return mGlobal ? "Disable Check" : "Disable Check in this file only"; + if (mResource instanceof IProject) { + return "Disable Check in This Project"; + } else if (mGlobal) { + return "Disable Check"; + } else { + return "Disable Check in This File Only"; + } } // ---- Implements MarkerResolution2 ---- @@ -231,7 +246,9 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi public String getAdditionalProposalInfo() { StringBuilder sb = new StringBuilder(200); - if (mGlobal) { + if (mResource instanceof IProject) { + sb.append("Suppresses this type of lint warning in the current project only."); + } else 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."); @@ -263,7 +280,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi private void perform() { IResource resource = mGlobal ? mResource.getProject() : mResource; - LintEclipseContext.clearMarkers(resource); + EclipseLintClient.clearMarkers(resource); } public String getDisplayString() { @@ -316,7 +333,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi public Image getImage() { ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); - return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK); + return sharedImages.getImage(ISharedImages.IMG_ELCL_REMOVE); } public IContextInformation getContextInformation() { @@ -334,7 +351,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi } private void perform() { - Issue issue = LintEclipseContext.getRegistry().getIssue(mId); + Issue issue = EclipseLintClient.getRegistry().getIssue(mId); assert issue != null : mId; StringBuilder sb = new StringBuilder(300); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java index 546921d..24f7ab9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java @@ -21,7 +21,7 @@ import com.android.ide.eclipse.adt.internal.editors.IconFactory; 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.client.api.IssueRegistry; import com.android.tools.lint.detector.api.Issue; import org.eclipse.core.resources.IFile; @@ -172,7 +172,7 @@ class LintList extends Composite implements IResourceChangeListener, ControlList List<IMarker> markerList = new ArrayList<IMarker>(); if (mResources != null) { for (IResource resource : mResources) { - IMarker[] markers = LintEclipseContext.getMarkers(resource); + IMarker[] markers = EclipseLintClient.getMarkers(resource); for (IMarker marker : markers) { markerList.add(marker); int severity = marker.getAttribute(IMarker.SEVERITY, 0); @@ -184,14 +184,14 @@ class LintList extends Composite implements IResourceChangeListener, ControlList } } - final DetectorRegistry registry = LintEclipseContext.getRegistry(); + final IssueRegistry registry = EclipseLintClient.getRegistry(); Collections.sort(markerList, new Comparator<IMarker>() { public int compare(IMarker marker1, IMarker marker2) { // Sort by priority, then by category, then by id, // then by file, then by line - String id1 = LintEclipseContext.getId(marker1); - String id2 = LintEclipseContext.getId(marker2); + String id1 = EclipseLintClient.getId(marker1); + String id2 = EclipseLintClient.getId(marker2); if (id1 == null || id2 == null) { return marker1.getResource().getName().compareTo( marker2.getResource().getName()); @@ -299,12 +299,12 @@ class LintList extends Composite implements IResourceChangeListener, ControlList ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); switch (severity) { case IMarker.SEVERITY_ERROR: - if (LintFix.hasFix(LintEclipseContext.getId(marker))) { + if (LintFix.hasFix(EclipseLintClient.getId(marker))) { return IconFactory.getInstance().getIcon("quickfix_error"); //$NON-NLS-1$ } return sharedImages.getImage(ISharedImages.IMG_OBJS_ERROR_TSK); case IMarker.SEVERITY_WARNING: - if (LintFix.hasFix(LintEclipseContext.getId(marker))) { + if (LintFix.hasFix(EclipseLintClient.getId(marker))) { return IconFactory.getInstance().getIcon("quickfix_warning"); //$NON-NLS-1$ } return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK); 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 index df41cde..5daa632 100644 --- 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 @@ -167,7 +167,7 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener { return; } - mDetailsText.setText(LintEclipseContext.describe(marker)); + mDetailsText.setText(EclipseLintClient.describe(marker)); } // ---- Implements SelectionListener ---- @@ -180,12 +180,12 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener { } else if (source == mShowButton) { List<IMarker> selection = mList.getSelectedMarkers(); if (selection.size() > 0) { - LintEclipseContext.showMarker(selection.get(0)); + EclipseLintClient.showMarker(selection.get(0)); } } else if (source == mFixButton) { List<IMarker> selection = mList.getSelectedMarkers(); for (IMarker marker : selection) { - LintFix fix = LintFix.getFix(LintEclipseContext.getId(marker), marker); + LintFix fix = LintFix.getFix(EclipseLintClient.getId(marker), marker); IEditorPart editor = AdtUtils.getActiveEditor(); if (editor instanceof AndroidXmlEditor) { IStructuredDocument doc = ((AndroidXmlEditor) editor).getStructuredDocument(); @@ -199,9 +199,9 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener { } } else if (source == mIgnoreTypeButton) { for (IMarker marker : mList.getSelectedMarkers()) { - String id = LintEclipseContext.getId(marker); + String id = EclipseLintClient.getId(marker); if (id != null) { - LintFixGenerator.suppressDetector(id, true, mFile); + LintFixGenerator.suppressDetector(id, true, mFile, true /*all*/); } } } @@ -218,14 +218,14 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener { boolean canFix = selection.size() > 0; for (IMarker marker : selection) { - if (!LintFix.hasFix(LintEclipseContext.getId(marker))) { + if (!LintFix.hasFix(EclipseLintClient.getId(marker))) { canFix = false; break; } // Some fixes cannot be run in bulk if (selection.size() > 1) { - LintFix fix = LintFix.getFix(LintEclipseContext.getId(marker), marker); + LintFix fix = LintFix.getFix(EclipseLintClient.getId(marker), marker); if (!fix.isBulkCapable()) { canFix = false; break; @@ -242,7 +242,7 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener { // Jump to editor List<IMarker> selection = mList.getSelectedMarkers(); if (selection.size() > 0) { - LintEclipseContext.showMarker(selection.get(0)); + EclipseLintClient.showMarker(selection.get(0)); close(); } } 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 index 495625f..f50f660 100644 --- 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 @@ -21,8 +21,8 @@ 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.sdklib.SdkConstants; -import com.android.tools.lint.api.DetectorRegistry; -import com.android.tools.lint.api.Lint; +import com.android.tools.lint.client.api.IssueRegistry; +import com.android.tools.lint.client.api.Lint; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.Scope; @@ -81,7 +81,7 @@ public class LintRunner { */ public static Job startLint(IResource resource, IDocument doc, boolean fatalOnly) { if (resource != null) { - cancelCurrentJobs(); + cancelCurrentJobs(false); CheckFileJob job = new CheckFileJob(resource, doc, fatalOnly); job.schedule(); @@ -124,12 +124,23 @@ public class LintRunner { return jobManager.find(CheckFileJob.FAMILY_RUN_LINT); } - /** Cancels the current lint jobs, if any */ - static void cancelCurrentJobs() { + /** Cancels the current lint jobs, if any, and optionally waits for them to finish */ + static void cancelCurrentJobs(boolean wait) { // Cancel any current running jobs first - for (Job job : getCurrentJobs()) { + Job[] currentJobs = getCurrentJobs(); + for (Job job : currentJobs) { job.cancel(); } + + if (wait) { + for (Job job : currentJobs) { + try { + job.join(); + } catch (InterruptedException e) { + AdtPlugin.log(e, null); + } + } + } } private static final class CheckFileJob extends Job { @@ -165,7 +176,7 @@ public class LintRunner { protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN); - DetectorRegistry registry = LintEclipseContext.getRegistry(); + IssueRegistry registry = EclipseLintClient.getRegistry(); File file = AdtUtils.getAbsolutePath(mResource).toFile(); EnumSet<Scope> scope; if (mResource instanceof IProject) { @@ -182,7 +193,7 @@ public class LintRunner { "Only XML files are supported for single file lint", null); //$NON-NLS-1$ } if (scope == Scope.RESOURCE_FILE_SCOPE || scope == EnumSet.of(Scope.MANIFEST)) { - IMarker[] markers = LintEclipseContext.getMarkers(mResource); + IMarker[] markers = EclipseLintClient.getMarkers(mResource); for (IMarker marker : markers) { String id = marker.getAttribute(MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$ Issue issue = registry.getIssue(id); @@ -191,19 +202,14 @@ public class LintRunner { } } } else { - LintEclipseContext.clearMarkers(mResource); + EclipseLintClient.clearMarkers(mResource); } - LintEclipseContext toolContext; - if (mFatalOnly) { - toolContext = new LintEclipseContext.FatalContext(registry, mResource, - mDocument); - } else { - toolContext = new LintEclipseContext(registry, mResource, mDocument); - } - mLint = new Lint(registry, toolContext, scope); - mLint.analyze(Collections.singletonList(file)); - mFatal = toolContext.isFatal(); + EclipseLintClient client = new EclipseLintClient(registry, mResource, + mDocument, mFatalOnly); + mLint = new Lint(registry, client); + mLint.analyze(Collections.singletonList(file), scope); + mFatal = client.hasFatalErrors(); return Status.OK_STATUS; } catch (Exception e) { return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java index c74d546..f9a78a5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java @@ -126,15 +126,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha // If there are currently running jobs, listen for them such that we can update the // button state - Job[] currentJobs = LintRunner.getCurrentJobs(); - if (currentJobs.length > 0) { - ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); - mRefreshAction.setImageDescriptor(sharedImages.getImageDescriptor( - ISharedImages.IMG_ELCL_STOP)); - for (Job job : currentJobs) { - job.addJobChangeListener(this); - } - } + refreshStopIcon(); if (sInitialProject != null) { mLintView.setResources(Collections.<IResource>singletonList(sInitialProject)); @@ -184,11 +176,11 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha */ private void initializeToolBar() { IToolBarManager toolbarManager = getViewSite().getActionBars().getToolBarManager(); + toolbarManager.add(mRefreshAction); toolbarManager.add(mFixAction); toolbarManager.add(mIgnoreAction); toolbarManager.add(mRemoveAction); toolbarManager.add(mRemoveAllAction); - toolbarManager.add(mRefreshAction); } @Override @@ -203,6 +195,25 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha */ public void setResources(List<IResource> resources) { mLintView.setResources(resources); + + // Refresh the stop/refresh icon status + refreshStopIcon(); + } + + private void refreshStopIcon() { + Job[] currentJobs = LintRunner.getCurrentJobs(); + if (currentJobs.length > 0) { + ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); + mRefreshAction.setImageDescriptor(sharedImages.getImageDescriptor( + ISharedImages.IMG_ELCL_STOP)); + for (Job job : currentJobs) { + job.addJobChangeListener(this); + } + } else { + mRefreshAction.setImageDescriptor( + IconFactory.getInstance().getImageDescriptor(REFRESH_ICON)); + + } } // ---- Implements SelectionListener ---- @@ -212,7 +223,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha if (markers.size() != 1) { mDetailsText.setText(""); //$NON-NLS-1$ } else { - mDetailsText.setText(LintEclipseContext.describe(markers.get(0))); + mDetailsText.setText(EclipseLintClient.describe(markers.get(0))); } updateIssueCount(); @@ -224,14 +235,14 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha boolean hasSelection = markers.size() > 0; boolean canFix = hasSelection; for (IMarker marker : markers) { - if (!LintFix.hasFix(LintEclipseContext.getId(marker))) { + if (!LintFix.hasFix(EclipseLintClient.getId(marker))) { canFix = false; break; } // Some fixes cannot be run in bulk if (markers.size() > 1) { - LintFix fix = LintFix.getFix(LintEclipseContext.getId(marker), marker); + LintFix fix = LintFix.getFix(EclipseLintClient.getId(marker), marker); if (!fix.isBulkCapable()) { canFix = false; break; @@ -254,7 +265,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha // Jump to editor List<IMarker> selection = mLintView.getSelectedMarkers(); if (selection.size() > 0) { - LintEclipseContext.showMarker(selection.get(0)); + EclipseLintClient.showMarker(selection.get(0)); } } } @@ -326,7 +337,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha case ACTION_REFRESH: { Job[] jobs = LintRunner.getCurrentJobs(); if (jobs.length > 0) { - LintRunner.cancelCurrentJobs(); + LintRunner.cancelCurrentJobs(false); } else { List<IResource> resources = mLintView.getResources(); if (resources == null) { @@ -348,7 +359,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha case ACTION_FIX: { List<IMarker> markers = mLintView.getSelectedMarkers(); for (IMarker marker : markers) { - LintFix fix = LintFix.getFix(LintEclipseContext.getId(marker), marker); + LintFix fix = LintFix.getFix(EclipseLintClient.getId(marker), marker); IEditorPart editor = AdtUtils.getActiveEditor(); if (editor instanceof AndroidXmlEditor) { IStructuredDocument doc = @@ -375,15 +386,16 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha List<IResource> resources = mLintView.getResources(); if (resources != null) { for (IResource resource : resources) { - LintEclipseContext.clearMarkers(resource); + EclipseLintClient.clearMarkers(resource); } } break; case ACTION_IGNORE: { for (IMarker marker : mLintView.getSelectedMarkers()) { - String id = LintEclipseContext.getId(marker); + String id = EclipseLintClient.getId(marker); if (id != null) { - LintFixGenerator.suppressDetector(id, true, null); + // TODO: Add "ignore in all" button! + LintFixGenerator.suppressDetector(id, true, null, true/*all*/); } } break; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfiguration.java new file mode 100644 index 0000000..dd9bd5f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfiguration.java @@ -0,0 +1,89 @@ +/* + * 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.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AdtUtils; +import com.android.tools.lint.client.api.Configuration; +import com.android.tools.lint.client.api.DefaultConfiguration; +import com.android.tools.lint.client.api.LintClient; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Project; +import com.android.tools.lint.detector.api.Severity; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.QualifiedName; + +import java.io.File; + +/** Configuration for Lint in Eclipse projects */ +class ProjectLintConfiguration extends DefaultConfiguration { + private boolean mFatalOnly; + + private final static QualifiedName CONFIGURATION_NAME = new QualifiedName(AdtPlugin.PLUGIN_ID, + "lintconfig"); //$NON-NLS-1$ + + @VisibleForTesting + ProjectLintConfiguration(LintClient client, Project project, + Configuration parent, boolean fatalOnly) { + super(client, project, parent); + mFatalOnly = fatalOnly; + } + + private static ProjectLintConfiguration create(LintClient client, IProject project, + Configuration parent, boolean fatalOnly) { + File dir = AdtUtils.getAbsolutePath(project).toFile(); + Project lingProject = new Project(client, dir, dir); + return new ProjectLintConfiguration(client, lingProject, parent, fatalOnly); + } + + public static ProjectLintConfiguration get(LintClient client, IProject project, + boolean fatalOnly) { + // Don't cache fatal-only configurations: they're only used occasionally and typically + // not repeatedly + if (fatalOnly) { + return create(client, project, GlobalLintConfiguration.get(), true); + } + + ProjectLintConfiguration configuration = null; + try { + Object value = project.getSessionProperty(CONFIGURATION_NAME); + configuration = (ProjectLintConfiguration) value; + } catch (CoreException e) { + // Not a problem; we will just create a new one + } + if (configuration == null) { + configuration = create(client, project, GlobalLintConfiguration.get(), false); + try { + project.setSessionProperty(CONFIGURATION_NAME, configuration); + } catch (CoreException e) { + AdtPlugin.log(e, "Can't store lint configuration"); + } + } + return configuration; + } + + @Override + public Severity getSeverity(Issue issue) { + Severity severity = super.getSeverity(issue); + if (mFatalOnly && severity != Severity.ERROR) { + return Severity.IGNORE; + } + return severity; + } +}
\ No newline at end of file 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 index 273d8de..24244d0 100644 --- 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 @@ -21,12 +21,15 @@ import org.eclipse.core.runtime.IAdaptable; import org.eclipse.jface.action.IAction; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.ui.IActionDelegate; +import org.eclipse.ui.IObjectActionDelegate; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.IWorkbenchWindowActionDelegate; /** Action which runs Lint on the current project */ -public class RunLintAction implements IActionDelegate { +public class RunLintAction implements IWorkbenchWindowActionDelegate, IObjectActionDelegate { - private ISelection mSelection; + private ISelection mSelection; public void selectionChanged(IAction action, ISelection selection) { mSelection = selection; @@ -64,4 +67,14 @@ public class RunLintAction implements IActionDelegate { return null; } + + public void setActivePart(IAction action, IWorkbenchPart targetPart) { + } + + public void dispose() { + // Nothing to dispose + } + + public void init(IWorkbenchWindow window) { + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/UnusedResourceDetector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/UnusedResourceDetector.java index c6b8da9..424d39d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/UnusedResourceDetector.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/UnusedResourceDetector.java @@ -59,7 +59,7 @@ class UnusedResourceDetector extends com.android.tools.lint.checks.UnusedResourc try { javaProject = BaseProjectHelper.getJavaProject(project); } catch (CoreException e) { - context.toolContext.log(e, null); + context.client.log(e, null); return; } if (javaProject == null) { @@ -94,7 +94,7 @@ class UnusedResourceDetector extends com.android.tools.lint.checks.UnusedResourc } } } catch (CoreException e) { - context.toolContext.log(e, null); + context.client.log(e, null); } } 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 b35af5c..93326ec 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 @@ -64,7 +64,6 @@ public final class AdtPrefs extends AbstractPreferenceInitializer { 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 */ 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 index 43ae145..6549e42 100644 --- 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 @@ -16,75 +16,85 @@ 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.AdtUtils; +import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient; 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.client.api.Configuration; +import com.android.tools.lint.client.api.IssueRegistry; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Project; 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.core.runtime.IAdaptable; 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.IColorProvider; 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.jface.viewers.TreeNodeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; 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.Combo; 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.Link; import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; +import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.PreferencesUtil; +import org.eclipse.ui.dialogs.PropertyPage; +import java.io.File; +import java.util.ArrayList; 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; - +public class LintPreferencePage extends PropertyPage implements IWorkbenchPreferencePage, + SelectionListener, ControlListener { + private static final String ID = + "com.android.ide.eclipse.common.preferences.LintPreferencePage"; //$NON-NLS-1$ + private static final int ID_COLUMN_WIDTH = 150; + + private EclipseLintClient mClient; + private IssueRegistry mRegistry; + private Configuration mConfiguration; + private IProject mProject; private Map<Issue, Severity> mSeverities = new HashMap<Issue, Severity>(); - private LintEclipseContext mContext; - private DetectorRegistry mRegistry; + private boolean mIgnoreEvent; - private Table mTable; + private Tree mTree; + private TreeViewer mTreeViewer; private Text mDetailsText; - private Text mSuppressedText; private Button mCheckFileCheckbox; private Button mCheckExportCheckbox; + private Link mWorkspaceLink; + private TreeColumn mNameColumn; + private TreeColumn mIdColumn; + private Combo mSeverityCombo; /** * Create the preference page. @@ -95,131 +105,109 @@ public class LintPreferencePage extends PreferencePage implements IWorkbenchPref @Override public Control createContents(Composite parent) { + IAdaptable resource = getElement(); + if (resource != null) { + mProject = (IProject) resource.getAdapter(IProject.class); + } + 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); + Project project = null; + if (mProject != null) { + File dir = AdtUtils.getAbsolutePath(mProject).toFile(); + project = new Project(mClient, dir, dir); + + Label projectLabel = new Label(container, SWT.CHECK); + projectLabel.setLayoutData(new GridData(SWT.LEFT, SWT.BOTTOM, false, false, 1, + 1)); + projectLabel.setText("Project-specific configuration:"); + + mWorkspaceLink = new Link(container, SWT.NONE); + mWorkspaceLink.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + mWorkspaceLink.setText("<a>Configure Workspace Settings...</a>"); + mWorkspaceLink.addSelectionListener(this); + } else { + 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 separator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL); + separator.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("Issues:"); + } + + mRegistry = EclipseLintClient.getRegistry(); + mClient = new EclipseLintClient(mRegistry, mProject, null, false); + mConfiguration = mClient.getConfiguration(project); + + mTreeViewer = new TreeViewer(container, SWT.BORDER | SWT.FULL_SELECTION); + mTree = mTreeViewer.getTree(); + GridData gdTable = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1); + gdTable.widthHint = 500; + gdTable.heightHint = 160; + mTree.setLayoutData(gdTable); + mTree.setLinesVisible(true); + mTree.setHeaderVisible(true); + + TreeViewerColumn column1 = new TreeViewerColumn(mTreeViewer, SWT.NONE); + mIdColumn = column1.getColumn(); + mIdColumn.setWidth(100); + mIdColumn.setText("Id"); + + TreeViewerColumn column2 = new TreeViewerColumn(mTreeViewer, SWT.FILL); + mNameColumn = column2.getColumn(); + mNameColumn.setWidth(100); + mNameColumn.setText("Name"); + + mTreeViewer.setContentProvider(new ContentProvider()); + mTreeViewer.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, 1, 2); gdText.heightHint = 80; mDetailsText.setLayoutData(gdText); - mRegistry = LintEclipseContext.getRegistry(); - mContext = new LintEclipseContext(mRegistry, null, null); + Label severityLabel = new Label(container, SWT.NONE); + severityLabel.setText("Severity:"); + + mSeverityCombo = new Combo(container, SWT.READ_ONLY); + mSeverityCombo.setItems(new String[] { + "(Default)", "Error", "Warning", "Information", "Ignore" + }); + GridData gdSeverityCombo = new GridData(SWT.FILL, SWT.TOP, false, false, 1, 1); + gdSeverityCombo.widthHint = 90; + mSeverityCombo.setLayoutData(gdSeverityCombo); + mSeverityCombo.setText(""); + mSeverityCombo.addSelectionListener(this); List<Issue> issues = mRegistry.getIssues(); for (Issue issue : issues) { - mSeverities.put(issue, mContext.getSeverity(issue)); + Severity severity = mConfiguration.getSeverity(issue); + mSeverities.put(issue, severity); } - 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()); - } - }); + mTreeViewer.setInput(mRegistry); + mTree.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; - - // 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()); - } + mTree.addControlListener(this); - idColumn.setWidth(ID_COLUMN_WIDTH); - availableWidth -= ID_COLUMN_WIDTH; + loadSettings(false); - 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(); + mTreeViewer.expandAll(); return container; } @@ -236,19 +224,20 @@ public class LintPreferencePage extends PreferencePage implements IWorkbenchPref super.dispose(); } - /** Cache for {@link #getCheckboxWidth()} */ - private static int sCheckboxWidth = -1; + @Override + protected void performDefaults() { + super.performDefaults(); + + mConfiguration.startBulkEditing(); - /** 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(); + List<Issue> issues = mRegistry.getIssues(); + for (Issue issue : issues) { + mConfiguration.setSeverity(issue, null); } - return sCheckboxWidth; + mConfiguration.finishBulkEditing(); + + loadSettings(true); } @Override @@ -257,62 +246,53 @@ public class LintPreferencePage extends PreferencePage implements IWorkbenchPref 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); - } + private void loadSettings(boolean refresh) { + if (mCheckExportCheckbox != null) { + AdtPrefs prefs = AdtPrefs.getPrefs(); + mCheckFileCheckbox.setSelection(prefs.isLintOnSave()); + mCheckExportCheckbox.setSelection(prefs.isLintOnExport()); + } + + mSeverities.clear(); + List<Issue> issues = mRegistry.getIssues(); + for (Issue issue : issues) { + Severity severity = mConfiguration.getSeverity(issue); + mSeverities.put(issue, severity); } - 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())); + + if (refresh) { + mTreeViewer.refresh(); } } 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; + if (mCheckExportCheckbox != null) { + AdtPrefs prefs = AdtPrefs.getPrefs(); + prefs.setLintOnExport(mCheckExportCheckbox.getSelection()); + prefs.setLintOnSave(mCheckFileCheckbox.getSelection()); } - 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); + mConfiguration.startBulkEditing(); + boolean changed = false; + try { + // Severities + for (Map.Entry<Issue, Severity> entry : mSeverities.entrySet()) { + Issue issue = entry.getKey(); + Severity severity = entry.getValue(); + if (mConfiguration.getSeverity(issue) != severity) { + if (severity == issue.getDefaultSeverity()) { + severity = null; + } + mConfiguration.setSeverity(issue, severity); + changed = true; + } } + } finally { + mConfiguration.finishBulkEditing(); + } + if (changed) { // Ask user whether we should re-run the rules. MessageDialog dialog = new MessageDialog( null, "Lint Settings Have Changed", null, @@ -337,20 +317,136 @@ public class LintPreferencePage extends PreferencePage implements IWorkbenchPref } } - private static class ContentProvider implements IStructuredContentProvider { + // ---- Implements SelectionListener ---- + + public void widgetSelected(SelectionEvent e) { + if (mIgnoreEvent) { + return; + } + + Object source = e.getSource(); + if (source == mTree) { + TreeItem item = (TreeItem) e.item; + Object data = item.getData(); + if (data instanceof Issue) { + Issue issue = (Issue) data; + 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()); + try { + mIgnoreEvent = true; + Severity severity = getSeverity(issue); + mSeverityCombo.select(severity.ordinal() + 1); // Skip the default option + mSeverityCombo.setEnabled(true); + } finally { + mIgnoreEvent = false; + } + } else { + mDetailsText.setText(""); + try { + mIgnoreEvent = true; + mSeverityCombo.setText(""); + mSeverityCombo.setEnabled(false); + } finally { + mIgnoreEvent = false; + } + } + } else if (source == mWorkspaceLink) { + int result = PreferencesUtil.createPreferenceDialogOn(getShell(), ID, + new String[] { ID }, null).open(); + if (result == Window.OK) { + loadSettings(true); + } + } else if (source == mSeverityCombo) { + int index = mSeverityCombo.getSelectionIndex(); + Issue issue = (Issue) mTree.getSelection()[0].getData(); + Severity severity; + if (index == -1 || index == 0) { + // "(Default)" + severity = issue.getDefaultSeverity(); + } else { + // -1: Skip the "(Default)" + severity = Severity.values()[index - 1]; + } + mSeverities.put(issue, severity); + mTreeViewer.refresh(); + } + } + + private Severity getSeverity(Issue issue) { + Severity severity = mSeverities.get(issue); + if (severity != null) { + return severity; + } + + return mConfiguration.getSeverity(issue); + } + + public void widgetDefaultSelected(SelectionEvent e) { + if (e.getSource() == mTree) { + widgetSelected(e); + } + } + + // ---- Implements ControlListener ---- + + public void controlMoved(ControlEvent e) { + } + + public void controlResized(ControlEvent e) { + Rectangle r = mTree.getClientArea(); + int availableWidth = r.width; + + mIdColumn.setWidth(ID_COLUMN_WIDTH); + availableWidth -= ID_COLUMN_WIDTH; + + // Name absorbs everything else + mNameColumn.setWidth(availableWidth); + } + + private class ContentProvider extends TreeNodeContentProvider { + private Map<Category, List<Issue>> mCategoryToIssues; + + @Override public Object[] getElements(Object inputElement) { - @SuppressWarnings("unchecked") - List<Issue> issues = (List<Issue>) inputElement; - return issues.toArray(); + return mRegistry.getCategories().toArray(); } - public void dispose() { + + @Override + public boolean hasChildren(Object element) { + return element instanceof Category; } - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + + @Override + public Object[] getChildren(Object parentElement) { + if (mCategoryToIssues == null) { + mCategoryToIssues = new HashMap<Category, List<Issue>>(); + List<Issue> issues = mRegistry.getIssues(); + for (Issue issue : issues) { + List<Issue> list = mCategoryToIssues.get(issue.getCategory()); + if (list == null) { + list = new ArrayList<Issue>(); + mCategoryToIssues.put(issue.getCategory(), list); + } + list.add(issue); + } + } + + return mCategoryToIssues.get(parentElement).toArray(); + } + + @Override + public Object getParent(Object element) { + return super.getParent(element); } } - private class LabelProvider implements ITableLabelProvider { - // TODO: add IColorProvider ? + private class LabelProvider implements ITableLabelProvider, IColorProvider { public void addListener(ILabelProviderListener listener) { } @@ -366,7 +462,11 @@ public class LintPreferencePage extends PreferencePage implements IWorkbenchPref } public Image getColumnImage(Object element, int columnIndex) { - if (columnIndex == 3) { + if (element instanceof Category) { + return null; + } + + if (columnIndex == 1) { Issue issue = (Issue) element; Severity severity = mSeverities.get(issue); if (severity == null) { @@ -382,7 +482,6 @@ public class LintPreferencePage extends PreferencePage implements IWorkbenchPref 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); } } @@ -390,104 +489,48 @@ public class LintPreferencePage extends PreferencePage implements IWorkbenchPref } public String getColumnText(Object element, int columnIndex) { + if (element instanceof Category) { + if (columnIndex == 0) { + return ((Category) element).getName(); + } else { + return ((Category) element).getExplanation(); + } + } + 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()); - } + // ---- IColorProvider ---- - @Override - protected boolean canEdit(Object element) { - return true; - } + public Color getForeground(Object element) { + if (element instanceof Category) { + return mTree.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND); + } - @Override - protected Object getValue(Object element) { if (element instanceof Issue) { Issue issue = (Issue) element; Severity severity = mSeverities.get(issue); - return severity; + if (severity == Severity.IGNORE) { + return mTree.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); + } } 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); + public Color getBackground(Object element) { + if (element instanceof Category) { + return mTree.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND); } - } - - @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.tests/.classpath b/eclipse/plugins/com.android.ide.eclipse.tests/.classpath index 7bfdfc4..6aeeb1f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/.classpath +++ b/eclipse/plugins/com.android.ide.eclipse.tests/.classpath @@ -15,5 +15,7 @@ <classpathentry kind="lib" path="/adt/libs/sdkuilib.jar" sourcepath="/SdkUiLib"/> <classpathentry kind="lib" path="/adt/libs/rule_api.jar" sourcepath="/rule_api"/> <classpathentry kind="lib" path="/adt/libs/common.jar"/> + <classpathentry kind="lib" path="/adt/libs/lint_api.jar" sourcepath="/lint-api"/> + <classpathentry kind="lib" path="/adt/libs/lint_checks.jar" sourcepath="/lint-checks"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF index b85af50..905fd83 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF @@ -10,4 +10,6 @@ Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-ClassPath: kxml2-2.3.0.jar, ., layoutlib.jar, + lint_api.jar, + lint_checks.jar, easymock.jar diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/build.properties b/eclipse/plugins/com.android.ide.eclipse.tests/build.properties index eece9f2..a79421c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/build.properties +++ b/eclipse/plugins/com.android.ide.eclipse.tests/build.properties @@ -8,6 +8,8 @@ bin.includes = META-INF/,\ unittest.xml,\ kxml2-2.3.0.jar,\ layoutlib.jar,\ + lint_api.jar, + lint_checks.jar, unittests/com/android/sdklib/testdata/,\ unittests/com/android/layoutlib/testdata/,\ unittests/com/android/ide/eclipse/testdata/,\ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java new file mode 100644 index 0000000..52ab9d7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java @@ -0,0 +1,228 @@ +/* + * 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.internal.editors.layout.refactoring.AdtProjectTest; +import com.android.tools.lint.checks.DuplicateIdDetector; +import com.android.tools.lint.checks.UnusedResourceDetector; +import com.android.tools.lint.client.api.Configuration; +import com.android.tools.lint.client.api.IDomParser; +import com.android.tools.lint.client.api.LintClient; +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.Project; +import com.android.tools.lint.detector.api.Severity; + +import org.eclipse.core.resources.IProject; + +import java.io.File; +import java.util.Calendar; + +@SuppressWarnings("javadoc") +public class ProjectLintConfigurationTest extends AdtProjectTest { + public void testBasic() { + Configuration parent = null; + LintClient client = new TestClient(); + + File dir = getTargetDir(); + if (!dir.exists()) { + boolean ok = dir.mkdirs(); + assertTrue(dir.getPath(), ok); + } + Project project = new Project(client, dir, dir); + + ProjectLintConfiguration config = + new ProjectLintConfiguration(client, project, parent, false /*fatalOnly*/); + + Issue usuallyEnabledIssue = DuplicateIdDetector.WITHIN_LAYOUT; + Issue usuallyDisabledIssue = UnusedResourceDetector.ISSUE_IDS; + + assertTrue(config.isEnabled(usuallyEnabledIssue)); + assertFalse(config.isEnabled(usuallyDisabledIssue)); + + config.setSeverity(usuallyEnabledIssue, Severity.IGNORE); + config.setSeverity(usuallyDisabledIssue, Severity.ERROR); + assertFalse(config.isEnabled(usuallyEnabledIssue)); + assertTrue(config.isEnabled(usuallyDisabledIssue)); + + // Make a NEW config object to ensure the state is persisted properly, not just + // kept on the config object! + config = new ProjectLintConfiguration(client, project, parent, false /*fatalOnly*/); + assertFalse(config.isEnabled(usuallyEnabledIssue)); + assertTrue(config.isEnabled(usuallyDisabledIssue)); + } + + public void testInheritance() { + Configuration parent = null; + LintClient client = new TestClient(); + + File dir = getTargetDir(); + assertTrue(dir.mkdirs()); + Project project = new Project(client, dir, dir); + + File otherDir = new File(dir, "otherConfig"); + assertTrue(otherDir.mkdir()); + Project otherProject = new Project(client, otherDir, otherDir); + + ProjectLintConfiguration otherConfig = + new ProjectLintConfiguration(client, otherProject, parent, false); + + ProjectLintConfiguration config = + new ProjectLintConfiguration(client, project, otherConfig, false); + + Issue usuallyEnabledIssue = DuplicateIdDetector.WITHIN_LAYOUT; + Issue usuallyDisabledIssue = UnusedResourceDetector.ISSUE_IDS; + + assertTrue(config.isEnabled(usuallyEnabledIssue)); + assertFalse(config.isEnabled(usuallyDisabledIssue)); + + otherConfig.setSeverity(usuallyEnabledIssue, Severity.IGNORE); + otherConfig.setSeverity(usuallyDisabledIssue, Severity.ERROR); + + // Ensure inheritance works + assertFalse(config.isEnabled(usuallyEnabledIssue)); + assertTrue(config.isEnabled(usuallyDisabledIssue)); + + // Revert + otherConfig.setSeverity(usuallyEnabledIssue, Severity.ERROR); + otherConfig.setSeverity(usuallyDisabledIssue, Severity.IGNORE); + assertTrue(config.isEnabled(usuallyEnabledIssue)); + assertFalse(config.isEnabled(usuallyDisabledIssue)); + + // Now override in child + config.setSeverity(usuallyEnabledIssue, Severity.ERROR); + config.setSeverity(usuallyDisabledIssue, Severity.IGNORE); + assertTrue(config.isEnabled(usuallyEnabledIssue)); + assertFalse(config.isEnabled(usuallyDisabledIssue)); + + // Now change in parent: no change in child + otherConfig.setSeverity(usuallyEnabledIssue, Severity.IGNORE); + otherConfig.setSeverity(usuallyDisabledIssue, Severity.ERROR); + assertTrue(config.isEnabled(usuallyEnabledIssue)); + assertFalse(config.isEnabled(usuallyDisabledIssue)); + assertFalse(otherConfig.isEnabled(usuallyEnabledIssue)); + assertTrue(otherConfig.isEnabled(usuallyDisabledIssue)); + + // Clear override in child + config.setSeverity(usuallyEnabledIssue, null); + config.setSeverity(usuallyDisabledIssue, null); + assertFalse(config.isEnabled(usuallyEnabledIssue)); + assertTrue(config.isEnabled(usuallyDisabledIssue)); + } + + public void testBulkEditing() { + Configuration parent = null; + LintClient client = new TestClient(); + + File dir = getTargetDir(); + assertTrue(dir.mkdirs()); + Project project = new Project(client, dir, dir); + + ProjectLintConfiguration config = + new ProjectLintConfiguration(client, project, parent, false /*fatalOnly*/); + + Issue usuallyEnabledIssue = DuplicateIdDetector.WITHIN_LAYOUT; + Issue usuallyDisabledIssue = UnusedResourceDetector.ISSUE_IDS; + + assertTrue(config.isEnabled(usuallyEnabledIssue)); + assertFalse(config.isEnabled(usuallyDisabledIssue)); + + config.setSeverity(usuallyEnabledIssue, Severity.IGNORE); + assertFalse(config.isEnabled(usuallyEnabledIssue)); + assertFalse(config.isEnabled(usuallyDisabledIssue)); + + File configFile = new File(dir, "lint.xml"); + assertTrue(configFile.getPath(), configFile.exists()); + long lastModified = configFile.lastModified(); + + // We need to make sure that the timestamp of the file is a couple of seconds + // after the last update or we can't tell whether the file was updated or not + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + System.err.println("Sleep interrupted, test may not work."); + } + config.startBulkEditing(); + assertFalse(lastModified < configFile.lastModified()); + assertEquals(lastModified, configFile.lastModified()); + config.setSeverity(usuallyDisabledIssue, Severity.ERROR); + config.finishBulkEditing(); + assertTrue(lastModified < configFile.lastModified()); + + assertTrue(config.isEnabled(usuallyDisabledIssue)); + } + + public void testPersistence() { + // Ensure that we use the same configuration object repeatedly for a + // single project, such that we don't recompute and parse XML for each and + // every lint run! + IProject project = getProject(); + TestClient client = new TestClient(); + ProjectLintConfiguration config1 = ProjectLintConfiguration.get(client, project, false); + ProjectLintConfiguration config2 = ProjectLintConfiguration.get(client, project, false); + assertSame(config1, config2); + } + + private static File sTempDir = null; + @Override + protected 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.mkdirs()) { + sTempDir = tmpDir; + } else { + sTempDir = base; + } + } + + return sTempDir; + } + + @Override + protected File getTargetDir() { + return new File(getTempDir(), getClass().getSimpleName() + "_" + getName()); + } + + private static class TestClient extends LintClient { + @Override + public void report(Context context, Issue issue, Location location, String message, + Object data) { + } + + @Override + public void log(Throwable exception, String format, Object... args) { + } + + @Override + public IDomParser getParser() { + return null; + } + + @Override + public String readFile(File file) { + return null; + } + } +} diff --git a/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java index c7cef97..2adf9a4 100644 --- a/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java @@ -26,7 +26,7 @@ public final class VersionQualifier extends ResourceQualifier { /** Default pixel density value. This means the property is not set. */ private final static int DEFAULT_VERSION = -1; - private final static Pattern sCountryCodePattern = Pattern.compile("^v(\\d+)$");//$NON-NLS-1$ + private final static Pattern sVersionPattern = Pattern.compile("^v(\\d+)$");//$NON-NLS-1$ private int mVersion = DEFAULT_VERSION; @@ -39,7 +39,7 @@ public final class VersionQualifier extends ResourceQualifier { * @return a new {@link VersionQualifier} object or <code>null</code> */ public static VersionQualifier getQualifier(String segment) { - Matcher m = sCountryCodePattern.matcher(segment); + Matcher m = sVersionPattern.matcher(segment); if (m.matches()) { String v = m.group(1); diff --git a/lint/cli/src/com/android/tools/lint/HtmlReporter.java b/lint/cli/src/com/android/tools/lint/HtmlReporter.java index 0d8335e..c4b8602 100644 --- a/lint/cli/src/com/android/tools/lint/HtmlReporter.java +++ b/lint/cli/src/com/android/tools/lint/HtmlReporter.java @@ -16,6 +16,7 @@ package com.android.tools.lint; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Severity; @@ -66,6 +67,11 @@ class HtmlReporter extends Reporter { " font-weight: bold;\n" + //$NON-NLS-1$ " margin: 5px 0px 5px 0px;\n" + //$NON-NLS-1$ "}\n" + //$NON-NLS-1$ + ".category {\n" + //$NON-NLS-1$ + " font-size: 18pt;\n" + //$NON-NLS-1$ + " font-weight: bold;\n" + //$NON-NLS-1$ + " margin: 10px 0px 5px 0px;\n" + //$NON-NLS-1$ + "}\n" + //$NON-NLS-1$ // The issue summary line //".summary {\n" + //$NON-NLS-1$ //" font-weight: bold;\n" + //$NON-NLS-1$ @@ -93,6 +99,9 @@ class HtmlReporter extends Reporter { " max-width: 200px;\n" + //$NON-NLS-1$ " max-height: 200px;\n" + //$NON-NLS-1$ "}\n" + //$NON-NLS-1$ + // Image labels + "th { font-weight: normal; }\n" + //$NON-NLS-1$ + "table { border: none; }\n" + //$NON-NLS-1$ // The Priority/Category section ".metadata { }\n" + //$NON-NLS-1$ // Each error message @@ -132,28 +141,59 @@ class HtmlReporter extends Reporter { mWriter.write(String.format("Check performed at %1$s.", new Date().toString())); - mWriter.write("<br/>"); //$NON-NLS-1$ + mWriter.write("<br/><br/>"); //$NON-NLS-1$ mWriter.write(String.format("%1$d errors and %2$d warnings found:", errorCount, warningCount)); mWriter.write("<br/>"); //$NON-NLS-1$ // Write issue id summary mWriter.write("<ul>\n"); //$NON-NLS-1$ + Category previousCategory = null; for (List<Warning> warnings : related) { - mWriter.write("<li> <a href=\"#" //$NON-NLS-1$ - + warnings.get(0).issue.getId() - +"\">"); //$NON-NLS-1$ + Issue issue = warnings.get(0).issue; + + if (issue.getCategory() != previousCategory) { + if (previousCategory != null) { + mWriter.write("</ul>\n"); //$NON-NLS-1$ + } + previousCategory = issue.getCategory(); + String categoryName = issue.getCategory().getFullName(); + mWriter.write("<li> <a href=\"#"); //$NON-NLS-1$ + mWriter.write(categoryName); + mWriter.write("\">"); //$NON-NLS-1$ + mWriter.write(categoryName); + mWriter.write("</a>\n"); //$NON-NLS-1$ + mWriter.write("\n<ul>\n"); //$NON-NLS-1$ + } + + mWriter.write("<li> <a href=\"#"); //$NON-NLS-1$ + mWriter.write(issue.getId()); + mWriter.write("\">"); //$NON-NLS-1$ mWriter.write(String.format("%1$3d %2$s", //$NON-NLS-1$ - warnings.size(), warnings.get(0).issue.getId())); + warnings.size(), issue.getId())); mWriter.write("</a>\n"); //$NON-NLS-1$ } + if (previousCategory != null) { + mWriter.write("</ul>\n"); //$NON-NLS-1$ + } mWriter.write("</ul>\n"); //$NON-NLS-1$ mWriter.write("<br/>"); //$NON-NLS-1$ + previousCategory = null; for (List<Warning> warnings : related) { Warning first = warnings.get(0); Issue issue = first.issue; + if (issue.getCategory() != previousCategory) { + previousCategory = issue.getCategory(); + mWriter.write("\n<a name=\""); //$NON-NLS-1$ + mWriter.write(issue.getCategory().getFullName()); + mWriter.write("\">\n"); //$NON-NLS-1$ + mWriter.write("<div class=\"category\">"); //$NON-NLS-1$ + mWriter.write(issue.getCategory().getFullName()); + mWriter.write("</div>\n"); //$NON-NLS-1$ + } + mWriter.write("<a name=\"" + issue.getId() + "\">\n"); //$NON-NLS-1$ //$NON-NLS-2$ mWriter.write("<div class=\"issue\">\n"); //$NON-NLS-1$ @@ -225,7 +265,7 @@ class HtmlReporter extends Reporter { mWriter.write(issue.getPriority()); mWriter.write("<br/>\n"); //$NON-NLS-1$ mWriter.write("Category: "); - mWriter.write(issue.getCategory()); + mWriter.write(issue.getCategory().getFullName()); mWriter.write("</div>\n"); //$NON-NLS-1$ mWriter.write("Severity: "); @@ -300,7 +340,22 @@ class HtmlReporter extends Reporter { return getDpiRank(s1) - getDpiRank(s2); } }); - mWriter.write("<table normal\" border=\"0\"><tr>"); //$NON-NLS-1$ + mWriter.write("<table>"); //$NON-NLS-1$ + mWriter.write("<tr>"); //$NON-NLS-1$ + for (String linkedUrl : urls) { + // Image series: align top + mWriter.write("<td>"); //$NON-NLS-1$ + mWriter.write("<a href=\""); //$NON-NLS-1$ + mWriter.write(linkedUrl); + mWriter.write("\">"); //$NON-NLS-1$ + mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$ + mWriter.write(linkedUrl); + mWriter.write("\" /></a>\n"); //$NON-NLS-1$ + mWriter.write("</td>"); //$NON-NLS-1$ + } + mWriter.write("</tr>"); //$NON-NLS-1$ + + mWriter.write("<tr>"); //$NON-NLS-1$ for (String linkedUrl : urls) { mWriter.write("<th>"); //$NON-NLS-1$ int index = linkedUrl.lastIndexOf("drawable-"); //$NON-NLS-1$ @@ -313,19 +368,9 @@ class HtmlReporter extends Reporter { } mWriter.write("</th>"); //$NON-NLS-1$ } - mWriter.write("</tr>\n<tr>"); //$NON-NLS-1$ - for (String linkedUrl : urls) { - // Image series: align top - mWriter.write("<td>"); //$NON-NLS-1$ - mWriter.write("<a href=\""); //$NON-NLS-1$ - mWriter.write(linkedUrl); - mWriter.write("\">"); //$NON-NLS-1$ - mWriter.write("<img border=\"0\" align=\"top\" src=\""); //$NON-NLS-1$ - mWriter.write(linkedUrl); - mWriter.write("\" /></a>\n"); //$NON-NLS-1$ - mWriter.write("</td>"); //$NON-NLS-1$ - } - mWriter.write("</tr></table>"); //$NON-NLS-1$ + mWriter.write("</tr>\n"); //$NON-NLS-1$ + + mWriter.write("</table>\n"); //$NON-NLS-1$ } } else { // Just this image: float to the right diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java index 11d2af1..0fe1fcb 100644 --- a/lint/cli/src/com/android/tools/lint/Main.java +++ b/lint/cli/src/com/android/tools/lint/Main.java @@ -16,15 +16,20 @@ 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.checks.BuiltinIssueRegistry; +import com.android.tools.lint.client.api.Configuration; +import com.android.tools.lint.client.api.DefaultConfiguration; +import com.android.tools.lint.client.api.IDomParser; +import com.android.tools.lint.client.api.IssueRegistry; +import com.android.tools.lint.client.api.Lint; +import com.android.tools.lint.client.api.LintClient; +import com.android.tools.lint.client.api.LintListener; +import com.android.tools.lint.detector.api.Category; 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.Project; import com.android.tools.lint.detector.api.Severity; import java.io.BufferedReader; @@ -51,20 +56,21 @@ import java.util.Set; * <li>Offer suppressing violations * </ul> */ -public class Main extends ToolContext { +public class Main extends LintClient { private static final int MAX_LINE_WIDTH = 70; private static final String ARG_ENABLE = "--enable"; //$NON-NLS-1$ private static final String ARG_DISABLE = "--disable"; //$NON-NLS-1$ private static final String ARG_CHECK = "--check"; //$NON-NLS-1$ - private static final String ARG_SUPPRESS = "--suppress"; //$NON-NLS-1$ private static final String ARG_IGNORE = "--ignore"; //$NON-NLS-1$ private static final String ARG_LISTIDS = "--list"; //$NON-NLS-1$ private static final String ARG_SHOW = "--show"; //$NON-NLS-1$ + private static final String ARG_QUIET = "--quiet"; //$NON-NLS-1$ private static final String ARG_FULLPATH = "--fullpath"; //$NON-NLS-1$ private static final String ARG_HELP = "--help"; //$NON-NLS-1$ private static final String ARG_NOLINES = "--nolines"; //$NON-NLS-1$ private static final String ARG_HTML = "--html"; //$NON-NLS-1$ - private static final String ARG_URL = "--url"; //$NON-NLS-1$ + private static final String ARG_XML = "--xml"; //$NON-NLS-1$ + private static final String ARG_URL = "--url"; //$NON-NLS-1$ private static final int ERRNO_ERRORS = -1; private static final int ERRNO_USAGE = -2; private static final int ERRNO_EXISTS = -3; @@ -82,6 +88,7 @@ public class Main extends ToolContext { private int mWarningCount; private boolean mShowLines = true; private Reporter mReporter; + private boolean mQuiet; /** Creates a CLI driver */ public Main() { @@ -107,7 +114,7 @@ public class Main extends ToolContext { System.exit(ERRNO_USAGE); } - DetectorRegistry registry = new BuiltinDetectorRegistry(); + IssueRegistry registry = new BuiltinIssueRegistry(); // Mapping from file path prefix to URL. Applies only to HTML reports String urlMap = null; @@ -141,6 +148,8 @@ public class Main extends ToolContext { } else if (arg.equals(ARG_FULLPATH) || arg.equals(ARG_FULLPATH + "s")) { // allow "--fullpaths" too mFullPath = true; + } else if (arg.equals(ARG_QUIET) || arg.equals("-q")) { + mQuiet = true; } else if (arg.equals(ARG_NOLINES)) { mShowLines = false; } else if (arg.equals(ARG_URL)) { @@ -178,21 +187,44 @@ public class Main extends ToolContext { log(e, null); System.exit(ERRNO_INVALIDARGS); } - } else if (arg.equals(ARG_SUPPRESS) || arg.equals(ARG_DISABLE) - || arg.equals(ARG_IGNORE)) { + } else if (arg.equals(ARG_XML)) { + if (index == args.length - 1) { + System.err.println("Missing XML output file name"); + System.exit(ERRNO_INVALIDARGS); + } + File output = new File(args[++index]); + if (output.exists()) { + boolean delete = output.delete(); + if (!delete) { + System.err.println("Could not delete old " + output); + System.exit(ERRNO_EXISTS); + } + } + if (output.canWrite()) { + System.err.println("Cannot write XML output file " + output); + System.exit(ERRNO_EXISTS); + } + try { + mReporter = new XmlReporter(output); + } catch (IOException e) { + log(e, null); + System.exit(ERRNO_INVALIDARGS); + } + } else if (arg.equals(ARG_DISABLE) || arg.equals(ARG_IGNORE)) { if (index == args.length - 1) { System.err.println("Missing categories or id's to disable"); System.exit(ERRNO_INVALIDARGS); } String[] ids = args[++index].split(","); for (String id : ids) { - if (registry.isCategory(id)) { + if (registry.isCategoryName(id)) { // Suppress all issues with the given category String category = id; for (Issue issue : registry.getIssues()) { // Check prefix such that filtering on the "Usability" category // will match issue category "Usability:Icons" etc. - if (category.startsWith(issue.getCategory())) { + if (issue.getCategory().getName().startsWith(category) || + issue.getCategory().getFullName().startsWith(category)) { mSuppress.add(issue.getId()); } } @@ -211,11 +243,12 @@ public class Main extends ToolContext { } String[] ids = args[++index].split(","); for (String id : ids) { - if (registry.isCategory(id)) { + if (registry.isCategoryName(id)) { // Enable all issues with the given category String category = id; for (Issue issue : registry.getIssues()) { - if (category.startsWith(issue.getCategory())) { + if (issue.getCategory().getName().startsWith(category) || + issue.getCategory().getFullName().startsWith(category)) { mEnabled.add(issue.getId()); } } @@ -235,13 +268,14 @@ public class Main extends ToolContext { mCheck = new HashSet<String>(); String[] ids = args[++index].split(","); for (String id : ids) { - if (registry.isCategory(id)) { + if (registry.isCategoryName(id)) { // Suppress all issues with the given category String category = id; for (Issue issue : registry.getIssues()) { // Check prefix such that filtering on the "Usability" category // will match issue category "Usability:Icons" etc. - if (category.startsWith(issue.getCategory())) { + if (issue.getCategory().getName().startsWith(category) || + issue.getCategory().getFullName().startsWith(category)) { mCheck.add(issue.getId()); } } @@ -302,8 +336,13 @@ public class Main extends ToolContext { ((HtmlReporter) mReporter).setUrlMap(map); } - Lint analyzer = new Lint(new BuiltinDetectorRegistry(), this, null); - analyzer.analyze(files); + Lint analyzer = new Lint(registry, this); + + if (!mQuiet) { + analyzer.addLintListener(new ProgressPrinter()); + } + + analyzer.analyze(files, null /* scope */); Collections.sort(mWarnings); @@ -317,11 +356,11 @@ public class Main extends ToolContext { System.exit(mFatal ? ERRNO_ERRORS : 0); } - private void displayValidIds(DetectorRegistry registry, PrintStream out) { - List<String> categories = registry.getCategories(); + private void displayValidIds(IssueRegistry registry, PrintStream out) { + List<Category> categories = registry.getCategories(); out.println("Valid issue categories:"); - for (String category : categories) { - out.println(" " + category); + for (Category category : categories) { + out.println(" " + category.getFullName()); } out.println(); List<Issue> issues = registry.getIssues(); @@ -331,7 +370,7 @@ public class Main extends ToolContext { } } - private void showIssues(DetectorRegistry registry) { + private void showIssues(IssueRegistry registry) { List<Issue> issues = registry.getIssues(); List<Issue> sorted = new ArrayList<Issue>(issues); Collections.sort(sorted, new Comparator<Issue>() { @@ -350,12 +389,13 @@ public class Main extends ToolContext { }); System.out.println("Available issues:\n"); - String previousCategory = null; + Category previousCategory = null; for (Issue issue : sorted) { - String category = issue.getCategory(); + Category category = issue.getCategory(); if (!category.equals(previousCategory)) { - System.out.println(category); - for (int i = 0, n = category.length(); i < n; i++) { + String name = category.getFullName(); + System.out.println(name); + for (int i = 0, n = name.length(); i < n; i++) { System.out.print('='); } System.out.println('\n'); @@ -393,11 +433,17 @@ public class Main extends ToolContext { } } + static String wrapArg(String explanation) { + // Wrap arguments such that the wrapped lines are not showing up in the left column + return wrap(explanation, MAX_LINE_WIDTH, " "); + } + + static String wrap(String explanation) { - return wrap(explanation, MAX_LINE_WIDTH); + return wrap(explanation, MAX_LINE_WIDTH, ""); } - static String wrap(String explanation, int max) { + static String wrap(String explanation, int lineWidth, String hangingIndent) { int explanationLength = explanation.length(); StringBuilder sb = new StringBuilder(explanationLength * 2); int index = 0; @@ -406,12 +452,12 @@ public class Main extends ToolContext { int lineEnd = explanation.indexOf('\n', index); int next; - if (lineEnd != -1 && (lineEnd - index) < max) { + if (lineEnd != -1 && (lineEnd - index) < lineWidth) { next = lineEnd + 1; } else { // Line is longer than available width; grab as much as we can - lineEnd = Math.min(index + max, explanationLength); - if (lineEnd - index < max) { + lineEnd = Math.min(index + lineWidth, explanationLength); + if (lineEnd - index < lineWidth) { next = explanationLength; } else { // then back up to the last space @@ -427,6 +473,12 @@ public class Main extends ToolContext { } } + if (sb.length() > 0) { + sb.append(hangingIndent); + } else { + lineWidth -= hangingIndent.length(); + } + sb.append(explanation.substring(index, lineEnd)); sb.append('\n'); index = next; @@ -441,19 +493,31 @@ public class Main extends ToolContext { out.println("Usage: " + command + " [flags] <project directories>\n"); out.println("Flags:"); - out.println(ARG_SUPPRESS + " <list>: Suppress a list of categories or specific issue id's"); - out.println(ARG_CHECK + " <list>: Only check the specific list of issues (categories or id's)"); - out.println(ARG_DISABLE + " <list>: Disable the list of categories or specific issue id's"); - out.println(ARG_ENABLE + " <list>: Enable the specific list of issues (plus default enabled)"); - out.println(ARG_FULLPATH + " : Use full paths in the error output"); - out.println(ARG_NOLINES + " : Do not include the source file lines with errors in the output"); - out.println(ARG_HTML + " <filename>: Create an HTML report instead"); - out.println(ARG_URL + " filepath=url: Add links to HTML report, replacing local path prefixes with url prefix"); + out.print(wrapArg(ARG_HELP + ": This message.")); + out.print(wrapArg(ARG_DISABLE + " <list>: Disable the list of categories or " + + "specific issue id's. The list should be a comma-separated list of issue " + + "id's or categories.")); + out.print(wrapArg(ARG_ENABLE + " <list>: Enable the specific list of issues. " + + "This checks all the default issues plus the specifically enabled issues. The " + + "list should be a comma-separated list of issue id's or categories.")); + out.print(wrapArg(ARG_CHECK + " <list>: Only check the specific list of issues. " + + "This will disable everything and re-enable the given list of issues. " + + "The list should be a comma-separated list of issue id's or categories.")); + out.print(wrapArg(ARG_FULLPATH + " : Use full paths in the error output.")); + out.print(wrapArg(ARG_NOLINES + " : Do not include the source file lines with errors " + + "in the output. By default, the error output includes snippets of source code " + + "on the line containing the error, but this flag turns it off.")); + out.print(wrapArg(ARG_HTML + " <filename>: Create an HTML report instead.")); + out.print(wrapArg(ARG_URL + " filepath=url: Add links to HTML report, replacing local " + + "path prefixes with url prefix. The mapping can be a comma-separated list of " + + "path prefixes to corresponding URL prefixes, such as " + + "C:\\temp\\Proj1=http://buildserver/sources/temp/Proj1")); + out.print(wrapArg(ARG_XML + " <filename>: Create an XML report instead.")); out.println(); - out.println(ARG_LISTIDS + ": List the available issue id's and exit."); - out.println(ARG_SHOW + ": List available issues along with full explanations"); - out.println(ARG_SHOW + " <ids>: Show full explanations for the given list of issue id's"); - out.println("Id lists should be comma separated with no spaces. "); + out.print(wrapArg(ARG_LISTIDS + ": List the available issue id's and exit.")); + out.print(wrapArg(ARG_SHOW + ": List available issues along with full explanations.")); + out.print(wrapArg(ARG_SHOW + " <ids>: Show full explanations for the given list of issue id's.")); + out.print(wrapArg(ARG_QUIET + ": Don't show progress.")); } @Override @@ -472,31 +536,16 @@ public class Main extends ToolContext { } @Override - public boolean isEnabled(Issue issue) { - String id = issue.getId(); - if (mSuppress.contains(id)) { - return false; - } - - if (mEnabled.contains(id)) { - return true; - } - - if (mCheck != null) { - return mCheck.contains(id); - } - - return issue.isEnabledByDefault(); + public Configuration getConfiguration(Project project) { + return new CliConfiguration(null, project); } @Override public void report(Context context, Issue issue, Location location, String message, Object data) { - if (!isEnabled(issue)) { - return; - } + assert context.configuration.isEnabled(issue); - Severity severity = getSeverity(issue); + Severity severity = context.configuration.getSeverity(issue); if (severity == Severity.IGNORE) { return; } @@ -533,7 +582,7 @@ public class Main extends ToolContext { warning.line = line; warning.offset = startPosition.getOffset(); if (line >= 0) { - warning.fileContents = context.toolContext.readFile(location.getFile()); + warning.fileContents = context.client.readFile(location.getFile()); if (mShowLines) { // Compute error line contents @@ -595,18 +644,6 @@ public class Main extends ToolContext { } @Override - public boolean isSuppressed(Context context, Issue issue, Location range, String message, - Severity severity, Object data) { - // Not yet supported - return false; - } - - @Override - public Severity getSeverity(Issue issue) { - return issue.getDefaultSeverity(); - } - - @Override public String readFile(File file) { BufferedReader reader = null; try { @@ -634,4 +671,61 @@ public class Main extends ToolContext { return ""; //$NON-NLS-1$ } + + /** + * Consult the lint.xml file, but override with the --enable and --disable + * flags supplied on the command line + */ + private class CliConfiguration extends DefaultConfiguration { + CliConfiguration(Configuration parent, Project project) { + super(Main.this, project, parent); + } + + @Override + public Severity getSeverity(Issue issue) { + Severity severity = super.getSeverity(issue); + + String id = issue.getId(); + if (mSuppress.contains(id)) { + return Severity.IGNORE; + } + + if (mEnabled.contains(id) || (mCheck != null && mCheck.contains(id))) { + // Overriding default + // Detectors shouldn't be returning ignore as a default severity, + // but in case they do, force it up to warning here to ensure that + // it's run + if (severity == Severity.IGNORE) { + return Severity.WARNING; + } else { + return severity; + } + } + + if (mCheck != null) { + return Severity.IGNORE; + } + + return severity; + } + } + + private class ProgressPrinter implements LintListener { + public void update(EventType type, Context context) { + switch (type) { + case SCANNING_PROJECT: + System.out.print(String.format( + "Scanning %1$s: ", + context.project.getDir().getName())); + break; + case SCANNING_FILE: + System.out.print('.'); + break; + case CANCELED: + case COMPLETED: + System.out.println(); + break; + } + } + } } diff --git a/lint/cli/src/com/android/tools/lint/PositionXmlParser.java b/lint/cli/src/com/android/tools/lint/PositionXmlParser.java index 06c195b..aa7f9c9 100644 --- a/lint/cli/src/com/android/tools/lint/PositionXmlParser.java +++ b/lint/cli/src/com/android/tools/lint/PositionXmlParser.java @@ -16,13 +16,11 @@ package com.android.tools.lint; -import com.android.tools.lint.api.IDomParser; +import com.android.tools.lint.client.api.IDomParser; +import com.android.tools.lint.client.api.IssueRegistry; 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.Scope; -import com.android.tools.lint.detector.api.Severity; import org.w3c.dom.Attr; import org.w3c.dom.Document; @@ -36,7 +34,6 @@ import org.xml.sax.helpers.DefaultHandler; import java.io.StringReader; import java.util.ArrayList; -import java.util.EnumSet; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -74,25 +71,19 @@ public class PositionXmlParser implements IDomParser { parser.parse(input, handler); return handler.getDocument(); } catch (ParserConfigurationException e) { - context.toolContext.log(e, null); + context.client.log(e, null); } catch (SAXException e) { - context.toolContext.report( + context.client.report( context, // Must provide an issue since API guarantees that the issue parameter // is valid - Issue.create("fatal", "", "", "", 10, Severity.ERROR, //$NON-NLS-1$ - EnumSet.noneOf(Scope.class)), + IssueRegistry.PARSER_ERROR, new Location(context.file, null, null), e.getCause() != null ? e.getCause().getLocalizedMessage() : e.getLocalizedMessage(), null); - - context.toolContext.log(null, String.format("Failed parsing %1$s: %2$s", - context.file.getName(), - e.getCause() != null ? e.getCause().getLocalizedMessage() : - e.getLocalizedMessage())); } catch (Throwable t) { - context.toolContext.log(t, null); + context.client.log(t, null); } return null; } @@ -378,4 +369,7 @@ public class PositionXmlParser implements IDomParser { return mColumn; } } + + public void dispose(Context context) { + } } diff --git a/lint/cli/src/com/android/tools/lint/Warning.java b/lint/cli/src/com/android/tools/lint/Warning.java index 0cc3d81..e6ca111 100644 --- a/lint/cli/src/com/android/tools/lint/Warning.java +++ b/lint/cli/src/com/android/tools/lint/Warning.java @@ -16,7 +16,7 @@ package com.android.tools.lint; -import com.android.tools.lint.api.ToolContext; +import com.android.tools.lint.client.api.LintClient; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Severity; @@ -24,7 +24,7 @@ import com.android.tools.lint.detector.api.Severity; import java.io.File; /** - * A {@link Warning} represents a specific warning that a {@link ToolContext} + * A {@link Warning} represents a specific warning that a {@link LintClient} * has been told about. The context stores these as they are reported into a * list of warnings such that it can sort them all before presenting them all at * the end. @@ -52,7 +52,7 @@ class Warning implements Comparable<Warning> { // ---- Implements Comparable<Warning> ---- public int compareTo(Warning other) { - // Sort by priority, then by category, then by id, + // Sort by category, then by priority, then by id, // then by file, then by line String id1 = issue.getId(); String id2 = other.issue.getId(); @@ -60,15 +60,15 @@ class Warning implements Comparable<Warning> { return file.getName().compareTo( other.file.getName()); } + int categoryDelta = issue.getCategory().compareTo(other.issue.getCategory()); + if (categoryDelta != 0) { + return categoryDelta; + } // DECREASING priority order int priorityDelta = other.issue.getPriority() - issue.getPriority(); if (priorityDelta != 0) { return priorityDelta; } - int categoryDelta = issue.getCategory().compareTo(other.issue.getCategory()); - if (categoryDelta != 0) { - return categoryDelta; - } int idDelta = id1.compareTo(id2); if (idDelta != -1) { return idDelta; diff --git a/lint/cli/src/com/android/tools/lint/XmlReporter.java b/lint/cli/src/com/android/tools/lint/XmlReporter.java new file mode 100644 index 0000000..39044eb --- /dev/null +++ b/lint/cli/src/com/android/tools/lint/XmlReporter.java @@ -0,0 +1,107 @@ +/* + * 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.detector.api.Position; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +/** + * A reporter which emits lint results into an XML report. + */ +class XmlReporter extends Reporter { + private final File mOutput; + + XmlReporter(File output) throws IOException { + super(new BufferedWriter(new FileWriter(output))); + mOutput = output; + } + + @Override + void write(int errorCount, int warningCount, List<Warning> issues) throws IOException { + mWriter.write( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //$NON-NLS-1$ + "<issues>\n"); //$NON-NLS-1$ + + if (issues.size() > 0) { + for (Warning warning : issues) { + mWriter.write("\n <issue"); + writeAttribute(mWriter, "id", warning.issue.getId()); //$NON-NLS-1$ + writeAttribute(mWriter, "severity", warning.severity.getDescription()); //$NON-NLS-1$ + writeAttribute(mWriter, "message", warning.issue.getId()); //$NON-NLS-1$ + if (warning.file != null) { + writeAttribute(mWriter, "file", warning.file.getPath()); //$NON-NLS-1$ + if (warning.location != null) { + Position start = warning.location.getStart(); + if (start != null) { + int line = start.getLine(); + int column = start.getColumn(); + if (line >= 0) { + writeAttribute(mWriter, "line", Integer.toString(line)); //$NON-NLS-1$ + if (column >= 0) { + writeAttribute(mWriter, "column", Integer.toString(column)); //$NON-NLS-1$ + } + } + } + } + } + mWriter.write("\n />\n"); + } + } + + mWriter.write( + "\n</issues>\n"); //$NON-NLS-1$ + mWriter.close(); + + String path = mOutput.getAbsolutePath(); + System.out.println(String.format("Wrote HTML report to %1$s", path)); + } + + private static void writeAttribute(Writer writer, String name, String value) + throws IOException { + writer.write("\n "); //$NON-NLS-1$ + writer.write(name); + writer.write('='); + writer.write('"'); + for (int i = 0, n = value.length(); i < n; i++) { + char c = value.charAt(i); + switch (c) { + case '"': + writer.write("""); //$NON-NLS-1$ + break; + case '\'': + writer.write("'"); //$NON-NLS-1$ + break; + case '&': + writer.write("&"); //$NON-NLS-1$ + break; + case '<': + writer.write("<"); //$NON-NLS-1$ + break; + default: + writer.write(c); + break; + } + } + writer.write('"'); + } +}
\ No newline at end of file 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 deleted file mode 100644 index 441601b..0000000 --- a/lint/libs/lint_api/src/com/android/tools/lint/api/DetectorRegistry.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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 com.android.tools.lint.detector.api.Severity; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -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; - private static List<String> sCategories; - private static Map<String, Issue> sIdToIssue; - - /** - * 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 true if the given category is a valid category - * - * @param category the category to be checked - * @return true if the given string is a valid category - */ - public boolean isCategory(String category) { - for (String c : getCategories()) { - if (c.equals(category)) { - return true; - } - } - - return false; - } - - /** - * Returns the available categories - * - * @return an iterator for all the categories, never null - */ - public List<String> getCategories() { - if (sCategories == null) { - // Compute the categories from the available issues. The order of categories - // will be determined by finding the maximum severity and maximum priority of - // the issues in each category and then sorting in descending order, using - // alphabetical order if the others are the same. - final Map<String, Integer> maxPriority = new HashMap<String, Integer>(); - final Map<String, Severity> maxSeverity = new HashMap<String, Severity>(); - for (Issue issue : getIssues()) { - String category = issue.getCategory(); - Integer priority = maxPriority.get(category); - if (priority == null || priority.intValue() < issue.getPriority()) { - maxPriority.put(category, issue.getPriority()); - } - Severity severity = maxSeverity.get(category); - if (severity == null || severity.compareTo(issue.getDefaultSeverity()) < 0) { - maxSeverity.put(category, issue.getDefaultSeverity()); - } - } - List<String> categories = new ArrayList<String>(maxPriority.keySet()); - Collections.sort(categories, new Comparator<String>() { - public int compare(String category1, String category2) { - Severity severity1 = maxSeverity.get(category1); - Severity severity2 = maxSeverity.get(category2); - if (severity1 != severity2) { - return severity2.compareTo(severity1); - } - - Integer priority1 = maxPriority.get(category1); - Integer priority2 = maxPriority.get(category2); - int compare = priority2.compareTo(priority1); - if (compare != 0) { - return compare; - } - - return category1.compareTo(category2); - } - }); - - sCategories = Collections.unmodifiableList(categories); - } - - return sCategories; - } - - /** - * 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) { - getIssues(); // Ensure initialized - return sIdToIssue.get(id); - } - - /** - * 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) { - sIdToIssue = new HashMap<String, Issue>(); - - List<Issue> issues = new ArrayList<Issue>(); - for (Detector detector : getDetectors()) { - for (Issue issue : detector.getIssues()) { - issues.add(issue); - sIdToIssue.put(issue.getId(), 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/client/api/Configuration.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Configuration.java new file mode 100644 index 0000000..2cf7598 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Configuration.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.lint.client.api; + +import com.android.tools.lint.detector.api.Context; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Location; +import com.android.tools.lint.detector.api.Severity; + +/** + * Lint configuration for an Android project such as which specific rules to include, + * which specific rules to exclude, and which specific errors to ignore. + * <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 Configuration { + /** + * 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 context the context used by the detector when the issue was found + * @param issue the issue that was found + * @param location the location of the issue + * @param message the associated user message + * @param data additional information about an issue (see + * {@link LintClient#report(Context, Issue, Location, String, Object)} for + * more information + * @return true if this issue should be suppressed + */ + public boolean isIgnored(Context context, Issue issue, Location location, + String message, Object data) { + return false; + } + + /** + * Returns false if the given issue has been disabled. This is just + * a convenience method for {@code getSeverity(issue) != Severity.IGNORE}. + * + * @param issue the issue to check + * @return false if the issue has been disabled + */ + public boolean isEnabled(Issue issue) { + return getSeverity(issue) != Severity.IGNORE; + } + + /** + * 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) { + return issue.getDefaultSeverity(); + } + + // Editing configurations + + /** + * Marks the given warning as "ignored". + * + * @param context The scanning context + * @param issue the issue to be ignored + * @param location The location to ignore the warning at + * @param message The message for the warning + * @param data The corresponding data, or null + */ + public abstract void ignore(Context context, Issue issue, Location location, + String message, Object data); + + /** + * Sets the severity to be used for this issue. + * + * @param issue the issue to set the severity for + * @param severity the severity to associate with this issue, or null to + * reset the severity to the default + */ + public abstract void setSeverity(Issue issue, Severity severity); + + // Bulk editing support + + /** + * Marks the beginning of a "bulk" editing operation with repeated calls to + * {@link #setSeverity} or {@link #ignore}. After all the values haver been + * set, the client <b>must</b> call {@link #finishBulkEditing()}. This + * allows configurations to avoid doing expensive I/O (such as writing out a + * config XML file) for each and every editing operation when they are + * applied in bulk, such as from a configuration dialog's "Apply" action. + */ + public void startBulkEditing() { + } + + /** + * Marks the end of a "bulk" editing operation, where values should be + * committed to persistent storage. See {@link #startBulkEditing()} for + * details. + */ + public void finishBulkEditing() { + } +} diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/DefaultConfiguration.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/DefaultConfiguration.java new file mode 100644 index 0000000..deb8b02 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/DefaultConfiguration.java @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.lint.client.api; + +import com.android.tools.lint.detector.api.Context; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Location; +import com.android.tools.lint.detector.api.Project; +import com.android.tools.lint.detector.api.Severity; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import java.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +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; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +/** + * Default implementation of a {@link Configuration} which reads and writes + * configuration data into {@code lint.xml} in the project directory. + * + * <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 DefaultConfiguration extends Configuration { + private final LintClient mClient; + private static final String CONFIG_FILE_NAME = "lint.xml"; //$NON-NLS-1$ + + // Lint XML File + private static final String TAG_ISSUE = "issue"; //$NON-NLS-1$ + private static final String ATTR_ID = "id"; //$NON-NLS-1$ + private static final String ATTR_SEVERITY = "severity"; //$NON-NLS-1$ + private static final String ATTR_PATH = "path"; //$NON-NLS-1$ + private static final String TAG_IGNORE = "ignore"; //$NON-NLS-1$ + + private final Configuration mParent; + protected final Project mProject; + private final File mConfigFile; + private boolean mBulkEditing; + + /** Map from id to list of project-relative paths for suppressed warnings */ + private Map<String, List<String>> mSuppressed; + + /** + * Map from id to custom {@link Severity} override + */ + private Map<String, Severity> mSeverity; + + protected DefaultConfiguration(LintClient client, Project project, Configuration parent) { + mClient = client; + mProject = project; + mParent = parent; + mConfigFile = new File(project.getDir(), CONFIG_FILE_NAME); + } + + /** + * Creates a new {@link DefaultConfiguration} + * + * @param client the client to report errors to etc + * @param project the associated project + * @param parent the parent/fallback configuration or null + * @return a new configuration + */ + public static DefaultConfiguration create(LintClient client, Project project, + Configuration parent) { + return new DefaultConfiguration(client, project, parent); + } + + @Override + public boolean isIgnored(Context context, Issue issue, Location location, String message, + Object data) { + ensureInitialized(); + + String id = issue.getId(); + List<String> paths = mSuppressed.get(id); + if (paths != null && location != null) { + File file = location.getFile(); + String relativePath = context.project.getRelativePath(file); + for (String suppressedPath : paths) { + if (suppressedPath.equals(relativePath)) { + return true; + } + } + } + + if (mParent != null) { + return mParent.isIgnored(context, issue, location, message, data); + } + + return false; + } + + @Override + public Severity getSeverity(Issue issue) { + ensureInitialized(); + + Severity severity = mSeverity.get(issue.getId()); + if (severity != null) { + return severity; + } + + if (mParent != null) { + return mParent.getSeverity(issue); + } + + if (!issue.isEnabledByDefault()) { + return Severity.IGNORE; + } + + return issue.getDefaultSeverity(); + } + + private void ensureInitialized() { + if (mSuppressed == null) { + readConfig(); + } + } + + private void readConfig() { + mSuppressed = new HashMap<String, List<String>>(); + mSeverity = new HashMap<String, Severity>(); + + if (!mConfigFile.exists()) { + return; + } + + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + BufferedInputStream input = new BufferedInputStream(new FileInputStream(mConfigFile)); + InputSource source = new InputSource(input); + factory.setNamespaceAware(false); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(source); + NodeList issues = document.getElementsByTagName(TAG_ISSUE); + for (int i = 0, count = issues.getLength(); i < count; i++) { + Node node = issues.item(i); + Element element = (Element) node; + String id = element.getAttribute(ATTR_ID); + if (id.length() == 0) { + mClient.log(null, + "Invalid lint config file: Missing required issue id attribute"); + continue; + } + + NamedNodeMap attributes = node.getAttributes(); + for (int j = 0, n = attributes.getLength(); j < n; j++) { + Node attribute = attributes.item(j); + String name = attribute.getNodeName(); + String value = attribute.getNodeValue(); + if (ATTR_ID.equals(name)) { + // already handled + } else if (ATTR_SEVERITY.equals(name)) { + for (Severity severity : Severity.values()) { + if (value.equalsIgnoreCase(severity.name())) { + mSeverity.put(id, severity); + break; + } + } + } else { + mClient.log(null, "Unexpected attribute %1$s", name); + } + } + + // Look up ignored errors + NodeList childNodes = element.getChildNodes(); + if (childNodes.getLength() > 0) { + for (int j = 0, n = childNodes.getLength(); j < n; j++) { + Node child = childNodes.item(j); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element ignore = (Element) child; + String path = ignore.getAttribute(ATTR_PATH); + if (path.length() == 0) { + mClient.log(null, "Missing required %1$s attribute under %2$s", + ATTR_PATH, id); + } else { + List<String> paths = mSuppressed.get(id); + if (paths == null) { + paths = new ArrayList<String>(n / 2 + 1); + mSuppressed.put(id, paths); + } + paths.add(path); + } + } + } + } + } + } catch (Exception e) { + mClient.log(e, null); + } + } + + private void writeConfig() { + try { + // Write the contents to a new file first such that we don't clobber the + // existing file if some I/O error occurs. + File file = new File(mConfigFile.getParentFile(), + mConfigFile.getName() + ".new"); //$NON-NLS-1$ + + Writer writer = new BufferedWriter(new FileWriter(file)); + writer.write( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //$NON-NLS-1$ + "<lint>\n"); //$NON-NLS-1$ + + if (mSuppressed.size() > 0 || mSeverity.size() > 0) { + // Process the maps in a stable sorted order such that if the + // files are checked into version control with the project, + // there are no random diffs just because hashing algorithms + // differ: + Set<String> idSet = new HashSet<String>(); + for (String id : mSuppressed.keySet()) { + idSet.add(id); + } + for (String id : mSeverity.keySet()) { + idSet.add(id); + } + List<String> ids = new ArrayList<String>(idSet); + Collections.sort(ids); + + for (String id : ids) { + writer.write(" <"); //$NON-NLS-1$ + writer.write(TAG_ISSUE); + writeAttribute(writer, ATTR_ID, id); + Severity severity = mSeverity.get(id); + if (severity != null) { + writeAttribute(writer, ATTR_SEVERITY, severity.name().toLowerCase()); + } + + List<String> paths = mSuppressed.get(id); + if (paths != null && paths.size() > 0) { + writer.write('>'); + writer.write('\n'); + // The paths are already kept in sorted order when they are modified + // by ignore(...) + for (String path : paths) { + writer.write(" <"); //$NON-NLS-1$ + writer.write(TAG_IGNORE); + writeAttribute(writer, ATTR_PATH, path); + writer.write(" />\n"); //$NON-NLS-1$ + } + writer.write(" </"); //$NON-NLS-1$ + writer.write(TAG_ISSUE); + writer.write('>'); + writer.write('\n'); + } else { + writer.write(" />\n"); //$NON-NLS-1$ + } + } + } + + writer.write("</lint>"); //$NON-NLS-1$ + writer.close(); + + // Move file into place: move current version to lint.xml~ (removing the old ~ file + // if it exists), then move the new version to lint.xml. + File oldFile = new File(mConfigFile.getParentFile(), + mConfigFile.getName() + "~"); //$NON-NLS-1$ + if (oldFile.exists()) { + oldFile.delete(); + } + if (mConfigFile.exists()) { + mConfigFile.renameTo(oldFile); + } + boolean ok = file.renameTo(mConfigFile); + if (ok && oldFile.exists()) { + oldFile.delete(); + } + } catch (Exception e) { + mClient.log(e, null); + } + } + + private static void writeAttribute(Writer writer, String name, String value) + throws IOException { + writer.write(' '); + writer.write(name); + writer.write('='); + writer.write('"'); + writer.write(value); + writer.write('"'); + } + + @Override + public void ignore(Context context, Issue issue, Location location, String message, + Object data) { + // This configuration only supports suppressing warnings on a per-file basis + if (location != null) { + ignore(issue, location.getFile()); + } + } + + /** + * Marks the given issue and file combination as being ignored. + * + * @param issue the issue to be ignored in the given file + * @param file the file to ignore the issue in + */ + public void ignore(Issue issue, File file) { + ensureInitialized(); + + String path = mProject.getRelativePath(file); + + List<String> paths = mSuppressed.get(issue.getId()); + if (paths == null) { + paths = new ArrayList<String>(); + mSuppressed.put(issue.getId(), paths); + } + paths.add(path); + + // Keep paths sorted alphabetically; makes XML output stable + Collections.sort(paths); + + if (!mBulkEditing) { + writeConfig(); + } + } + + @Override + public void setSeverity(Issue issue, Severity severity) { + ensureInitialized(); + + String id = issue.getId(); + if (severity == null) { + mSeverity.remove(id); + } else { + mSeverity.put(id, severity); + } + + if (!mBulkEditing) { + writeConfig(); + } + } + + @Override + public void startBulkEditing() { + mBulkEditing = true; + } + + @Override + public void finishBulkEditing() { + mBulkEditing = false; + writeConfig(); + } +} 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/client/api/IDomParser.java index 41e0449..d1d9461 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/api/IDomParser.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IDomParser.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.tools.lint.api; +package com.android.tools.lint.client.api; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Location; @@ -72,4 +72,10 @@ public interface IDomParser { * @return a location for the given node */ public Location getLocation(Context context, Node node); + + /** + * Dispose any data structures held for the given context. + * @param context information about the file previously parsed + */ + public void dispose(Context context); } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java new file mode 100644 index 0000000..c01f7c3 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.lint.client.api; + +import com.android.tools.lint.detector.api.Category; +import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Scope; +import com.android.tools.lint.detector.api.Severity; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Registry which provides a list of checks to be performed on an Android project */ +public abstract class IssueRegistry { + private static List<Category> sCategories; + private static Map<String, Issue> sIdToIssue; + + /** + * Issue reported by lint (not a specific detector) when it cannot even + * parse an XML file prior to analysis + */ + public static final Issue PARSER_ERROR = Issue.create( + "XmlParserError", //$NON-NLS-1$ + "Finds XML files that contain fatal parser errors", + "XML files must be parsable.", + Category.CORRECTNESS, + 10, + Severity.ERROR, + null, + Scope.RESOURCE_FILE_SCOPE); + + /** + * 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!) + */ + public abstract List<Issue> getIssues(); + + /** + * Creates a list of detectors applicable to the given cope, and with the + * given configuration. + * + * @param client the client to report errors to + * @param configuration the configuration to look up which issues are + * enabled etc from + * @param scope the scope for the analysis, to filter out detectors that + * require wider analysis than is currently being performed + * @param scopeToDetectors an optional map which (if not null) will be + * filled by this method to contain mappings from each scope to + * the applicable detectors for that scope + * @return a list of new detector instances + */ + final List<? extends Detector> createDetectors( + LintClient client, + Configuration configuration, + EnumSet<Scope> scope, + Map<Scope, List<Detector>> scopeToDetectors) { + List<Issue> issues = getIssues(); + Set<Class<? extends Detector>> detectorClasses = new HashSet<Class<? extends Detector>>(); + Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope = + new HashMap<Class<? extends Detector>, EnumSet<Scope>>(); + for (Issue issue : issues) { + Class<? extends Detector> detectorClass = issue.getDetectorClass(); + if (detectorClasses.contains(detectorClass)) { + continue; + } + + // Determine if the issue is enabled + if (!configuration.isEnabled(issue)) { + continue; + } + + // Determine if the scope matches + if (!scope.containsAll(issue.getScope())) { + continue; + } + + detectorClass = client.replaceDetector(detectorClass); + + if (scopeToDetectors != null) { + EnumSet<Scope> s = detectorToScope.get(detectorClass); + if (s == null) { + detectorToScope.put(detectorClass, issue.getScope()); + } else { + EnumSet<Scope> union = EnumSet.copyOf(s); + union.addAll(issue.getScope()); + detectorToScope.put(detectorClass, union); + } + } + + assert detectorClass != null : issue.getId(); + detectorClasses.add(detectorClass); + } + + List<Detector> detectors = new ArrayList<Detector>(detectorClasses.size()); + for (Class<? extends Detector> clz : detectorClasses) { + try { + Detector detector = clz.newInstance(); + detectors.add(detector); + + if (scopeToDetectors != null) { + EnumSet<Scope> union = detectorToScope.get(clz); + for (Scope s : union) { + List<Detector> list = scopeToDetectors.get(s); + if (list == null) { + list = new ArrayList<Detector>(); + scopeToDetectors.put(s, list); + } + list.add(detector); + } + + } + } catch (Throwable t) { + client.log(t, "Can't initialize detector %1$s", clz.getName()); //$NON-NLS-1$ + } + } + + return detectors; + } + + /** + * 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 final boolean isIssueId(String id) { + return getIssue(id) != null; + } + + /** + * Returns true if the given category is a valid category + * + * @param name the category name to be checked + * @return true if the given string is a valid category + */ + public final boolean isCategoryName(String name) { + for (Category c : getCategories()) { + if (c.getName().equals(name) || c.getFullName().equals(name)) { + return true; + } + } + + return false; + } + + /** + * Returns the available categories + * + * @return an iterator for all the categories, never null + */ + public List<Category> getCategories() { + if (sCategories == null) { + final Set<Category> categories = new HashSet<Category>(); + for (Issue issue : getIssues()) { + categories.add(issue.getCategory()); + } + List<Category> sorted = new ArrayList<Category>(categories); + Collections.sort(sorted); + sCategories = Collections.unmodifiableList(sorted); + } + + return sCategories; + } + + /** + * 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 final Issue getIssue(String id) { + if (sIdToIssue == null) { + List<Issue> issues = getIssues(); + sIdToIssue = new HashMap<String, Issue>(issues.size()); + for (Issue issue : issues) { + sIdToIssue.put(issue.getId(), issue); + } + } + return sIdToIssue.get(id); + } +} 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/client/api/Lint.java index f6be5d2..4d8671a 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/api/Lint.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java @@ -14,12 +14,15 @@ * limitations under the License. */ -package com.android.tools.lint.api; +package com.android.tools.lint.client.api; import com.android.resources.ResourceFolderType; +import com.android.tools.lint.client.api.LintListener.EventType; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LintUtils; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Project; import com.android.tools.lint.detector.api.ResourceXmlDetector; @@ -44,27 +47,24 @@ public class Lint { private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; //$NON-NLS-1$ private static final String RES_FOLDER_NAME = "res"; //$NON-NLS-1$ - private final ToolContext mToolContext; + private final LintClient mClient; private volatile boolean mCanceled; - private DetectorRegistry mRegistry; + private IssueRegistry mRegistry; private EnumSet<Scope> mScope; - private Map<Scope, List<Detector>> mApplicableDetectors = new HashMap<Scope, List<Detector>>(); + private List<? extends Detector> mApplicableDetectors; + private Map<Scope, List<Detector>> mScopeDetectors; + private List<LintListener> mListeners; /** * 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. If null, the scope will be inferred from the files - * passed to {@link #analyze(List)}. + * @param registry The registry containing issues to be checked + * @param client the tool wrapping the analyzer, such as an IDE or a CLI */ - public Lint(DetectorRegistry registry, ToolContext toolContext, EnumSet<Scope> scope) { - assert toolContext != null; + public Lint(IssueRegistry registry, LintClient client) { + assert client != null; mRegistry = registry; - mToolContext = toolContext; - mScope = scope; + mClient = new LintClientWrapper(client); } /** Cancels the current lint run as soon as possible */ @@ -74,14 +74,19 @@ public class Lint { /** * Analyze the given file (which can point to an Android project). Issues found - * are reported to the associated {@link ToolContext}. + * are reported to the associated {@link LintClient}. * * @param files the files and directories to be analyzed + * @param scope the scope of the analysis; detectors with a wider scope will + * not be run. If null, the scope will be inferred from the files. */ - public void analyze(List<File> files) { + public void analyze(List<File> files, EnumSet<Scope> scope) { + mCanceled = false; + mScope = scope; + Collection<Project> projects = computeProjects(files); if (projects.size() == 0) { - mToolContext.log(null, "No projects found for %1$s", files.toString()); + mClient.log(null, "No projects found for %1$s", files.toString()); return; } if (mCanceled) { @@ -119,52 +124,28 @@ public class Lint { } } - List<? extends Detector> availableChecks = mRegistry.getDetectors(); - EnumSet<Scope> missingScopes = EnumSet.complementOf(mScope); - - // Filter out disabled checks - List<Detector> checks = new ArrayList<Detector>(availableChecks.size()); - for (Detector detector : availableChecks) { - boolean hasValidScope = false; - for (Issue issue : detector.getIssues()) { - if (mScope.containsAll(issue.getScope())) { - hasValidScope = true; - break; - } - } - if (!hasValidScope) { - continue; - } - // A detector is enabled if at least one of its issues is enabled - EnumSet<Scope> scope = EnumSet.noneOf(Scope.class); - for (Issue issue : detector.getIssues()) { - if (mToolContext.isEnabled(issue)) { - scope.addAll(issue.getScope()); - } - } - // Only run those detectors whose scope are matched by the current analysis context: - scope.removeAll(missingScopes); - if (scope.size() > 0) { - checks.add(detector); - for (Scope s : scope) { - List<Detector> detectors = mApplicableDetectors.get(s); - if (detectors == null) { - detectors = new ArrayList<Detector>(); - mApplicableDetectors.put(s, detectors); - } - detectors.add(detector); - } - } - } - - validateScopeList(); + fireEvent(EventType.STARTING, null); for (Project project : projects) { - checkProject(project, checks); + // The set of available detectors varies between projects + computeDetectors(project); + + checkProject(project); if (mCanceled) { - return; + break; } } + + fireEvent(mCanceled ? EventType.CANCELED : EventType.COMPLETED, null); + } + + private void computeDetectors(Project project) { + Configuration configuration = project.getConfiguration(); + mScopeDetectors = new HashMap<Scope, List<Detector>>(); + mApplicableDetectors = mRegistry.createDetectors(mClient, configuration, + mScope, mScopeDetectors); + + validateScopeList(); } /** Development diagnostics only, run with assertions on */ @@ -173,33 +154,33 @@ public class Lint { boolean assertionsEnabled = false; assert assertionsEnabled = true; // Intentional side-effect if (assertionsEnabled) { - List<Detector> resourceFileDetectors = mApplicableDetectors.get(Scope.RESOURCE_FILE); + List<Detector> resourceFileDetectors = mScopeDetectors.get(Scope.RESOURCE_FILE); if (resourceFileDetectors != null) { for (Detector detector : resourceFileDetectors) { assert detector instanceof ResourceXmlDetector : detector; } } - List<Detector> manifestDetectors = mApplicableDetectors.get(Scope.MANIFEST); + List<Detector> manifestDetectors = mScopeDetectors.get(Scope.MANIFEST); if (manifestDetectors != null) { for (Detector detector : manifestDetectors) { assert detector instanceof Detector.XmlScanner : detector; } } - List<Detector> javaCodeDetectors = mApplicableDetectors.get(Scope.ALL_JAVA_FILES); + List<Detector> javaCodeDetectors = mScopeDetectors.get(Scope.ALL_JAVA_FILES); if (javaCodeDetectors != null) { for (Detector detector : javaCodeDetectors) { assert detector instanceof Detector.JavaScanner : detector; } } - List<Detector> javaFileDetectors = mApplicableDetectors.get(Scope.JAVA_FILE); + List<Detector> javaFileDetectors = mScopeDetectors.get(Scope.JAVA_FILE); if (javaFileDetectors != null) { for (Detector detector : javaFileDetectors) { assert detector instanceof Detector.JavaScanner : detector; } } - List<Detector> classDetectors = mApplicableDetectors.get(Scope.CLASS_FILE); + List<Detector> classDetectors = mScopeDetectors.get(Scope.CLASS_FILE); if (classDetectors != null) { for (Detector detector : classDetectors) { assert detector instanceof Detector.ClassScanner : detector; @@ -212,7 +193,8 @@ public class Lint { File projectDir, File rootDir) { Project project = fileToProject.get(projectDir); if (project == null) { - project = new Project(mToolContext, projectDir, rootDir); + project = new Project(mClient, projectDir, rootDir); + project.setConfiguration(mClient.getConfiguration(project)); } fileToProject.put(file, project); } @@ -304,11 +286,14 @@ public class Lint { return new File(dir, ANDROID_MANIFEST_XML).exists(); } - private void checkProject(Project project, List<Detector> checks) { + private void checkProject(Project project) { + File projectDir = project.getDir(); - Context projectContext = new Context(mToolContext, project, projectDir, mScope); - for (Detector check : checks) { + Context projectContext = new Context(mClient, project, projectDir, mScope); + fireEvent(EventType.SCANNING_PROJECT, projectContext); + + for (Detector check : mApplicableDetectors) { check.beforeCheckProject(projectContext); if (mCanceled) { return; @@ -317,7 +302,7 @@ public class Lint { runFileDetectors(project, projectDir); - for (Detector check : checks) { + for (Detector check : mApplicableDetectors) { check.afterCheckProject(projectContext); if (mCanceled) { return; @@ -325,12 +310,12 @@ public class Lint { } if (mCanceled) { - mToolContext.report( + mClient.report( projectContext, // Must provide an issue since API guarantees that the issue parameter // is valid - Issue.create("dummy", "", "", "", 0, Severity.INFORMATIONAL, //$NON-NLS-1$ - EnumSet.noneOf(Scope.class)), + Issue.create("Lint", "", "", Category.PERFORMANCE, 0, Severity.INFORMATIONAL, //$NON-NLS-1$ + null, EnumSet.noneOf(Scope.class)), null /*range*/, "Lint canceled by user", null); } @@ -339,13 +324,14 @@ public class Lint { private void runFileDetectors(Project project, File projectDir) { // Look up manifest information if (mScope.contains(Scope.MANIFEST)) { - List<Detector> detectors = mApplicableDetectors.get(Scope.MANIFEST); + List<Detector> detectors = mScopeDetectors.get(Scope.MANIFEST); if (detectors != null) { File file = new File(project.getDir(), ANDROID_MANIFEST_XML); if (file.exists()) { - Context context = new Context(mToolContext, project, file, mScope); + Context context = new Context(mClient, project, file, mScope); context.location = new Location(file, null, null); - XmlVisitor v = new XmlVisitor(mToolContext.getParser(), detectors); + XmlVisitor v = new XmlVisitor(mClient.getParser(), detectors); + fireEvent(EventType.SCANNING_FILE, context); v.visitFile(context, file); } } @@ -354,8 +340,8 @@ public class Lint { // Process both Scope.RESOURCE_FILE and Scope.ALL_RESOURCE_FILES detectors together // in a single pass through the resource directories. if (mScope.contains(Scope.ALL_RESOURCE_FILES) || mScope.contains(Scope.RESOURCE_FILE)) { - List<Detector> checks = union(mApplicableDetectors.get(Scope.RESOURCE_FILE), - mApplicableDetectors.get(Scope.ALL_RESOURCE_FILES)); + List<Detector> checks = union(mScopeDetectors.get(Scope.RESOURCE_FILE), + mScopeDetectors.get(Scope.ALL_RESOURCE_FILES)); if (checks.size() > 0) { List<ResourceXmlDetector> xmlDetectors = new ArrayList<ResourceXmlDetector>(checks.size()); @@ -377,29 +363,42 @@ public class Lint { } } + if (mCanceled) { + return; + } + if (mScope.contains(Scope.JAVA_FILE) || mScope.contains(Scope.ALL_JAVA_FILES)) { - List<Detector> checks = union(mApplicableDetectors.get(Scope.JAVA_FILE), - mApplicableDetectors.get(Scope.ALL_JAVA_FILES)); + List<Detector> checks = union(mScopeDetectors.get(Scope.JAVA_FILE), + mScopeDetectors.get(Scope.ALL_JAVA_FILES)); if (checks.size() > 0) { List<File> sourceFolders = project.getJavaSourceFolders(); checkJava(project, sourceFolders, checks); } } + if (mCanceled) { + return; + } + if (mScope.contains(Scope.CLASS_FILE)) { - List<Detector> detectors = mApplicableDetectors.get(Scope.CLASS_FILE); + List<Detector> detectors = mScopeDetectors.get(Scope.CLASS_FILE); if (detectors != null) { List<File> binFolders = project.getJavaClassFolders(); checkClasses(project, binFolders, detectors); } } + if (mCanceled) { + return; + } + if (mScope.contains(Scope.PROGUARD)) { - List<Detector> detectors = mApplicableDetectors.get(Scope.PROGUARD); + List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD); if (detectors != null) { File file = new File(project.getDir(), PROGUARD_CFG); if (file.exists()) { - Context context = new Context(mToolContext, project, file, mScope); + Context context = new Context(mClient, project, file, mScope); + fireEvent(EventType.SCANNING_FILE, context); context.location = new Location(file, null, null); for (Detector detector : detectors) { if (detector.appliesTo(context, file)) { @@ -430,7 +429,8 @@ public class Lint { } private void checkClasses(Project project, List<File> binFolders, List<Detector> checks) { - Context context = new Context(mToolContext, project, project.getDir(), mScope); + Context context = new Context(mClient, project, project.getDir(), mScope); + fireEvent(EventType.SCANNING_FILE, context); for (Detector detector : checks) { ((Detector.ClassScanner) detector).checkJavaClasses(context); @@ -441,7 +441,8 @@ public class Lint { } private void checkJava(Project project, List<File> sourceFolders, List<Detector> checks) { - Context context = new Context(mToolContext, project, project.getDir(), mScope); + Context context = new Context(mClient, project, project.getDir(), mScope); + fireEvent(EventType.SCANNING_FILE, context); for (Detector detector : checks) { ((Detector.JavaScanner) detector).checkJavaSources(context, sourceFolders); @@ -479,7 +480,7 @@ public class Lint { return null; } - mCurrentVisitor = new XmlVisitor(mToolContext.getParser(), applicableChecks); + mCurrentVisitor = new XmlVisitor(mClient.getParser(), applicableChecks); } return mCurrentVisitor; @@ -517,9 +518,10 @@ public class Lint { XmlVisitor visitor = getVisitor(type, checks); if (visitor != null) { // if not, there are no applicable rules in this folder for (File file : xmlFiles) { - if (ResourceXmlDetector.isXmlFile(file)) { - Context context = new Context(mToolContext, project, file, + if (LintUtils.isXmlFile(file)) { + Context context = new Context(mClient, project, file, mScope); + fireEvent(EventType.SCANNING_FILE, context); visitor.visitFile(context, file); if (mCanceled) { return; @@ -544,18 +546,19 @@ public class Lint { // Yes checkResFolder(project, file, xmlDetectors); } else { - mToolContext.log(null, "Unexpected folder %1$s; should be project, " + + mClient.log(null, "Unexpected folder %1$s; should be project, " + "\"res\" folder or resource folder", file.getPath()); continue; } - } else if (file.isFile() && ResourceXmlDetector.isXmlFile(file)) { + } else if (file.isFile() && LintUtils.isXmlFile(file)) { // Yes, find out its resource type String folderName = file.getParentFile().getName(); ResourceFolderType type = ResourceFolderType.getFolderType(folderName); if (type != null) { XmlVisitor visitor = getVisitor(type, xmlDetectors); if (visitor != null) { - Context context = new Context(mToolContext, project, file, mScope); + Context context = new Context(mClient, project, file, mScope); + fireEvent(EventType.SCANNING_FILE, context); visitor.visitFile(context, file); } } @@ -564,12 +567,107 @@ public class Lint { } /** - * Returns the associated tool context for the surrounding tool that is - * embedding lint analysis + * Adds a listener to be notified of lint progress * - * @return the surrounding tool context + * @param listener the listener to be added */ - public ToolContext getToolContext() { - return mToolContext; + public void addLintListener(LintListener listener) { + if (mListeners == null) { + mListeners = new ArrayList<LintListener>(1); + } + mListeners.add(listener); + } + + /** + * Removes a listener such that it is no longer notified of progress + * + * @param listener the listener to be removed + */ + public void removeLintListener(LintListener listener) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + mListeners = null; + } + } + + /** Notifies listeners, if any, that the given event has occurred */ + private void fireEvent(LintListener.EventType type, Context context) { + if (mListeners != null) { + for (int i = 0, n = mListeners.size(); i < n; i++) { + LintListener listener = mListeners.get(i); + listener.update(type, context); + } + } + } + + /** + * Wrapper around the lint client. This sits in the middle between a + * detector calling for example + * {@link LintClient#report(Context, Issue, Location, String, Object)} and + * the actual embedding tool, and performs filtering etc such that detectors + * and lint clients don't have to make sure they check for ignored issues or + * filtered out warnings. + */ + private static class LintClientWrapper extends LintClient { + private LintClient mDelegate; + + public LintClientWrapper(LintClient delegate) { + mDelegate = delegate; + } + + @Override + public void report(Context context, Issue issue, Location location, String message, + Object data) { + Configuration configuration = context.configuration; + if (!configuration.isEnabled(issue)) { + mDelegate.log(null, "Incorrect detector reported disabled issue %1$s", + issue.toString()); + return; + } + + if (configuration.isIgnored(context, issue, location, message, data)) { + return; + } + + Severity severity = configuration.getSeverity(issue); + if (severity == Severity.IGNORE) { + return; + } + + mDelegate.report(context, issue, location, message, data); + } + + // Everything else just delegates to the embedding lint client + + @Override + public Configuration getConfiguration(Project project) { + return mDelegate.getConfiguration(project); + } + + + @Override + public void log(Throwable exception, String format, Object... args) { + mDelegate.log(exception, format, args); + } + + @Override + public IDomParser getParser() { + return mDelegate.getParser(); + } + + @Override + public String readFile(File file) { + return mDelegate.readFile(file); + } + + @Override + public List<File> getJavaSourceFolders(Project project) { + return mDelegate.getJavaSourceFolders(project); + } + + @Override + public List<File> getJavaClassFolders(Project project) { + return mDelegate.getJavaClassFolders(project); + } } } 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/client/api/LintClient.java index 9e7441c..cae89b6 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/api/ToolContext.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.tools.lint.api; +package com.android.tools.lint.client.api; 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.Project; -import com.android.tools.lint.detector.api.Severity; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -43,9 +43,27 @@ import javax.xml.parsers.DocumentBuilderFactory; * <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 ToolContext { +public abstract class LintClient { /** - * Report the given issue. + * Returns a configuration for use by the given project. The configuration + * provides information about which issues are enabled, any customizations + * to the severity of an issue, etc. + * <p> + * By default this method returns a {@link DefaultConfiguration}. + * + * @param project the project to obtain a configuration for + * @return a configuration, never null. + */ + public Configuration getConfiguration(Project project) { + return DefaultConfiguration.create(this, project, null); + } + + /** + * Report the given issue. This method will only be called if the configuration + * provided by {@link #getConfiguration(Project)} has reported the corresponding + * issue as enabled and has not filtered out the issue with its + * {@link Configuration#ignore(Context, Issue, Location, String, Object)} method. + * <p> * * @param context the context used by the detector when the issue was found * @param issue the issue that was found @@ -62,27 +80,6 @@ public abstract class ToolContext { Object data); /** - * 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 context the context used by the detector when the issue was found - * @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 - * @param data additional information about an issue (see - * {@link #report(Context, Issue, Location, String, Object)} for - * more information - * @return true if this issue should be suppressed - */ - public boolean isSuppressed(Context context, Issue issue, Location location, - String message, Severity severity, Object data) { - return false; - } - - /** * Send an exception to the log * * @param exception the exception, possibly null @@ -92,33 +89,23 @@ public abstract class ToolContext { public abstract void log(Throwable exception, String format, Object... args); /** - * Returns a {@link IDomParser} to use to parse XML - * - * @return a new {@link IDomParser} - */ - public abstract IDomParser getParser(); - - /** - * Returns false if the given issue has been disabled + * Returns an optimal detector, if applicable. By default, just returns the + * original detector, but tools can replace detectors using this hook with a version + * that takes advantage of native capabilities of the tool. * - * @param issue the issue to check - * @return false if the issue has been disabled + * @param detectorClass the class of the detector to be replaced + * @return the new detector class, or just the original detector (not null) */ - public boolean isEnabled(Issue issue) { - return true; + public Class<? extends Detector> replaceDetector(Class<? extends Detector> detectorClass) { + return detectorClass; } /** - * 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). + * Returns a {@link IDomParser} to use to parse XML * - * @param issue the issue to look up the severity from - * @return the severity use for issues for the given detector + * @return a new {@link IDomParser} */ - public Severity getSeverity(Issue issue) { - return issue.getDefaultSeverity(); - } + public abstract IDomParser getParser(); /** * Reads the given text file and returns the content as a string diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintListener.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintListener.java new file mode 100644 index 0000000..1852f87 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintListener.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.lint.client.api; + +import com.android.tools.lint.detector.api.Context; + +/** Interface implemented by listeners to be notified of lint events */ +public interface LintListener { + /** The various types of events provided to lint listeners */ + public enum EventType { + /** A lint check is about to begin */ + STARTING, + /** Lint is about to check the given project, see {@link Context#project} */ + SCANNING_PROJECT, + /** Lint is about to check the given file, see {@link Context#file} */ + SCANNING_FILE, + /** The lint check was canceled */ + CANCELED, + /** The lint check is done */ + COMPLETED, + }; + + /** + * Notifies listeners that the event of the given type has occurred. Additional + * information, such as the file being scanned, or the project being scanned, + * is available in the {@link Context} object (except for the {@link EventType#STARTING}, + * {@link EventType#CANCELED} or {@link EventType#COMPLETED} events which are fired + * outside of project contexts.) + * + * @param type the type of event that occurred + * @param context the context providing additional information + */ + public void update(EventType type, Context context); +} 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/client/api/XmlVisitor.java index b469f9a..2035bca 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/api/XmlVisitor.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java @@ -14,16 +14,12 @@ * limitations under the License. */ -package com.android.tools.lint.api; +package com.android.tools.lint.client.api; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Detector.XmlScanner; -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 com.android.tools.lint.detector.api.LintUtils; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -34,7 +30,6 @@ import org.w3c.dom.NodeList; import java.io.File; import java.util.ArrayList; import java.util.Collection; -import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -118,45 +113,43 @@ class XmlVisitor { } void visitFile(Context context, File file) { - assert ResourceXmlDetector.isXmlFile(file); + assert LintUtils.isXmlFile(file); context.location = null; context.parser = mParser; - if (context.document == null) { - context.document = mParser.parse(context); + try { if (context.document == null) { - context.toolContext.report( - context, - // Must provide an issue since API guarantees that the issue parameter - // is valid - Issue.create("dummy", "", "", "", 0, Severity.ERROR, //$NON-NLS-1$ - EnumSet.noneOf(Scope.class)), - new Location(file, null, null), - "Skipped file because it contains parsing errors", null); - return; - } - if (context.document.getDocumentElement() == null) { - // Ignore empty documents - return; + context.document = mParser.parse(context); + if (context.document == null) { + context.client.log( + null, "Skipped file because it contains parsing errors"); + return; + } + if (context.document.getDocumentElement() == null) { + // Ignore empty documents + return; + } } - } - for (Detector check : mAllDetectors) { - check.beforeCheckFile(context); - } + for (Detector check : mAllDetectors) { + check.beforeCheckFile(context); + } - for (Detector.XmlScanner check : mDocumentDetectors) { - check.visitDocument(context, context.document); - } + for (Detector.XmlScanner 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()); - } + if (mElementToCheck.size() > 0 || mAttributeToCheck.size() > 0 + || mAllAttributeDetectors.size() > 0 || mAllElementDetectors.size() > 0) { + visitElement(context, context.document.getDocumentElement()); + } - for (Detector check : mAllDetectors) { - check.afterCheckFile(context); + for (Detector check : mAllDetectors) { + check.afterCheckFile(context); + } + } finally { + mParser.dispose(context); } } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Category.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Category.java new file mode 100644 index 0000000..41eac96 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Category.java @@ -0,0 +1,139 @@ +/* + * 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; + +/** + * A category is a container for related issues. + * <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 Category implements Comparable<Category> { + private final String mName; + private final String mExplanation; + private final int mPriority; + private final Category mParent; + + /** + * Creates a new {@link Category}. + * + * @param parent the name of a parent category, or null + * @param name the name of the category + * @param explanation an optional explanation of the category + * @param priority a sorting priority, with higher being more important + */ + private Category(Category parent, String name, String explanation, int priority) { + mParent = parent; + mName = name; + mExplanation = explanation; + mPriority = priority; + } + + /** + * Creates a new top level {@link Category} with the given sorting priority. + * + * @param name the name of the category + * @param priority a sorting priority, with higher being more important + * @return a new category + */ + public static Category create(String name, int priority) { + return new Category(null, name, null, priority); + } + + /** + * Creates a new top level {@link Category} with the given sorting priority. + * + * @param parent the name of a parent category, or null + * @param name the name of the category + * @param explanation an optional explanation of the category + * @param priority a sorting priority, with higher being more important + * @return a new category + */ + public static Category create(Category parent, String name, String explanation, int priority) { + return new Category(parent, name, null, priority); + } + + /** + * Returns the parent category, or null if this is a top level category + * + * @return the parent category, or null if this is a top level category + */ + public Category getParent() { + return mParent; + } + + /** + * Returns the name of this category + * + * @return the name of this category + */ + public String getName() { + return mName; + } + + /** + * Returns an explanation for this category, or null + * + * @return an explanation for this category, or null + */ + public String getExplanation() { + return mExplanation; + } + + /** + * Returns a full name for this category. For a top level category, this is just + * the {@link #getName()} value, but for nested categories it will include the parent + * names as well. + * + * @return a full name for this category + */ + public String getFullName() { + if (mParent != null) { + return mParent.getFullName() + ':' + mName; + } else { + return mName; + } + } + + public int compareTo(Category other) { + if (other.mPriority == mPriority) { + if (mParent == other) { + return 1; + } else if (other.mParent == this) { + return -1; + } + } + return other.mPriority - mPriority; + } + + /** Issues related to correctness */ + public static final Category CORRECTNESS = Category.create("Correctness", 10); + /** Issues related to security */ + public static final Category SECURITY = Category.create("Security", 9); + /** Issues related to performance */ + public static final Category PERFORMANCE = Category.create("Performance", 8); + /** Issues related to usability */ + public static final Category USABILITY = Category.create("Usability", 7); + /** Issues related to accessibility */ + public static final Category A11Y = Category.create("Accessibility", 6); + /** Issues related to internationalization */ + public static final Category I18N = Category.create("Internationalization", 5); + + // Sub categories + /** Issues related to icons */ + public static final Category ICONS = Category.create(USABILITY, "Icons", null, 7); +} 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 index 59345c8..d80144a 100644 --- 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 @@ -16,8 +16,9 @@ package com.android.tools.lint.detector.api; -import com.android.tools.lint.api.IDomParser; -import com.android.tools.lint.api.ToolContext; +import com.android.tools.lint.client.api.Configuration; +import com.android.tools.lint.client.api.IDomParser; +import com.android.tools.lint.client.api.LintClient; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -44,7 +45,8 @@ import java.util.concurrent.atomic.AtomicBoolean; public class Context { public final Project project; public final File file; - public final ToolContext toolContext; + public final LintClient client; + public final Configuration configuration; public final EnumSet<Scope> scope; public Document document; public Location location; @@ -60,11 +62,14 @@ public class Context { private Map<String, Object> properties; - public Context(ToolContext toolContext, Project project, File file, EnumSet<Scope> scope) { - this.toolContext = toolContext; + public Context(LintClient client, Project project, File file, + EnumSet<Scope> scope) { + this.client = client; this.project = project; this.file = file; this.scope = scope; + + this.configuration = project.getConfiguration(); } public Location getLocation(Node node) { @@ -87,7 +92,7 @@ public class Context { // TODO: This should be delegated to the tool context! public String getContents() { if (contents == null) { - contents = toolContext.readFile(file); + contents = client.readFile(file); } return contents; diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java index 539a454..b48af6a 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java @@ -19,8 +19,6 @@ package com.android.tools.lint.detector.api; 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; @@ -124,7 +122,6 @@ public abstract class Detector { /** Concrete implementation of a detector that is a {@link Detector.XmlScanner} */ public static abstract class XmlDetectorAdapter extends Detector implements Detector.XmlScanner { - private static final String XML_SUFFIX = ".xml"; //$NON-NLS-1$ @Override public void run(Context context) { @@ -164,67 +161,11 @@ public abstract class Detector { 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; - } } /** - * Returns a list of issues detected by this detector. - * - * @return a list of issues detected by this detector, never null. - */ - public abstract Issue[] getIssues(); - - /** * Runs the detector + * * @param context the context describing the work to be done */ public abstract void run(Context context); @@ -232,7 +173,12 @@ public abstract class Detector { /** Returns true if this detector applies to the given file */ public abstract boolean appliesTo(Context context, File file); - /** Analysis is about to begin, perform any setup steps. */ + /** + * Analysis is about to begin, perform any setup steps. + * <p> + * TODO: Rename "check" to "scan" here? beforeScanProject, beforeScanFile + * etc? + */ public void beforeCheckProject(Context context) { } @@ -260,15 +206,4 @@ public abstract class Detector { * @return the expected speed of this detector */ public abstract Speed getSpeed(); - - /** Namespace used in XML files for Android attributes */ - protected final static String ANDROID_URI = - "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ - - 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_SECURITY = "Security"; } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java index 3308bd8..5320362 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java @@ -16,8 +16,9 @@ package com.android.tools.lint.detector.api; -import java.util.EnumSet; +import com.android.tools.lint.client.api.Configuration; +import java.util.EnumSet; /** @@ -36,16 +37,17 @@ 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 Category mCategory; private final int mPriority; private final Severity mSeverity; private String mMoreInfoUrl; private boolean mEnabledByDefault = true; private final EnumSet<Scope> mScope; + private final Class<? extends Detector> mClass; // Use factory methods - private Issue(String id, String description, String explanation, String category, int priority, - Severity severity, EnumSet<Scope> scope) { + private Issue(String id, String description, String explanation, Category category, int priority, + Severity severity, Class<? extends Detector> detectorClass, EnumSet<Scope> scope) { super(); mId = id; mDescription = description; @@ -53,6 +55,7 @@ public final class Issue implements Comparable<Issue> { mCategory = category; mPriority = priority; mSeverity = severity; + mClass = detectorClass; mScope = scope; } @@ -67,12 +70,15 @@ public final class Issue implements Comparable<Issue> { * @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 detectorClass the class of the detector to find this issue * @param scope the scope of files required to analyze this issue * @return a new {@link Issue} */ - public static Issue create(String id, String description, String explanation, String category, - int priority, Severity severity, EnumSet<Scope> scope) { - return new Issue(id, description, explanation, category, priority, severity, scope); + public static Issue create(String id, String description, String explanation, + Category category, int priority, Severity severity, + Class<? extends Detector> detectorClass, EnumSet<Scope> scope) { + return new Issue(id, description, explanation, category, priority, severity, + detectorClass, scope); } /** @@ -112,7 +118,7 @@ public final class Issue implements Comparable<Issue> { * * @return the category, or null if no category has been assigned */ - public String getCategory() { + public Category getCategory() { return mCategory; } @@ -129,6 +135,15 @@ public final class Issue implements Comparable<Issue> { /** * Returns the default severity of the issues found by this detector (some * tools may allow the user to specify custom severities for detectors). + * <p> + * Note that even though the normal way for an issue to be disabled is for + * the {@link Configuration} to return {@link Severity#IGNORE}, there is a + * {@link #isEnabledByDefault()} method which can be used to turn off issues + * by default. This is done rather than just having the severity as the only + * attribute on the issue such that an issue can be configured with an + * appropriate severity (such as {@link Severity#ERROR}) even when issues + * are disabled by default for example because they are experimental or not + * yet stable. * * @return the severity of the issues found by this detector */ @@ -199,4 +214,18 @@ public final class Issue implements Comparable<Issue> { mEnabledByDefault = enabledByDefault; return this; } + + /** + * Returns the class of the detector to use to find this issue + * + * @return the class of the detector to use to find this issue + */ + public Class<? extends Detector> getDetectorClass() { + return mClass; + } + + @Override + public String toString() { + return mId; + } } 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 index b2eaf10..c18c762 100644 --- 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 @@ -16,6 +16,17 @@ package com.android.tools.lint.detector.api; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_WIDTH; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_PADDING; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_PADDING_BOTTOM; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_PADDING_LEFT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_PADDING_RIGHT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_PADDING_TOP; +import static com.android.tools.lint.detector.api.LintConstants.VALUE_FILL_PARENT; +import static com.android.tools.lint.detector.api.LintConstants.VALUE_MATCH_PARENT; + import com.android.resources.ResourceFolderType; import org.w3c.dom.Element; @@ -28,57 +39,6 @@ import org.w3c.dom.Element; * 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 EDIT_TEXT = "EditText"; //$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 IMAGE_BUTTON = "ImageButton"; //$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_TEXT = "text"; //$NON-NLS-1$ - protected static final String ATTR_LABEL = "label"; //$NON-NLS-1$ - protected static final String ATTR_HINT = "hint"; //$NON-NLS-1$ - protected static final String ATTR_PROMPT = "prompt"; //$NON-NLS-1$ - protected static final String ATTR_INPUT_TYPE = "inputType"; //$NON-NLS-1$ - protected static final String ATTR_INPUT_METHOD = "inputMethod"; //$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$ - protected static final String ATTR_LAYOUT_ROW = "layout_row"; //$NON-NLS-1$ - protected static final String ATTR_LAYOUT_ROW_SPAN = "layout_rowSpan";//$NON-NLS-1$ - protected static final String ATTR_LAYOUT_COLUMN = "layout_column"; //$NON-NLS-1$ - protected static final String ATTR_ROW_COUNT = "rowCount"; //$NON-NLS-1$ - protected static final String ATTR_COLUMN_COUNT = "columnCount"; //$NON-NLS-1$ - protected static final String ATTR_LAYOUT_COLUMN_SPAN = "layout_columnSpan"; //$NON-NLS-1$ - protected static final String ATTR_CONTENT_DESCRIPTION = "contentDescription"; //$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; diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java new file mode 100644 index 0000000..b0ff394 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.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.detector.api; + +/** + * Constants used by the various detectors, defined in one place + */ +@SuppressWarnings("javadoc") // Not documenting each and every obvious constant +public class LintConstants { + /** Namespace used in XML files for Android attributes */ + public static final String ANDROID_URI = + "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ + + // Tags: Manifest + public static final String TAG_SERVICE = "service"; //$NON-NLS-1$ + public static final String TAG_USES_PERMISSION = "uses-permission";//$NON-NLS-1$ + public static final String TAG_APPLICATION = "application"; //$NON-NLS-1$ + public static final String TAG_INTENT_FILTER = " intent-filter"; //$NON-NLS-1$ + public static final String TAG_USES_SDK = "uses-sdk"; //$NON-NLS-1$ + + // Tags: Layouts + public static final String FRAME_LAYOUT = "FrameLayout"; //$NON-NLS-1$ + public static final String LINEAR_LAYOUT = "LinearLayout"; //$NON-NLS-1$ + public static final String SCROLL_VIEW = "ScrollView"; //$NON-NLS-1$ + public static final String GALLERY = "Gallery"; //$NON-NLS-1$ + public static final String GRID_VIEW = "GridView"; //$NON-NLS-1$ + public static final String EDIT_TEXT = "EditText"; //$NON-NLS-1$ + public static final String LIST_VIEW = "ListView"; //$NON-NLS-1$ + public static final String TEXT_VIEW = "TextView"; //$NON-NLS-1$ + public static final String IMAGE_VIEW = "ImageView"; //$NON-NLS-1$ + public static final String IMAGE_BUTTON = "ImageButton"; //$NON-NLS-1$ + public static final String INCLUDE = "include"; //$NON-NLS-1$ + public static final String MERGE = "merge"; //$NON-NLS-1$ + public static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; //$NON-NLS-1$ + + // Attributes: Manifest + public static final String ATTR_EXPORTED = "exported"; //$NON-NLS-1$ + public static final String ATTR_PERMISSION = "permission"; //$NON-NLS-1$ + public static final String ATTR_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-1$ + public static final String ATTR_TARGET_SDK_VERSION = "targetSdkVersion"; //$NON-NLS-1$ + public static final String ATTR_ICON = "icon"; //$NON-NLS-1$ + + // Attributes: Layout + public static final String ATTR_ID = "id"; //$NON-NLS-1$ + public static final String ATTR_TEXT = "text"; //$NON-NLS-1$ + public static final String ATTR_LABEL = "label"; //$NON-NLS-1$ + public static final String ATTR_HINT = "hint"; //$NON-NLS-1$ + public static final String ATTR_PROMPT = "prompt"; //$NON-NLS-1$ + public static final String ATTR_INPUT_TYPE = "inputType"; //$NON-NLS-1$ + public static final String ATTR_INPUT_METHOD = "inputMethod"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_GRAVITY = "layout_gravity"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_WIDTH = "layout_width"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_HEIGHT = "layout_height"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_WEIGHT = "layout_weight"; //$NON-NLS-1$ + public static final String ATTR_PADDING = "padding"; //$NON-NLS-1$ + public static final String ATTR_PADDING_BOTTOM = "paddingBottom"; //$NON-NLS-1$ + public static final String ATTR_PADDING_TOP = "paddingTop"; //$NON-NLS-1$ + public static final String ATTR_PADDING_RIGHT = "paddingRight"; //$NON-NLS-1$ + public static final String ATTR_PADDING_LEFT = "paddingLeft"; //$NON-NLS-1$ + public static final String ATTR_FOREGROUND = "foreground"; //$NON-NLS-1$ + public static final String ATTR_BACKGROUND = "background"; //$NON-NLS-1$ + public static final String ATTR_ORIENTATION = "orientation"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT = "layout"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ROW = "layout_row"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ROW_SPAN = "layout_rowSpan";//$NON-NLS-1$ + public static final String ATTR_LAYOUT_COLUMN = "layout_column"; //$NON-NLS-1$ + public static final String ATTR_ROW_COUNT = "rowCount"; //$NON-NLS-1$ + public static final String ATTR_COLUMN_COUNT = "columnCount"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_COLUMN_SPAN = "layout_columnSpan"; //$NON-NLS-1$ + public static final String ATTR_CONTENT_DESCRIPTION = "contentDescription"; //$NON-NLS-1$ + + // Attribute values + public static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$ + public static final String VALUE_MATCH_PARENT = "match_parent"; //$NON-NLS-1$ + public static final String VALUE_VERTICAL = "vertical"; //$NON-NLS-1$ + + // Filenames and folder names + public static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; //$NON-NLS-1$ + public static final String RES_FOLDER = "res"; //$NON-NLS-1$ + public static final String DOT_XML = ".xml"; //$NON-NLS-1$ + public static final String DOT_GIF = ".gif"; //$NON-NLS-1$ + public static final String DOT_JPG = ".jpg"; //$NON-NLS-1$ + public static final String DOT_PNG = ".png"; //$NON-NLS-1$ + public static final String DOT_9PNG = ".9.png"; //$NON-NLS-1$ + public static final String DRAWABLE_FOLDER = "drawable"; //$NON-NLS-1$ + public static final String DRAWABLE_XHDPI = "drawable-xhdpi"; //$NON-NLS-1$ + public static final String DRAWABLE_HDPI = "drawable-hdpi"; //$NON-NLS-1$ + public static final String DRAWABLE_MDPI = "drawable-mdpi"; //$NON-NLS-1$ + public static final String DRAWABLE_LDPI = "drawable-ldpi"; //$NON-NLS-1$ + + // Resources + public static final String DRAWABLE_RESOURCE_PREFIX = "@drawable/";//$NON-NLS-1$ + public static final String VALUE_LAYOUT_PREFIX = "@layout/"; //$NON-NLS-1$ +} diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java new file mode 100644 index 0000000..4bc8503 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java @@ -0,0 +1,148 @@ +/* + * 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 static com.android.tools.lint.detector.api.LintConstants.DOT_XML; + +import com.android.resources.FolderTypeRelationship; +import com.android.resources.ResourceFolderType; +import com.android.resources.ResourceType; + +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.HashSet; +import java.util.List; +import java.util.Set; + + +/** + * Useful utility methods related to lint. + */ +public class LintUtils { + /** + * Format a list of strings, and cut of the list at {@code maxItems} if the + * number of items are greater. + * + * @param strings the list of strings to print out as a comma separated list + * @param maxItems the maximum number of items to print + * @return a comma separated list + */ + public static String formatList(List<String> strings, int maxItems) { + StringBuilder sb = new StringBuilder(20 * strings.size()); + + for (int i = 0, n = strings.size(); i < n; i++) { + if (sb.length() > 0) { + sb.append(", "); //$NON-NLS-1$ + } + sb.append(strings.get(i)); + + if (i == maxItems - 1 && n > maxItems) { + sb.append(String.format("... (%1$d more)", n - i - 1)); + break; + } + } + + return sb.toString(); + } + + /** + * Computes the set difference {@code a - b} + * + * @param a the set to subtract from + * @param b the set to subtract + * @return the elements that are in {@code a} but not in {@code b} + */ + public static Set<String> difference(Set<String> a, Set<String> b) { + HashSet<String> copy = new HashSet<String>(a); + copy.removeAll(b); + return copy; + } + + /** + * Determine if the given type corresponds to a resource that has a unique + * file + * + * @param type the resource type to check + * @return true if the given type corresponds to a file-type resource + */ + public static boolean isFileBasedResourceType(ResourceType type) { + List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); + for (ResourceFolderType folderType : folderTypes) { + if (folderType != ResourceFolderType.VALUES) { + if (type == ResourceType.ID) { + return false; + } + return true; + } + } + return false; + } + + /** + * 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() - DOT_XML.length(), + DOT_XML, 0, DOT_XML.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/Project.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java index f09d4a9..2011e86 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java @@ -16,7 +16,8 @@ package com.android.tools.lint.detector.api; -import com.android.tools.lint.api.ToolContext; +import com.android.tools.lint.client.api.Configuration; +import com.android.tools.lint.client.api.LintClient; import java.io.File; import java.util.ArrayList; @@ -31,9 +32,11 @@ import java.util.List; */ public class Project { /** The associated tool */ - private final ToolContext mTool; - private final File dir; - private final File referenceDir; + private final LintClient mTool; + private final File mDir; + private final File mReferenceDir; + private Configuration mConfiguration; + /** * If non null, specifies a non-empty list of specific files under this * project which should be checked. @@ -49,22 +52,22 @@ public class Project { * @param dir the root directory of the project * @param referenceDir See {@link #getReferenceDir()}. */ - public Project(ToolContext tool, File dir, File referenceDir) { - this.mTool = tool; - this.dir = dir; - this.referenceDir = referenceDir; + public Project(LintClient tool, File dir, File referenceDir) { + mTool = tool; + mDir = dir; + mReferenceDir = referenceDir; } @Override public String toString() { - return "Project [dir=" + dir + "]"; + return "Project [dir=" + mDir + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((dir == null) ? 0 : dir.hashCode()); + result = prime * result + ((mDir == null) ? 0 : mDir.hashCode()); return result; } @@ -77,10 +80,10 @@ public class Project { if (getClass() != obj.getClass()) return false; Project other = (Project) obj; - if (dir == null) { - if (other.dir != null) + if (mDir == null) { + if (other.mDir != null) return false; - } else if (!dir.equals(other.dir)) + } else if (!mDir.equals(other.mDir)) return false; return true; } @@ -138,11 +141,32 @@ public class Project { * directory when a directory tree is being scanned * * @param file the file under this project to check - * @return the relative path + * @return the path relative to the reference directory (often the project directory) + */ + public String getDisplayPath(File file) { + String path = file.getPath(); + String referencePath = mReferenceDir.getPath(); + if (path.startsWith(referencePath)) { + int length = referencePath.length(); + if (path.length() > length && path.charAt(length) == File.separatorChar) { + length++; + } + + return path.substring(length); + } + + return path; + } + + /** + * Returns the relative path of a given file within the current project. + * + * @param file the file under this project to check + * @return the path relative to the project */ public String getRelativePath(File file) { String path = file.getPath(); - String referencePath = referenceDir.getPath(); + String referencePath = mDir.getPath(); if (path.startsWith(referencePath)) { int length = referencePath.length(); if (path.length() > length && path.charAt(length) == File.separatorChar) { @@ -161,7 +185,7 @@ public class Project { * @return the dir */ public File getDir() { - return dir; + return mDir; } /** @@ -174,6 +198,24 @@ public class Project { * @return the reference directory, never null */ public File getReferenceDir() { - return referenceDir; + return mReferenceDir; + } + + /** + * Gets the configuration associated with this project + * + * @return the configuration associated with this project + */ + public Configuration getConfiguration() { + return mConfiguration; + } + + /** + * Sets the configuration associated with this project + * + * @param configuration sets the configuration associated with this project + */ + public void setConfiguration(Configuration configuration) { + mConfiguration = configuration; } } 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 index 34ceaf2..828f946 100644 --- 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 @@ -31,7 +31,7 @@ import java.io.File; public abstract class ResourceXmlDetector extends Detector.XmlDetectorAdapter { @Override public boolean appliesTo(Context context, File file) { - return isXmlFile(file); + return LintUtils.isXmlFile(file); } /** 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 index 8ee4cd9..055d8bb 100644 --- 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 @@ -16,6 +16,12 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_CONTENT_DESCRIPTION; +import static com.android.tools.lint.detector.api.LintConstants.IMAGE_BUTTON; +import static com.android.tools.lint.detector.api.LintConstants.IMAGE_VIEW; + +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; @@ -44,18 +50,17 @@ public class AccessibilityDetector extends LayoutDetector { "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, Scope.RESOURCE_FILE_SCOPE); + Category.A11Y, + 3, + Severity.WARNING, + AccessibilityDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Constructs a new accessibility check */ public AccessibilityDetector() { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -71,12 +76,12 @@ public class AccessibilityDetector extends LayoutDetector { @Override public void visitElement(Context context, Element element) { if (!element.hasAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION)) { - context.toolContext.report(context, ISSUE, context.getLocation(context), + context.client.report(context, ISSUE, context.getLocation(context), "[Accessibility] Missing contentDescription attribute on image", null); } else { String attribute = element.getAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION); if (attribute.length() == 0 || attribute.equals("TODO")) { //$NON-NLS-1$ - context.toolContext.report(context, ISSUE, context.getLocation(context), + context.client.report(context, ISSUE, context.getLocation(context), "[Accessibility] Empty contentDescription attribute on image", null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java index 875e63b..1ecc159 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java @@ -22,8 +22,10 @@ import static com.android.tools.lint.checks.TranslationDetector.TAG_INTEGER_ARRA import static com.android.tools.lint.checks.TranslationDetector.TAG_STRING_ARRAY; import com.android.resources.ResourceFolderType; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LintUtils; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; @@ -66,7 +68,11 @@ public class ArraySizeDetector extends ResourceXmlDetector { "decide if this is really an error.\n" + "\n" + "You can suppress this error type if it finds false errors in your project.", - CATEGORY_CORRECTNESS, 7, Severity.WARNING, Scope.ALL_RESOURCES_SCOPE); + Category.CORRECTNESS, + 7, + Severity.WARNING, + ArraySizeDetector.class, + Scope.ALL_RESOURCES_SCOPE); private Map<File, Pair<String, Integer>> mFileToArrayCount; @@ -80,11 +86,6 @@ public class ArraySizeDetector extends ResourceXmlDetector { } @Override - public Issue[] getIssues() { - return new Issue[] { INCONSISTENT }; - } - - @Override public Speed getSpeed() { return Speed.NORMAL; } @@ -134,7 +135,7 @@ public class ArraySizeDetector extends ResourceXmlDetector { File otherFile = fileMap.get(name); String otherName = otherFile.getParentFile().getName() + File.separator + otherFile.getName(); - context.toolContext.report(context, INCONSISTENT, location, + context.client.report(context, INCONSISTENT, location, String.format( "Array %1$s has an inconsistent number of items (%2$d in %3$s, %4$d in %5$s)", name, count, thisName, current, otherName), null); @@ -149,12 +150,12 @@ public class ArraySizeDetector extends ResourceXmlDetector { public void visitElement(Context context, Element element) { Attr attribute = element.getAttributeNode(ATTR_NAME); if (attribute == null || attribute.getValue().length() == 0) { - context.toolContext.report(context, INCONSISTENT, context.getLocation(element), + context.client.report(context, INCONSISTENT, context.getLocation(element), String.format("Missing name attribute in %1$s declaration", element.getTagName()), null); } else { String name = attribute.getValue(); - int childCount = getChildCount(element); + int childCount = LintUtils.getChildCount(element); mFileToArrayCount.put(context.file, Pair.of(name, childCount)); } } 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 deleted file mode 100644 index f807319..0000000 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinDetectorRegistry.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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()); - detectors.add(new GridLayoutDetector()); - detectors.add(new TranslationDetector()); - detectors.add(new HardcodedValuesDetector()); - detectors.add(new ProguardDetector()); - detectors.add(new PxUsageDetector()); - detectors.add(new TextFieldDetector()); - detectors.add(new UnusedResourceDetector()); - detectors.add(new ArraySizeDetector()); - detectors.add(new ManifestOrderDetector()); - detectors.add(new ExportedServiceDetector()); - - // 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/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java new file mode 100644 index 0000000..5e63caf --- /dev/null +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java @@ -0,0 +1,90 @@ +/* + * 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.client.api.IssueRegistry; +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 class BuiltinIssueRegistry extends IssueRegistry { + private static final List<Issue> sIssues; + + static { + List<Issue> issues = new ArrayList<Issue>(); + + issues.add(AccessibilityDetector.ISSUE); + issues.add(DuplicateIdDetector.CROSS_LAYOUT); + issues.add(DuplicateIdDetector.WITHIN_LAYOUT); + issues.add(StateListDetector.ISSUE); + issues.add(InefficientWeightDetector.ISSUE); + issues.add(ScrollViewChildDetector.ISSUE); + issues.add(MergeRootFrameLayoutDetector.ISSUE); + issues.add(NestedScrollingWidgetDetector.ISSUE); + issues.add(ChildCountDetector.SCROLLVIEW_ISSUE); + issues.add(ChildCountDetector.ADAPTERVIEW_ISSUE); + issues.add(UseCompoundDrawableDetector.ISSUE); + issues.add(UselessViewDetector.USELESS_PARENT); + issues.add(UselessViewDetector.USELESS_LEAF); + issues.add(TooManyViewsDetector.TOO_MANY); + issues.add(TooManyViewsDetector.TOO_DEEP); + issues.add(GridLayoutDetector.ISSUE); + issues.add(TranslationDetector.EXTRA); + issues.add(TranslationDetector.MISSING); + issues.add(HardcodedValuesDetector.ISSUE); + issues.add(ProguardDetector.ISSUE); + issues.add(PxUsageDetector.ISSUE); + issues.add(TextFieldDetector.ISSUE); + issues.add(UnusedResourceDetector.ISSUE); + issues.add(UnusedResourceDetector.ISSUE_IDS); + issues.add(ArraySizeDetector.INCONSISTENT); + issues.add(ManifestOrderDetector.ISSUE); + issues.add(ExportedServiceDetector.ISSUE); + + // TODO: Populate dynamically somehow? + + 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); + } + } + } + + /** + * Constructs a new {@link BuiltinIssueRegistry} + */ + public BuiltinIssueRegistry() { + } + + @Override + public List<Issue> getIssues() { + return sIssues; + } +} 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 index 23e00a6..4b2cea5 100644 --- 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 @@ -16,9 +16,16 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.GRID_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.HORIZONTAL_SCROLL_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.LIST_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.SCROLL_VIEW; + +import com.android.tools.lint.detector.api.Category; 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.LintUtils; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; @@ -40,7 +47,11 @@ public class ChildCountDetector extends LayoutDetector { "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_CORRECTNESS, 8, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.CORRECTNESS, + 8, + Severity.WARNING, + ChildCountDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** The main issue discovered by this detector */ public static final Issue ADAPTERVIEW_ISSUE = Issue.create( @@ -48,7 +59,11 @@ public class ChildCountDetector extends LayoutDetector { "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_CORRECTNESS, 10, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE).setMoreInfo( + Category.CORRECTNESS, + 10, + Severity.WARNING, + ChildCountDetector.class, + Scope.RESOURCE_FILE_SCOPE).setMoreInfo( "http://developer.android.com/reference/android/widget/AdapterView.html"); //$NON-NLS-1$ /** Constructs a new {@link ChildCountDetector} */ @@ -56,11 +71,6 @@ public class ChildCountDetector extends LayoutDetector { } @Override - public Issue[] getIssues() { - return new Issue[] { SCROLLVIEW_ISSUE, ADAPTERVIEW_ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -78,18 +88,18 @@ public class ChildCountDetector extends LayoutDetector { @Override public void visitElement(Context context, Element element) { - int childCount = getChildCount(element); + int childCount = LintUtils.getChildCount(element); String tagName = element.getTagName(); if (tagName.equals(SCROLL_VIEW) || tagName.equals(HORIZONTAL_SCROLL_VIEW)) { if (childCount > 1) { - context.toolContext.report(context, SCROLLVIEW_ISSUE, + context.client.report(context, SCROLLVIEW_ISSUE, context.getLocation(element), "A scroll view can have only one child", null); } } else { // Adapter view if (childCount > 0) { - context.toolContext.report(context, ADAPTERVIEW_ISSUE, + context.client.report(context, ADAPTERVIEW_ISSUE, context.getLocation(element), "A list/grid should have no children declared in XML", null); } 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 index f31840e..2e7aadd 100644 --- 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 @@ -16,7 +16,13 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_ID; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT; +import static com.android.tools.lint.detector.api.LintConstants.INCLUDE; +import static com.android.tools.lint.detector.api.LintConstants.VALUE_LAYOUT_PREFIX; + import com.android.resources.ResourceFolderType; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; @@ -53,7 +59,11 @@ public class DuplicateIdDetector extends LayoutDetector { "Checks for duplicate ids within a single layout", "Within a layout, id's should be unique since otherwise findViewById() can " + "return an unexpected view.", - CATEGORY_CORRECTNESS, 7, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.CORRECTNESS, + 7, + Severity.WARNING, + DuplicateIdDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** The main issue discovered by this detector */ public static final Issue CROSS_LAYOUT = Issue.create( @@ -63,7 +73,11 @@ public class DuplicateIdDetector extends LayoutDetector { "layouts are combined with include tags, then the id's need to be unique " + "within any chain of included layouts, or Activity#findViewById() can " + "return an unexpected view.", - CATEGORY_CORRECTNESS, 6, Severity.WARNING, Scope.ALL_RESOURCES_SCOPE); + Category.CORRECTNESS, + 6, + Severity.WARNING, + DuplicateIdDetector.class, + Scope.ALL_RESOURCES_SCOPE); /** Constructs a duplicate id check */ public DuplicateIdDetector() { @@ -71,11 +85,6 @@ public class DuplicateIdDetector extends LayoutDetector { @Override - public Issue[] getIssues() { - return new Issue[] { WITHIN_LAYOUT, CROSS_LAYOUT }; - } - - @Override public boolean appliesTo(ResourceFolderType folderType) { return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.MENU; } @@ -147,7 +156,7 @@ public class DuplicateIdDetector extends LayoutDetector { } private void checkForIncludeDuplicates(Context context) { - if (!context.toolContext.isEnabled(CROSS_LAYOUT) || + if (!context.configuration.isEnabled(CROSS_LAYOUT) || !context.scope.contains(Scope.ALL_RESOURCE_FILES)) { return; } @@ -342,7 +351,7 @@ public class DuplicateIdDetector extends LayoutDetector { // Also record the secondary location location.setSecondary(new Location(second, null, null)); } - context.toolContext.report(context, CROSS_LAYOUT, location, msg, null); + context.client.report(context, CROSS_LAYOUT, location, msg, null); } /** @@ -390,7 +399,7 @@ public class DuplicateIdDetector extends LayoutDetector { assert attribute.getLocalName().equals(ATTR_ID); String id = attribute.getValue(); if (mIds.contains(id)) { - context.toolContext.report(context, WITHIN_LAYOUT, context.getLocation(attribute), + context.client.report(context, WITHIN_LAYOUT, context.getLocation(attribute), String.format("Duplicate id %1$s, already defined earlier in this layout", id), null); } else if (id.startsWith("@+id/")) { //$NON-NLS-1$ diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ExportedServiceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ExportedServiceDetector.java index e85be6e..437256c 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ExportedServiceDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ExportedServiceDetector.java @@ -16,16 +16,19 @@ package com.android.tools.lint.checks; -import static com.android.tools.lint.checks.LintConstants.ANDROID_MANIFEST_XML; -import static com.android.tools.lint.checks.LintConstants.ATTR_EXPORTED; -import static com.android.tools.lint.checks.LintConstants.ATTR_PERMISSION; -import static com.android.tools.lint.checks.LintConstants.TAG_APPLICATION; -import static com.android.tools.lint.checks.LintConstants.TAG_INTENT_FILTER; -import static com.android.tools.lint.checks.LintConstants.TAG_SERVICE; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_MANIFEST_XML; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_EXPORTED; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_PERMISSION; +import static com.android.tools.lint.detector.api.LintConstants.TAG_APPLICATION; +import static com.android.tools.lint.detector.api.LintConstants.TAG_INTENT_FILTER; +import static com.android.tools.lint.detector.api.LintConstants.TAG_SERVICE; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LintUtils; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; @@ -51,18 +54,17 @@ public class ExportedServiceDetector extends Detector.XmlDetectorAdapter { "an intent-filter and do not specify exported=false) should define a " + "permission that an entity must have in order to launch the service " + "or bind to it. Without this, any application can use this service.", - CATEGORY_SECURITY, 5, Severity.WARNING, EnumSet.of(Scope.MANIFEST)); + Category.SECURITY, + 5, + Severity.WARNING, + ExportedServiceDetector.class, + EnumSet.of(Scope.MANIFEST)); /** Constructs a new accessibility check */ public ExportedServiceDetector() { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -90,7 +92,7 @@ public class ExportedServiceDetector extends Detector.XmlDetectorAdapter { exported = Boolean.valueOf(exportValue); } else { boolean haveIntentFilters = false; - for (Element child : getChildren(element)) { + for (Element child : LintUtils.getChildren(element)) { if (child.getTagName().equals(TAG_INTENT_FILTER)) { haveIntentFilters = true; break; @@ -110,9 +112,9 @@ public class ExportedServiceDetector extends Detector.XmlDetectorAdapter { permission = application.getAttributeNS(ANDROID_URI, ATTR_PERMISSION); if (permission == null || permission.length() == 0) { // No declared permission for this exported service: complain - context.toolContext.report(context, ISSUE, + context.client.report(context, ISSUE, context.getLocation(element), - "Export service does not require permission", null); + "Exported service does not require permission", null); } } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java index 78d8083..f8cc3c9 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java @@ -16,9 +16,17 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_COLUMN_COUNT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_COLUMN; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_ROW; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_ROW_COUNT; + +import com.android.tools.lint.detector.api.Category; 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.LintUtils; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; @@ -41,18 +49,17 @@ public class GridLayoutDetector extends LayoutDetector { "the declared grid dimensions", "Declaring a layout_row or layout_column that falls outside the declared size " + "of a GridLayout's rowCount or columnCount is usually an unintentional error.", - CATEGORY_CORRECTNESS, 4, Severity.ERROR, Scope.RESOURCE_FILE_SCOPE); + Category.CORRECTNESS, + 4, + Severity.ERROR, + GridLayoutDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Constructs a new accessibility check */ public GridLayoutDetector() { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -83,12 +90,12 @@ public class GridLayoutDetector extends LayoutDetector { int declaredColumnCount = getInt(element, ATTR_COLUMN_COUNT, -1); if (declaredColumnCount != -1 || declaredRowCount != -1) { - for (Element child : getChildren(element)) { + for (Element child : LintUtils.getChildren(element)) { if (declaredColumnCount != -1) { int column = getInt(child, ATTR_LAYOUT_COLUMN, -1); if (column >= declaredColumnCount) { Attr node = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_COLUMN); - context.toolContext.report(context, ISSUE, context.getLocation(node), + context.client.report(context, ISSUE, context.getLocation(node), String.format("Column attribute (%1$d) exceeds declared grid column count (%2$d)", column, declaredColumnCount), null); } @@ -97,7 +104,7 @@ public class GridLayoutDetector extends LayoutDetector { int row = getInt(child, ATTR_LAYOUT_ROW, -1); if (row > declaredRowCount) { Attr node = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_ROW); - context.toolContext.report(context, ISSUE, context.getLocation(node), + context.client.report(context, ISSUE, context.getLocation(node), String.format("Row attribute (%1$d) exceeds declared grid row count (%2$d)", row, declaredRowCount), null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedValuesDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedValuesDetector.java index 35922f9..fd43ecd 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedValuesDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedValuesDetector.java @@ -16,6 +16,14 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_CONTENT_DESCRIPTION; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_HINT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LABEL; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_PROMPT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_TEXT; + +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; @@ -45,7 +53,11 @@ public class HardcodedValuesDetector extends LayoutDetector { "* The application cannot be translated to other languages by just adding new " + "translations for existing string resources.", - CATEGORY_I18N, 5, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.I18N, + 5, + Severity.WARNING, + HardcodedValuesDetector.class, + Scope.RESOURCE_FILE_SCOPE); // TODO: Add additional issues here, such as hardcoded colors, hardcoded sizes, etc @@ -54,11 +66,6 @@ public class HardcodedValuesDetector extends LayoutDetector { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -83,7 +90,7 @@ public class HardcodedValuesDetector extends LayoutDetector { return; } - context.toolContext.report(context, ISSUE, context.getLocation(attribute), + context.client.report(context, ISSUE, context.getLocation(attribute), String.format("[I18N] Hardcoded string \"%1$s\", should use @string resource", value), null); } 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 index 3ca204f..0f92f44 100644 --- 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 @@ -16,9 +16,19 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_WEIGHT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_WIDTH; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_ORIENTATION; +import static com.android.tools.lint.detector.api.LintConstants.LINEAR_LAYOUT; +import static com.android.tools.lint.detector.api.LintConstants.VALUE_VERTICAL; + +import com.android.tools.lint.detector.api.Category; 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.LintUtils; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; @@ -43,18 +53,17 @@ public class InefficientWeightDetector extends LayoutDetector { "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_PERFORMANCE, 3, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.PERFORMANCE, + 3, + Severity.WARNING, + InefficientWeightDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Constructs a new {@link InefficientWeightDetector} */ public InefficientWeightDetector() { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -66,7 +75,7 @@ public class InefficientWeightDetector extends LayoutDetector { @Override public void visitElement(Context context, Element element) { - List<Element> children = getChildren(element); + List<Element> children = LintUtils.getChildren(element); // See if there is exactly one child with a weight Element weightChild = null; for (Element child : children) { @@ -93,7 +102,7 @@ public class InefficientWeightDetector extends LayoutDetector { String msg = String.format( "Use a %1$s of 0dip instead of %2$s for better performance", dimension, size); - context.toolContext.report(context, ISSUE, + context.client.report(context, ISSUE, context.getLocation(sizeNode != null ? sizeNode : weightChild), msg, null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/LintConstants.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/LintConstants.java deleted file mode 100644 index 79feac2..0000000 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/LintConstants.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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; - -/** - * Constants used by the various detectors, defined in one place - */ -public class LintConstants { - // Tags - public static final String TAG_SERVICE = "service"; //$NON-NLS-1$ - public static final String TAG_USES_PERMISSION = "uses-permission";//$NON-NLS-1$ - public static final String TAG_APPLICATION = "application"; //$NON-NLS-1$ - public static final String TAG_INTENT_FILTER = " intent-filter"; //$NON-NLS-1$ - - // Attributes - public static final String ATTR_EXPORTED = "exported"; //$NON-NLS-1$ - public static final String ATTR_PERMISSION = "permission"; //$NON-NLS-1$ - - // Filenames - public static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; //$NON-NLS-1$ - -} diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java index 1126080..bb82541 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java @@ -16,10 +16,12 @@ package com.android.tools.lint.checks; -import static com.android.tools.lint.checks.LintConstants.ANDROID_MANIFEST_XML; -import static com.android.tools.lint.checks.LintConstants.TAG_APPLICATION; -import static com.android.tools.lint.checks.LintConstants.TAG_USES_PERMISSION; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_MANIFEST_XML; +import static com.android.tools.lint.detector.api.LintConstants.TAG_APPLICATION; +import static com.android.tools.lint.detector.api.LintConstants.TAG_USES_PERMISSION; +import static com.android.tools.lint.detector.api.LintConstants.TAG_USES_SDK; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Issue; @@ -50,7 +52,11 @@ public class ManifestOrderDetector extends Detector.XmlDetectorAdapter { "themes not getting applied correctly) when the <application> tag appears " + "before some of these other elements, so it's best to order your" + "manifest in the logical dependency order.", - CATEGORY_CORRECTNESS, 5, Severity.WARNING, EnumSet.of(Scope.MANIFEST)); + Category.CORRECTNESS, + 5, + Severity.WARNING, + ManifestOrderDetector.class, + EnumSet.of(Scope.MANIFEST)); /** Constructs a new accessibility check */ public ManifestOrderDetector() { @@ -59,14 +65,10 @@ public class ManifestOrderDetector extends Detector.XmlDetectorAdapter { private boolean mSeenApplication; @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } + @Override public boolean appliesTo(Context context, File file) { return file.getName().equals(ANDROID_MANIFEST_XML); @@ -87,7 +89,7 @@ public class ManifestOrderDetector extends Detector.XmlDetectorAdapter { "permission", //$NON-NLS-1$ "permission-tree", //$NON-NLS-1$ "permission-group", //$NON-NLS-1$ - "uses-sdk", //$NON-NLS-1$ + TAG_USES_SDK, "uses-configuration", //$NON-NLS-1$ "uses-feature", //$NON-NLS-1$ "supports-screens", //$NON-NLS-1$ @@ -102,7 +104,7 @@ public class ManifestOrderDetector extends Detector.XmlDetectorAdapter { if (tag.equals(TAG_APPLICATION)) { mSeenApplication = true; } else if (mSeenApplication) { - context.toolContext.report(context, ISSUE, context.getLocation(element), + context.client.report(context, ISSUE, context.getLocation(element), String.format("<%1$s> tag appears after <application> tag", tag), null); // Don't complain for *every* element following the <application> tag 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 index 92f7058..d5fb886 100644 --- 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 @@ -16,6 +16,13 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_BACKGROUND; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_FOREGROUND; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_GRAVITY; +import static com.android.tools.lint.detector.api.LintConstants.FRAME_LAYOUT; + +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; @@ -38,18 +45,17 @@ public class MergeRootFrameLayoutDetector extends LayoutDetector { "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_PERFORMANCE, 4, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.PERFORMANCE, + 4, + Severity.WARNING, + MergeRootFrameLayoutDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Constructs a new {@link MergeRootFrameLayoutDetector} */ public MergeRootFrameLayoutDetector() { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -63,7 +69,7 @@ public class MergeRootFrameLayoutDetector extends LayoutDetector { && !root.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND) && !root.hasAttributeNS(ANDROID_URI, ATTR_FOREGROUND) && !hasPadding(root)) { - context.toolContext.report(context, ISSUE, context.getLocation(root), + context.client.report(context, ISSUE, context.getLocation(root), "This <FrameLayout> can be replaced with a <merge> tag", null); } } 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 index c264f15..8528e90 100644 --- 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 @@ -16,6 +16,13 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.GALLERY; +import static com.android.tools.lint.detector.api.LintConstants.GRID_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.HORIZONTAL_SCROLL_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.LIST_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.SCROLL_VIEW; + +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; @@ -43,18 +50,17 @@ public class NestedScrollingWidgetDetector extends LayoutDetector { // TODO: Better description! "A scrolling widget such as a ScrollView should not contain any nested " + "scrolling widgets since this has various usability issues", - CATEGORY_CORRECTNESS, 7, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.CORRECTNESS, + 7, + Severity.WARNING, + NestedScrollingWidgetDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** 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; @@ -116,7 +122,7 @@ public class NestedScrollingWidgetDetector extends LayoutDetector { "horizontally scrolling widget (%2$s)"; } String msg = String.format(format, parent.getTagName(), element.getTagName()); - context.toolContext.report(context, ISSUE, context.getLocation(element), msg, null); + context.client.report(context, ISSUE, context.getLocation(element), msg, null); } } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java index 500d14a..841e7d6 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java @@ -16,6 +16,7 @@ package com.android.tools.lint.checks; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Issue; @@ -42,15 +43,14 @@ public class ProguardDetector extends Detector { "-keepclasseswithmembers, since the old flags also implies " + "\"allow shrinking\" which means symbols only referred to from XML and " + "not Java (such as possibly CustomViews) can get deleted.", - CATEGORY_CORRECTNESS, 8, Severity.ERROR, EnumSet.of(Scope.PROGUARD)).setMoreInfo( + Category.CORRECTNESS, + 8, + Severity.ERROR, + ProguardDetector.class, + EnumSet.of(Scope.PROGUARD)).setMoreInfo( "http://http://code.google.com/p/android/issues/detail?id=16384"); //$NON-NLS-1$ @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public void run(Context context) { String contents = context.getContents(); if (contents != null) { @@ -59,7 +59,7 @@ public class ProguardDetector extends Detector { "-keepclasseswithmembernames class * {\n" + //$NON-NLS-1$ " public <init>(android."); //$NON-NLS-1$ if (index != -1) { - context.toolContext.report(context, ISSUE, context.getLocation(context), + context.client.report(context, ISSUE, context.getLocation(context), "Obsolete proguard file; use -keepclasseswithmembers instead of -keepclasseswithmembernames", null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java index e2bee0e..2e8d41e 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java @@ -16,6 +16,7 @@ package com.android.tools.lint.checks; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; @@ -46,7 +47,11 @@ public class PxUsageDetector extends LayoutDetector { "in your application code to work with bitmaps that are not pre-scaled for the " + "current screen density, you might need to scale the pixel values that you use in " + "your code to match the un-scaled bitmap source.", - CATEGORY_CORRECTNESS, 2, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE).setMoreInfo( + Category.CORRECTNESS, + 2, + Severity.WARNING, + PxUsageDetector.class, + Scope.RESOURCE_FILE_SCOPE).setMoreInfo( "http://developer.android.com/guide/practices/screens_support.html#screen-independence"); //$NON-NLS-1$ /** Constructs a new {@link PxUsageDetector} */ @@ -54,11 +59,6 @@ public class PxUsageDetector extends LayoutDetector { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -76,7 +76,7 @@ public class PxUsageDetector extends LayoutDetector { // 0px is fine. 0px is 0dp regardless of density... return; } - context.toolContext.report(context, ISSUE, context.getLocation(attribute), + context.client.report(context, ISSUE, context.getLocation(attribute), "Avoid using \"px\" as units; use \"dp\" instead", null); } } 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 index 4387315..62db541 100644 --- 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 @@ -16,9 +16,19 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_WIDTH; +import static com.android.tools.lint.detector.api.LintConstants.HORIZONTAL_SCROLL_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.SCROLL_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.VALUE_FILL_PARENT; +import static com.android.tools.lint.detector.api.LintConstants.VALUE_MATCH_PARENT; + +import com.android.tools.lint.detector.api.Category; 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.LintUtils; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; @@ -43,18 +53,17 @@ public class ScrollViewChildDetector extends LayoutDetector { "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_CORRECTNESS, 7, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.CORRECTNESS, + 7, + Severity.WARNING, + ScrollViewChildDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Constructs a new {@link ScrollViewChildDetector} */ public ScrollViewChildDetector() { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -69,7 +78,7 @@ public class ScrollViewChildDetector extends LayoutDetector { @Override public void visitElement(Context context, Element element) { - List<Element> children = getChildren(element); + List<Element> children = LintUtils.getChildren(element); boolean isHorizontal = HORIZONTAL_SCROLL_VIEW.equals(element.getTagName()); String attributeName = isHorizontal ? ATTR_LAYOUT_WIDTH : ATTR_LAYOUT_HEIGHT; for (Element child : children) { @@ -78,7 +87,7 @@ public class ScrollViewChildDetector extends LayoutDetector { 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(context, ISSUE, context.getLocation(sizeNode), msg, + context.client.report(context, ISSUE, context.getLocation(sizeNode), msg, null); } } 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 index 3b412e5..7fa2ae4 100644 --- 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 @@ -17,8 +17,10 @@ package com.android.tools.lint.checks; import com.android.resources.ResourceFolderType; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LintUtils; import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; @@ -42,18 +44,17 @@ public class StateListDetector extends ResourceXmlDetector { "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, 5, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.CORRECTNESS, + 5, + Severity.WARNING, + StateListDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** 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; } @@ -72,7 +73,7 @@ public class StateListDetector extends ResourceXmlDetector { Element root = document.getDocumentElement(); if (root != null && root.getTagName().equals("selector")) { //$NON-NLS-1$ - List<Element> children = getChildren(root); + List<Element> children = LintUtils.getChildren(root); for (int i = 0; i < children.size() - 1; i++) { Element child = children.get(i); boolean hasState = false; @@ -85,7 +86,7 @@ public class StateListDetector extends ResourceXmlDetector { } } if (!hasState) { - context.toolContext.report(context, ISSUE, context.getLocation(child), + context.client.report(context, ISSUE, context.getLocation(child), String.format("No android:state_ attribute found on <item> %1$d, later states not reachable", i), null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TextFieldDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TextFieldDetector.java index cc17fbd..f06c5fb 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TextFieldDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TextFieldDetector.java @@ -16,6 +16,13 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_HINT; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_INPUT_METHOD; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_INPUT_TYPE; +import static com.android.tools.lint.detector.api.LintConstants.EDIT_TEXT; + +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; @@ -45,18 +52,17 @@ public class TextFieldDetector extends LayoutDetector { "If you really want to keep the text field generic, you can suppress this warning " + "by setting inputType=\"text\".", - CATEGORY_USABILITY, 5, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.USABILITY, + 5, + Severity.WARNING, + TextFieldDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Constructs a new {@link TextFieldDetector} */ public TextFieldDetector() { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -76,7 +82,7 @@ public class TextFieldDetector extends LayoutDetector { return; } - context.toolContext.report(context, ISSUE, context.getLocation(element), + context.client.report(context, ISSUE, context.getLocation(element), "This text field does not specify an inputType or a hint", null); } } 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 index 70bcf45..116cdfe 100644 --- 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 @@ -16,6 +16,7 @@ package com.android.tools.lint.checks; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; @@ -40,7 +41,11 @@ public class TooManyViewsDetector extends LayoutDetector { "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, Scope.RESOURCE_FILE_SCOPE); + Category.PERFORMANCE, + 1, + Severity.WARNING, + TooManyViewsDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Issue of having too deep hierarchies in layouts */ public static final Issue TOO_DEEP = Issue.create( @@ -50,7 +55,11 @@ public class TooManyViewsDetector extends LayoutDetector { "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, Scope.RESOURCE_FILE_SCOPE); + Category.PERFORMANCE, + 1, + Severity.WARNING, + TooManyViewsDetector.class, + Scope.RESOURCE_FILE_SCOPE); private static final int MAX_VIEW_COUNT; private static final int MAX_DEPTH; @@ -92,11 +101,6 @@ public class TooManyViewsDetector extends LayoutDetector { } @Override - public Issue[] getIssues() { - return new Issue[] { TOO_DEEP, TOO_MANY }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -125,12 +129,12 @@ public class TooManyViewsDetector extends LayoutDetector { 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(context, TOO_DEEP, context.getLocation(element), msg, null); + context.client.report(context, TOO_DEEP, context.getLocation(element), msg, null); } 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(context, TOO_MANY, context.getLocation(element), msg, null); + context.client.report(context, TOO_MANY, context.getLocation(element), msg, null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java index d105980..80371d8 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java @@ -18,8 +18,10 @@ package com.android.tools.lint.checks; import com.android.annotations.VisibleForTesting; import com.android.resources.ResourceFolderType; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LintUtils; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; @@ -72,7 +74,11 @@ public class TranslationDetector extends ResourceXmlDetector { "subset of the strings and fall back to the standard language strings. " + "You can require all regions to provide a full translation by setting the " + "environment variable ANDROID_LINT_COMPLETE_REGIONS.", - CATEGORY_CORRECTNESS, 8, Severity.ERROR, Scope.ALL_RESOURCES_SCOPE); + Category.CORRECTNESS, + 8, + Severity.ERROR, + TranslationDetector.class, + Scope.ALL_RESOURCES_SCOPE); /** Are there extra translations that are "unused" (appear only in specific languages) ? */ public static final Issue EXTRA = Issue.create( @@ -82,7 +88,11 @@ public class TranslationDetector extends ResourceXmlDetector { "no corresponding string in the default locale, then this string is probably " + "unused. (It's technically possible that your application is only intended to " + "run in a specific locale, but it's still a good idea to provide a fallback.)", - CATEGORY_CORRECTNESS, 6, Severity.WARNING, Scope.ALL_RESOURCES_SCOPE); + Category.CORRECTNESS, + 6, + Severity.WARNING, + TranslationDetector.class, + Scope.ALL_RESOURCES_SCOPE); private Set<String> mNames; private boolean mIgnoreFile; @@ -98,11 +108,6 @@ public class TranslationDetector extends ResourceXmlDetector { } @Override - public Issue[] getIssues() { - return new Issue[] { MISSING, EXTRA }; - } - - @Override public Speed getSpeed() { return Speed.NORMAL; } @@ -163,8 +168,8 @@ public class TranslationDetector extends ResourceXmlDetector { return; } - boolean reportMissing = context.toolContext.isEnabled(MISSING); - boolean reportExtra = context.toolContext.isEnabled(EXTRA); + boolean reportMissing = context.configuration.isEnabled(MISSING); + boolean reportExtra = context.configuration.isEnabled(EXTRA); // res/strings.xml etc String defaultLanguage = "Default"; @@ -299,26 +304,26 @@ public class TranslationDetector extends ResourceXmlDetector { // defined in other languages, so there's no problem. if (stringCount != strings.size()) { if (reportMissing) { - Set<String> difference = difference(defaultStrings, strings); + Set<String> difference = LintUtils.difference(defaultStrings, strings); if (difference.size() > 0) { List<String> sorted = new ArrayList<String>(difference); Collections.sort(sorted); Location location = getLocation(language, parentFolderToLanguage); - context.toolContext.report(context, MISSING, location, + context.client.report(context, MISSING, location, String.format("Locale %1$s is missing translations for: %2$s", - language, formatList(sorted, 4)), null); + language, LintUtils.formatList(sorted, 4)), null); } } if (reportExtra) { - Set<String> difference = difference(strings, defaultStrings); + Set<String> difference = LintUtils.difference(strings, defaultStrings); if (difference.size() > 0) { List<String> sorted = new ArrayList<String>(difference); Collections.sort(sorted); Location location = getLocation(language, parentFolderToLanguage); - context.toolContext.report(context, EXTRA, location, String.format( + context.client.report(context, EXTRA, location, String.format( "Locale %1$s is translating names not found in default locale: %2$s", - language, formatList(sorted, 4)), null); + language, LintUtils.formatList(sorted, 4)), null); } } } @@ -337,30 +342,6 @@ public class TranslationDetector extends ResourceXmlDetector { return location; } - private static Set<String> difference(Set<String> a, Set<String> b) { - HashSet<String> copy = new HashSet<String>(a); - copy.removeAll(b); - return copy; - } - - static String formatList(List<String> strings, int maxItems) { - StringBuilder sb = new StringBuilder(20 * strings.size()); - - for (int i = 0, n = strings.size(); i < n; i++) { - if (sb.length() > 0) { - sb.append(", "); //$NON-NLS-1$ - } - sb.append(strings.get(i)); - - if (i == maxItems - 1 && n > maxItems) { - sb.append(String.format("... (%1$d more)", n - i - 1)); - break; - } - } - - return sb.toString(); - } - @Override public void visitElement(Context context, Element element) { if (mIgnoreFile) { @@ -369,7 +350,7 @@ public class TranslationDetector extends ResourceXmlDetector { Attr attribute = element.getAttributeNode(ATTR_NAME); if (attribute == null || attribute.getValue().length() == 0) { - context.toolContext.report(context, MISSING, context.getLocation(element), + context.client.report(context, MISSING, context.getLocation(element), "Missing name attribute in <string> declaration", null); } else { String name = attribute.getValue(); @@ -379,12 +360,10 @@ public class TranslationDetector extends ResourceXmlDetector { return; } - // TODO: Consider string-arrays as well? - // Check for duplicate name definitions? No, because there can be // additional customizations like product= //if (mNames.contains(name)) { - // context.toolContext.report(ISSUE, context.getLocation(attribute), + // context.mClient.report(ISSUE, context.getLocation(attribute), // String.format("Duplicate name %1$s, already defined earlier in this file", // name)); //} diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java index 4af024f..4ec1551 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java @@ -16,12 +16,12 @@ package com.android.tools.lint.checks; -import com.android.resources.FolderTypeRelationship; -import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; +import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LintUtils; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Position; import com.android.tools.lint.detector.api.ResourceXmlDetector; @@ -57,7 +57,10 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec public static final Issue ISSUE = Issue.create("UnusedResources", //$NON-NLS-1$ "Looks for unused resources", "Unused resources make applications larger and slow down builds.", - CATEGORY_PERFORMANCE, 3, Severity.WARNING, + Category.PERFORMANCE, + 3, + Severity.WARNING, + UnusedResourceDetector.class, EnumSet.of(Scope.MANIFEST, Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES)); /** Unused id's */ public static final Issue ISSUE_IDS = Issue.create("UnusedIds", //$NON-NLS-1$ @@ -66,7 +69,10 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec "from anywhere. Having id definitions, even if unused, is not necessarily a bad " + "idea since they make working on layouts and menus easier, so there is not a " + "strong reason to delete these.", - CATEGORY_PERFORMANCE, 1, Severity.WARNING, + Category.PERFORMANCE, + 1, + Severity.WARNING, + UnusedResourceDetector.class, EnumSet.of(Scope.MANIFEST, Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES)) .setEnabledByDefault(false); @@ -83,11 +89,6 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public void run(Context context) { assert false; } @@ -109,14 +110,16 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec // TODO: Look up from project metadata File src = new File(context.project.getDir(), "src"); //$NON-NLS-1$ if (!src.exists()) { - context.toolContext.log(null, "Did not find source folder in project"); + context.client.log(null, "Did not find source folder in project %1$s", + context.project.getDir()); } else { scanJavaFile(context, src); } File gen = new File(context.project.getDir(), "gen"); //$NON-NLS-1$ if (!gen.exists()) { - context.toolContext.log(null, "Did not find gen folder in project"); + context.client.log(null, "Did not find gen folder in project %1$s", + context.project.getDir()); } else { scanJavaFile(context, gen); } @@ -146,7 +149,7 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec private void addJavaDeclarations(Context context, File file) { // mDeclarations - String s = context.toolContext.readFile(file); + String s = context.client.readFile(file); String[] lines = s.split("\n"); //$NON-NLS-1$ String currentType = null; for (int i = 0; i < lines.length; i++) { @@ -183,7 +186,7 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec /** Adds the resource identifiers found in the given file into the given set */ private void addJavaReferences(Context context, File file) { - String s = context.toolContext.readFile(file); + String s = context.client.readFile(file); if (s.length() <= 2) { return; } @@ -284,7 +287,7 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec mDeclarations.removeAll(mReferences); Set<String> unused = mDeclarations; - if (unused.size() > 0 && !context.toolContext.isEnabled(ISSUE_IDS)) { + if (unused.size() > 0 && !context.configuration.isEnabled(ISSUE_IDS)) { // Remove all R.id references List<String> ids = new ArrayList<String>(); for (String resource : unused) { @@ -309,11 +312,11 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec location = mAttrToLocation.get(attr); if (location == null) { File f = mAttrToFile.get(attr); - Position start = context.toolContext.getParser().getStartPosition(context, + Position start = context.client.getParser().getStartPosition(context, attr); Position end = null; if (start != null) { - end = context.toolContext.getParser().getEndPosition(context, attr); + end = context.client.getParser().getEndPosition(context, attr); } location = new Location(f, start, end); } @@ -325,7 +328,7 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec int secondDot = resource.indexOf('.', 2); String typeName = resource.substring(2, secondDot); // 2: Skip R. ResourceType type = ResourceType.getEnum(typeName); - if (type != null && isFileBasedResourceType(type)) { + if (type != null && LintUtils.isFileBasedResourceType(type)) { String name = resource.substring(secondDot + 1); File file = new File(context.project.getDir(), "res" + File.separator + typeName + File.separator + //$NON-NLS-1$ @@ -335,7 +338,7 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec } } } - context.toolContext.report(context, ISSUE, location, message, resource); + context.client.report(context, ISSUE, location, message, resource); } mReferences = null; @@ -345,21 +348,6 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec mDeclarations = null; } - /** Determine if the given type corresponds to a resource that has a unique file */ - private static boolean isFileBasedResourceType(ResourceType type) { - List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); - for (ResourceFolderType folderType : folderTypes) { - if (folderType != ResourceFolderType.VALUES) { - if (type == ResourceType.ID) { - return false; - } - return true; - } - } - return false; - } - - @Override public Collection<String> getApplicableAttributes() { return ALL; 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 index bdcf299..2a634e6 100644 --- 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 @@ -16,9 +16,17 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_WEIGHT; +import static com.android.tools.lint.detector.api.LintConstants.IMAGE_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.LINEAR_LAYOUT; +import static com.android.tools.lint.detector.api.LintConstants.TEXT_VIEW; + +import com.android.tools.lint.detector.api.Category; 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.LintUtils; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; @@ -41,18 +49,17 @@ public class UseCompoundDrawableDetector extends LayoutDetector { // 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, Scope.RESOURCE_FILE_SCOPE); + Category.PERFORMANCE, + 6, + Severity.WARNING, + UseCompoundDrawableDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Constructs a new {@link UseCompoundDrawableDetector} */ public UseCompoundDrawableDetector() { } @Override - public Issue[] getIssues() { - return new Issue[] { ISSUE }; - } - - @Override public Speed getSpeed() { return Speed.FAST; } @@ -66,9 +73,9 @@ public class UseCompoundDrawableDetector extends LayoutDetector { @Override public void visitElement(Context context, Element element) { - int childCount = getChildCount(element); + int childCount = LintUtils.getChildCount(element); if (childCount == 2) { - List<Element> children = getChildren(element); + List<Element> children = LintUtils.getChildren(element); Element first = children.get(0); Element second = children.get(1); if ((first.getTagName().equals(IMAGE_VIEW) && @@ -77,7 +84,7 @@ public class UseCompoundDrawableDetector extends LayoutDetector { ((second.getTagName().equals(IMAGE_VIEW) && first.getTagName().equals(TEXT_VIEW) && !second.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)))) { - context.toolContext.report(context, ISSUE, context.getLocation(element), + context.client.report(context, ISSUE, context.getLocation(element), "This tag and its children can be replaced by one <TextView/> and " + "a compound drawable", null); } 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 index becd401..6a5e3ab 100644 --- 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 @@ -16,9 +16,21 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_BACKGROUND; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_ID; +import static com.android.tools.lint.detector.api.LintConstants.FRAME_LAYOUT; +import static com.android.tools.lint.detector.api.LintConstants.GRID_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.HORIZONTAL_SCROLL_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.LINEAR_LAYOUT; +import static com.android.tools.lint.detector.api.LintConstants.MERGE; +import static com.android.tools.lint.detector.api.LintConstants.SCROLL_VIEW; + +import com.android.tools.lint.detector.api.Category; 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.LintUtils; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; @@ -43,7 +55,11 @@ public class UselessViewDetector extends LayoutDetector { "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_PERFORMANCE, 2, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.PERFORMANCE, + 2, + Severity.WARNING, + UselessViewDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** Issue of including a leaf that isn't shown */ public static final Issue USELESS_LEAF = Issue.create( @@ -51,18 +67,17 @@ public class UselessViewDetector extends LayoutDetector { "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_PERFORMANCE, 2, Severity.WARNING, Scope.RESOURCE_FILE_SCOPE); + Category.PERFORMANCE, + 2, + Severity.WARNING, + UselessViewDetector.class, + Scope.RESOURCE_FILE_SCOPE); /** 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; } @@ -106,7 +121,7 @@ public class UselessViewDetector extends LayoutDetector { @Override public void visitElement(Context context, Element element) { - int childCount = getChildCount(element); + int childCount = LintUtils.getChildCount(element); if (childCount == 0) { // Check to see if this is a leaf layout that can be removed checkUselessLeaf(context, element); @@ -141,9 +156,9 @@ public class UselessViewDetector extends LayoutDetector { } // This method is only called when we've already ensured that it has children - assert getChildCount(element) > 0; + assert LintUtils.getChildCount(element) > 0; - int parentChildCount = getChildCount(parent); + int parentChildCount = LintUtils.getChildCount(parent); if (parentChildCount != 1) { // Don't remove if the node has siblings return; @@ -176,12 +191,12 @@ public class UselessViewDetector extends LayoutDetector { format += "; transfer the background attribute to the other view"; } String message = String.format(format, tag, parentTag); - context.toolContext.report(context, USELESS_PARENT, location, message, null); + context.client.report(context, USELESS_PARENT, location, message, null); } // This is the old UselessView check from layoutopt private void checkUselessLeaf(Context context, Element element) { - assert getChildCount(element) == 0; + assert LintUtils.getChildCount(element) == 0; // Conditions: // - The node is a container view (LinearLayout, etc.) @@ -201,6 +216,6 @@ public class UselessViewDetector extends LayoutDetector { String tag = element.getTagName(); String message = String.format( "This %1$s view is useless (no children, no background, no id)", tag); - context.toolContext.report(context, USELESS_LEAF, location, message, null); + context.client.report(context, USELESS_LEAF, location, message, null); } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java index c26b84a..d4fd069 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/MainTest.java @@ -29,7 +29,7 @@ public class MainTest extends TestCase { "\n" + "* The application cannot be translated to other languages by just adding new " + "translations for existing string resources."; - String wrapped = Main.wrap(s, 70); + String wrapped = Main.wrap(s, 70, ""); assertEquals( "Hardcoding text attributes directly in layout files is bad for several\n" + "reasons:\n" + @@ -42,4 +42,27 @@ public class MainTest extends TestCase { "adding new translations for existing string resources.\n", wrapped); } + + public void testWrapPrefix() { + String s = + "Hardcoding text attributes directly in layout files is bad for several reasons:\n" + + "\n" + + "* When creating configuration variations (for example for landscape or portrait)" + + "you have to repeat the actual text (and keep it up to date when making changes)\n" + + "\n" + + "* The application cannot be translated to other languages by just adding new " + + "translations for existing string resources."; + String wrapped = Main.wrap(s, 70, " "); + assertEquals( + "Hardcoding text attributes directly in layout files is bad for several\n" + + " reasons:\n" + + " \n" + + " * When creating configuration variations (for example for\n" + + " landscape or portrait)you have to repeat the actual text (and keep\n" + + " it up to date when making changes)\n" + + " \n" + + " * The application cannot be translated to other languages by just\n" + + " adding new translations for existing string resources.\n", + wrapped); + } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/PositionXmlParserTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/PositionXmlParserTest.java index 038576c..af37f06 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/PositionXmlParserTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/PositionXmlParserTest.java @@ -106,6 +106,8 @@ public class PositionXmlParserTest extends TestCase { assertEquals(xml.indexOf("/>", start.getOffset()) + 2, end.getOffset()); assertEquals(16, end.getLine()); + parser.dispose(context); + file.delete(); } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java index 543bee8..3227f0d 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java @@ -17,15 +17,17 @@ 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.client.api.Configuration; +import com.android.tools.lint.client.api.IDomParser; +import com.android.tools.lint.client.api.IssueRegistry; +import com.android.tools.lint.client.api.Lint; +import com.android.tools.lint.client.api.LintClient; 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.Position; +import com.android.tools.lint.detector.api.Project; import com.android.tools.lint.detector.api.Severity; import java.io.BufferedReader; @@ -49,12 +51,25 @@ import junit.framework.TestCase; abstract class AbstractCheckTest extends TestCase { protected abstract Detector getDetector(); - private class CustomDetectorRegistry extends DetectorRegistry { + protected List<Issue> getIssues() { + List<Issue> issues = new ArrayList<Issue>(); + Class<? extends Detector> detectorClass = getDetector().getClass(); + // Get the list of issues from the registry and filter out others, to make sure + // issues are properly registered + List<Issue> candidates = new BuiltinIssueRegistry().getIssues(); + for (Issue issue : candidates) { + if (issue.getDetectorClass() == detectorClass) { + issues.add(issue); + } + } + + return issues; + } + + private class CustomIssueRegistry extends IssueRegistry { @Override - public List<? extends Detector> getDetectors() { - List<Detector> detectors = new ArrayList<Detector>(1); - detectors.add(AbstractCheckTest.this.getDetector()); - return detectors; + public List<Issue> getIssues() { + return AbstractCheckTest.this.getIssues(); } } @@ -73,12 +88,11 @@ abstract class AbstractCheckTest extends TestCase { protected String checkLint(List<File> files) throws Exception { mOutput = new StringBuilder(); - TestToolContext toolContext = new TestToolContext(); - Lint analyzer = new Lint(new CustomDetectorRegistry(), toolContext, - null); - analyzer.analyze(files); + TestLintClient lintClient = new TestLintClient(); + Lint analyzer = new Lint(new CustomIssueRegistry(), lintClient); + analyzer.analyze(files, null /* scope */); - List<String> errors = toolContext.getErrors(); + List<String> errors = lintClient.getErrors(); Collections.sort(errors); for (String error : errors) { if (mOutput.length() > 0) { @@ -140,9 +154,9 @@ abstract class AbstractCheckTest extends TestCase { base = new File("/tmp"); } Calendar c = Calendar.getInstance(); - String name = String.format("lintTests_%1$tF_%1$tT", c).replace(':', '-'); //$NON-NLS-1$ + 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()) { + if (!tmpDir.exists() && tmpDir.mkdirs()) { sTempDir = tmpDir; } else { sTempDir = base; @@ -231,10 +245,15 @@ abstract class AbstractCheckTest extends TestCase { } protected boolean isEnabled(Issue issue) { + Class<? extends Detector> detectorClass = getDetector().getClass(); + if (issue.getDetectorClass() == detectorClass) { + return true; + } + return false; } - private class TestToolContext extends ToolContext { + private class TestLintClient extends LintClient { private List<String> mErrors = new ArrayList<String>(); public List<String> getErrors() { @@ -263,7 +282,7 @@ abstract class AbstractCheckTest extends TestCase { sb.append(' '); } - Severity severity = getSeverity(issue); + Severity severity = context.configuration.getSeverity(issue); sb.append(severity.getDescription()); sb.append(": "); @@ -292,35 +311,36 @@ abstract class AbstractCheckTest extends TestCase { } @Override - public boolean isEnabled(Issue issue) { - for (Issue detectorIssue : getDetector().getIssues()) { - if (issue == detectorIssue) { - return true; - } + public String readFile(File file) { + try { + return AbstractCheckTest.readFile(new FileReader(file)); + } catch (Throwable e) { + fail(e.toString()); } + return null; + } - return AbstractCheckTest.this.isEnabled(issue); + @Override + public Configuration getConfiguration(Project project) { + return new TestConfiguration(); } + } + public class TestConfiguration extends Configuration { @Override - public boolean isSuppressed(Context context, Issue issue, Location range, String message, - Severity severity, Object data) { - return false; + public boolean isEnabled(Issue issue) { + return AbstractCheckTest.this.isEnabled(issue); } @Override - public Severity getSeverity(Issue issue) { - return issue.getDefaultSeverity(); + public void ignore(Context context, Issue issue, Location location, String message, + Object data) { + fail("Not supported in tests."); } @Override - public String readFile(File file) { - try { - return AbstractCheckTest.readFile(new FileReader(file)); - } catch (Throwable e) { - fail(e.toString()); - } - return null; + public void setSeverity(Issue issue, Severity severity) { + fail("Not supported in tests."); } } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ExportedServiceDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ExportedServiceDetectorTest.java index 874901a..839c199 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ExportedServiceDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ExportedServiceDetectorTest.java @@ -27,7 +27,7 @@ public class ExportedServiceDetectorTest extends AbstractCheckTest { public void testBroken() throws Exception { assertEquals( - "AndroidManifest.xml:12: Warning: Export service does not require permission", + "AndroidManifest.xml:12: Warning: Exported service does not require permission", lintProject( "exportservice1.xml=>AndroidManifest.xml", "res/values/strings.xml")); @@ -35,7 +35,7 @@ public class ExportedServiceDetectorTest extends AbstractCheckTest { public void testBroken2() throws Exception { assertEquals( - "AndroidManifest.xml:12: Warning: Export service does not require permission", + "AndroidManifest.xml:12: Warning: Exported service does not require permission", lintProject( "exportservice1.xml=>AndroidManifest.xml", "res/values/strings.xml")); diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java index f6a9727..2d4e807 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TranslationDetectorTest.java @@ -17,6 +17,7 @@ package com.android.tools.lint.checks; import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.LintUtils; import java.util.Arrays; @@ -69,18 +70,4 @@ public class TranslationDetectorTest extends AbstractCheckTest { "res/values-land/strings.xml", "res/values-nl-rNL/strings.xml")); } - - public void testPrintList() throws Exception { - assertEquals("foo, bar, baz", - TranslationDetector.formatList(Arrays.asList("foo", "bar", "baz"), 3)); - assertEquals("foo, bar, baz", - TranslationDetector.formatList(Arrays.asList("foo", "bar", "baz"), 5)); - - assertEquals("foo, bar, baz... (3 more)", - TranslationDetector.formatList( - Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 3)); - assertEquals("foo... (5 more)", - TranslationDetector.formatList( - Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 1)); - } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java index 498bfab..3dfb4f6 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/UnusedResourceDetectorTest.java @@ -32,9 +32,9 @@ public class UnusedResourceDetectorTest extends AbstractCheckTest { protected boolean isEnabled(Issue issue) { if (issue == UnusedResourceDetector.ISSUE_IDS) { return mEnableIds; + } else { + return true; } - - return super.isEnabled(issue); } public void testUnused() throws Exception { diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java new file mode 100644 index 0000000..e4c4372 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.lint.detector.api; + +import java.io.File; +import java.util.Arrays; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class LintUtilsTest extends TestCase { + public void testPrintList() throws Exception { + assertEquals("foo, bar, baz", + LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 3)); + assertEquals("foo, bar, baz", + LintUtils.formatList(Arrays.asList("foo", "bar", "baz"), 5)); + + assertEquals("foo, bar, baz... (3 more)", + LintUtils.formatList( + Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 3)); + assertEquals("foo... (5 more)", + LintUtils.formatList( + Arrays.asList("foo", "bar", "baz", "4", "5", "6"), 1)); + } + + public void testIsXmlFile() throws Exception { + assertTrue(LintUtils.isXmlFile(new File("foo.xml"))); + assertTrue(LintUtils.isXmlFile(new File("foo.Xml"))); + assertTrue(LintUtils.isXmlFile(new File("foo.XML"))); + + assertFalse(LintUtils.isXmlFile(new File("foo.png"))); + assertFalse(LintUtils.isXmlFile(new File("xml"))); + assertFalse(LintUtils.isXmlFile(new File("xml.png"))); + } +}
\ No newline at end of file |