diff options
author | Tor Norbye <tnorbye@google.com> | 2011-12-02 08:18:18 -0800 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2011-12-05 08:55:01 -0800 |
commit | 3ce45b249f898697ae82e8c6dd045966227f3438 (patch) | |
tree | 0f93dcdf3fb835a6a0afcb5faae5bf25ec53367f /lint | |
parent | 3f69598ca49f1672c6a1fd311fb4e3bac245512a (diff) | |
download | sdk-3ce45b249f898697ae82e8c6dd045966227f3438.zip sdk-3ce45b249f898697ae82e8c6dd045966227f3438.tar.gz sdk-3ce45b249f898697ae82e8c6dd045966227f3438.tar.bz2 |
Lint infrastructure improvements
This changeset fixes a bunch of issues in the infrastructure:
(1) It cleans up the Context class quite a bit. It had some hardcoded
XML stuff in it, which is now in a separate XmlContext class (and
there will be a JavaContext class in the Java support CL).
It also hides a bunch of public fields, cleans up some unused
stuff, and introduces a couple of wrapper methods to make detector
code cleaner; in particular, rather than calling
context.client.report(context, ...
you can now just call
context.report(...
and similarly there are wrappers for logging and checking for
disabled issues.
(2) The IParser interface is renamed to IDomParser since in the next
CL there will also be an IJavaParser. Some other related cleanup.
(3) There is now a "Location.Handle" interface. This allows detectors
to create light-weight location holders, and later on call
handle.resolve() to create a full-fledged Location. This is useful
when detectors don't yet know whether they'll need a location for
a node, but want to store it for later in case they do. As an
example, the unused resource detector creates location handles for
declaration and only resolves full locations for those that are
found to be unused.
Locations can now carry custom messages. For example, for a
duplicate id error, the secondary location now contains a
"original declaration here" message. And the CLI and HTML reports
now include alternate locations in the output.
Some other location cleanup too; using factory methods to make the
code cleaner, some default implementations that can be shared,
etc.
(4) There's a new SDK info class intended to provide SDK information
from a tool client (such as resource resolution). It currently
just contains parent-view information, used for the
ObsoleteLayoutParams detector and an upcoming CL for a
ViewTypeDetector.
(5) The Detector class now provides dummy implementations for the
inner-interfaces, so we no longer need the adapter classes. This
makes it easy to implement the XmlScanner or JavaScanner
interfaces without needing to also stub out a bunch of methods.
Change-Id: I4b3aaabe51febb25b000f9086703653bea6cf7c9
Diffstat (limited to 'lint')
62 files changed, 1546 insertions, 606 deletions
diff --git a/lint/cli/src/com/android/tools/lint/HtmlReporter.java b/lint/cli/src/com/android/tools/lint/HtmlReporter.java index 9ef0a9f..8f9ed4d 100644 --- a/lint/cli/src/com/android/tools/lint/HtmlReporter.java +++ b/lint/cli/src/com/android/tools/lint/HtmlReporter.java @@ -24,6 +24,7 @@ import com.android.tools.lint.detector.api.Category; 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.Severity; import java.io.BufferedWriter; @@ -55,6 +56,7 @@ class HtmlReporter extends Reporter { */ private static final int SHOWN_COUNT = SPLIT_LIMIT - 3; + private final Main mClient; private final File mOutput; private File mResources; private boolean mSimpleFormat; @@ -63,8 +65,9 @@ class HtmlReporter extends Reporter { private Map<File, String> mResourceUrl = new HashMap<File, String>(); private Map<String, File> mNameToFile = new HashMap<String, File>(); - HtmlReporter(File output) throws IOException { + HtmlReporter(Main client, File output) throws IOException { super(new BufferedWriter(new FileWriter(output))); + mClient = client; mOutput = output; } @@ -118,9 +121,9 @@ class HtmlReporter extends Reporter { "}\n" + //$NON-NLS-1$ ".moreinfo {\n" + //$NON-NLS-1$ "}\n" + //$NON-NLS-1$ - ".location {\n" + //$NON-NLS-1$ - " font-family: monospace;\n" + //$NON-NLS-1$ - "}\n" + + //".location {\n" + //$NON-NLS-1$ + //" font-family: monospace;\n" + //$NON-NLS-1$ + //"}\n" + // Preview images for icon issues: limit size to at most 200 in one dimension ".embedimage {\n" + //$NON-NLS-1$ " max-width: 200px;\n" + //$NON-NLS-1$ @@ -262,25 +265,7 @@ class HtmlReporter extends Reporter { count++; String url = null; if (warning.path != null) { - mWriter.write("<span class=\"location\">"); //$NON-NLS-1$ - - url = getUrl(warning.file); - if (url != null) { - mWriter.write("<a href=\""); //$NON-NLS-1$ - mWriter.write(url); - mWriter.write("\">"); //$NON-NLS-1$ - } - mWriter.write(warning.path); - if (url != null) { - mWriter.write("</a>"); //$NON-NLS-1$ - } - mWriter.write(':'); - if (warning.line >= 0) { - // 0-based line numbers, but display 1-based - mWriter.write(Integer.toString(warning.line + 1) + ':'); - } - mWriter.write("</span>"); //$NON-NLS-1$ - mWriter.write(' '); + url = writeLocation(warning.file, warning.path, warning.line); } // Is the URL for a single image? If so, place it here near the top @@ -307,6 +292,34 @@ class HtmlReporter extends Reporter { mWriter.write("\n</pre>"); //$NON-NLS-1$ } mWriter.write('\n'); + if (warning.location != null && warning.location.getSecondary() != null) { + mWriter.write("<ul>"); + Location l = warning.location.getSecondary(); + while (l != null) { + if (l.getMessage() != null && l.getMessage().length() > 0) { + Position start = l.getStart(); + int line = start != null ? start.getLine() : -1; + int offset = start != null ? start.getOffset() : -1; + String path = mClient.getDisplayPath(warning.project, l.getFile()); + writeLocation(l.getFile(), path, line); + mWriter.write("<span class=\"message\">"); //$NON-NLS-1$ + appendEscapedText(l.getMessage()); + mWriter.write("</span>"); //$NON-NLS-1$ + mWriter.write("<br />"); //$NON-NLS-1$ + + String s = mClient.readFile(l.getFile()); + if (s != null && s.length() > 0) { + mWriter.write("<pre class=\"errorlines\">\n"); //$NON-NLS-1$ + appendCodeBlock(s, line, offset); + mWriter.write("\n</pre>"); //$NON-NLS-1$ + } + } + + l = l.getSecondary(); + } + mWriter.write("</ul>"); + + } // Place a block of images? if (!addedImage && url != null && warning.location != null @@ -379,6 +392,30 @@ class HtmlReporter extends Reporter { System.out.println(String.format("Wrote HTML report to %1$s", path)); } + private String writeLocation(File file, String path, int line) throws IOException { + String url; + mWriter.write("<span class=\"location\">"); //$NON-NLS-1$ + + url = getUrl(file); + if (url != null) { + mWriter.write("<a href=\""); //$NON-NLS-1$ + mWriter.write(url); + mWriter.write("\">"); //$NON-NLS-1$ + } + mWriter.write(path); + if (url != null) { + mWriter.write("</a>"); //$NON-NLS-1$ + } + mWriter.write(':'); + if (line >= 0) { + // 0-based line numbers, but display 1-based + mWriter.write(Integer.toString(line + 1) + ':'); + } + mWriter.write("</span>"); //$NON-NLS-1$ + mWriter.write(' '); + return url; + } + private boolean addImage(String url, Location location) throws IOException { if (url != null && endsWith(url, DOT_PNG) && !endsWith(url, DOT_9PNG)) { if (location.getSecondary() != null) { diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java index b3c66bf..02bb070 100644 --- a/lint/cli/src/com/android/tools/lint/Main.java +++ b/lint/cli/src/com/android/tools/lint/Main.java @@ -249,7 +249,7 @@ public class Main extends LintClient { System.exit(ERRNO_EXISTS); } try { - HtmlReporter htmlReporter = new HtmlReporter(output); + HtmlReporter htmlReporter = new HtmlReporter(this, output); if (arg.equals(ARG_SIMPLEHTML)) { htmlReporter.setSimpleFormat(true); } @@ -392,7 +392,7 @@ public class Main extends LintClient { ARG_URL, ARG_HTML)); } - mReporter = new TextReporter(new PrintWriter(System.out, true)); + mReporter = new TextReporter(this, new PrintWriter(System.out, true)); } else if (mReporter instanceof HtmlReporter) { HtmlReporter htmlReporter = (HtmlReporter) mReporter; @@ -524,7 +524,6 @@ public class Main extends LintClient { return wrap(explanation, MAX_LINE_WIDTH, " "); } - static String wrap(String explanation) { return wrap(explanation, MAX_LINE_WIDTH, ""); } @@ -645,6 +644,12 @@ public class Main extends LintClient { @Override public void log(Throwable exception, String format, Object... args) { + System.out.flush(); + if (!mQuiet) { + // Place the error message on a line of its own since we're printing '.' etc + // with newlines during analysis + System.err.println(); + } if (format != null) { System.err.println(String.format(format, args)); } @@ -654,7 +659,7 @@ public class Main extends LintClient { } @Override - public IDomParser getParser() { + public IDomParser getDomParser() { return new PositionXmlParser(); } @@ -666,9 +671,9 @@ public class Main extends LintClient { @Override public void report(Context context, Issue issue, Location location, String message, Object data) { - assert context.configuration.isEnabled(issue); + assert context.isEnabled(issue); - Severity severity = context.configuration.getSeverity(issue); + Severity severity = context.getConfiguration().getSeverity(issue); if (severity == Severity.IGNORE) { return; } @@ -679,7 +684,7 @@ public class Main extends LintClient { mWarningCount++; } - Warning warning = new Warning(issue, message, severity, data); + Warning warning = new Warning(issue, message, severity, context.getProject(), data); mWarnings.add(warning); if (location != null) { @@ -687,16 +692,7 @@ public class Main extends LintClient { File file = location.getFile(); if (file != null) { warning.file = file; - - String path = file.getPath(); - if (!mFullPath && path.startsWith(context.project.getReferenceDir().getPath())) { - int chop = context.project.getReferenceDir().getPath().length(); - if (path.length() > chop && path.charAt(chop) == File.separatorChar) { - chop++; - } - path = path.substring(chop); - } - warning.path = path; + warning.path = getDisplayPath(context.getProject(), file); } Position startPosition = location.getStart(); @@ -709,19 +705,19 @@ public class Main extends LintClient { warning.fileContents = context.getContents(); } if (warning.fileContents == null) { - warning.fileContents = context.client.readFile(location.getFile()); + warning.fileContents = readFile(location.getFile()); } if (mShowLines) { // Compute error line contents - warning.errorLine = getLine(context.getContents(), line); + warning.errorLine = getLine(warning.fileContents, line); if (warning.errorLine != null) { // Replace tabs with spaces such that the column // marker (^) lines up properly: warning.errorLine = warning.errorLine.replace('\t', ' '); int column = startPosition.getColumn(); if (column < 0) { - column = 0; + column = 1; for (int i = 0; i < warning.errorLine.length(); i++, column++) { if (!Character.isWhitespace(warning.errorLine.charAt(i))) { break; @@ -874,7 +870,7 @@ public class Main extends LintClient { case SCANNING_PROJECT: System.out.print(String.format( "\nScanning %1$s: ", - context.project.getDir().getName())); + context.getProject().getDir().getName())); break; case SCANNING_FILE: System.out.print('.'); @@ -886,4 +882,17 @@ public class Main extends LintClient { } } } + + String getDisplayPath(Project project, File file) { + String path = file.getPath(); + if (!mFullPath && path.startsWith(project.getReferenceDir().getPath())) { + int chop = project.getReferenceDir().getPath().length(); + if (path.length() > chop && path.charAt(chop) == File.separatorChar) { + chop++; + } + path = path.substring(chop); + } + + return path; + } } diff --git a/lint/cli/src/com/android/tools/lint/PositionXmlParser.java b/lint/cli/src/com/android/tools/lint/PositionXmlParser.java index a243ab6..0515993 100644 --- a/lint/cli/src/com/android/tools/lint/PositionXmlParser.java +++ b/lint/cli/src/com/android/tools/lint/PositionXmlParser.java @@ -20,7 +20,9 @@ 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.Location; +import com.android.tools.lint.detector.api.Location.Handle; import com.android.tools.lint.detector.api.Position; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Document; @@ -32,6 +34,7 @@ import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import java.io.File; import java.io.StringReader; import java.util.ArrayList; import java.util.List; @@ -58,7 +61,7 @@ public class PositionXmlParser implements IDomParser { // ---- Implements IDomParser ---- - public Document parse(Context context) { + public Document parseXml(XmlContext context) { return parse(context, context.getContents(), true); } @@ -74,7 +77,7 @@ public class PositionXmlParser implements IDomParser { parser.parse(input, handler); return handler.getDocument(); } catch (ParserConfigurationException e) { - context.client.log(e, null); + context.log(e, null); } catch (SAXException e) { if (checkBom && e.getMessage().contains("Content is not allowed in prolog")) { // Byte order mark in the string? Skip it. There are many markers @@ -83,22 +86,20 @@ public class PositionXmlParser implements IDomParser { xml = xml.replaceFirst("^([\\W]+)<","<"); //$NON-NLS-1$ //$NON-NLS-2$ return parse(context, xml, false); } - context.client.report( - context, + context.report( // Must provide an issue since API guarantees that the issue parameter // is valid - IssueRegistry.PARSER_ERROR, - new Location(context.file, null, null), + IssueRegistry.PARSER_ERROR, Location.create(context.file), e.getCause() != null ? e.getCause().getLocalizedMessage() : e.getLocalizedMessage(), null); } catch (Throwable t) { - context.client.log(t, null); + context.log(t, null); } return null; } - public Position getStartPosition(Context context, Node node) { + static Position getPositions(Node node) { // Look up the position information stored while parsing for the given node. // Note however that we only store position information for elements (because // there is no SAX callback for individual attributes). @@ -155,18 +156,17 @@ public class PositionXmlParser implements IDomParser { return (OffsetPosition) node.getUserData(POS_KEY); } - public Position getEndPosition(Context context, Node node) { - OffsetPosition pos = (OffsetPosition) getStartPosition(context, node); - if (pos != null && pos.next != null) { - return pos.next; + public Location getLocation(XmlContext context, Node node) { + OffsetPosition pos = (OffsetPosition) getPositions(node); + if (pos != null) { + return Location.create(context.file, pos, pos.next); } return null; } - public Location getLocation(Context context, Node node) { - return new Location(context.file, getStartPosition(context, node), - getEndPosition(context, node)); + public Handle createLocationHandle(XmlContext context, Node node) { + return new LocationHandle(context.file, node); } /** @@ -378,6 +378,26 @@ public class PositionXmlParser implements IDomParser { } } - public void dispose(Context context) { + public void dispose(XmlContext context, Document document) { + } + + /* Handle for creating DOM positions cheaply and returning full fledged locations later */ + private class LocationHandle implements Handle { + private File mFile; + private Node mNode; + + public LocationHandle(File file, Node node) { + mFile = file; + mNode = node; + } + + public Location resolve() { + OffsetPosition pos = (OffsetPosition) getPositions(mNode); + if (pos != null) { + return Location.create(mFile, pos, pos.next); + } + + return null; + } } } diff --git a/lint/cli/src/com/android/tools/lint/TextReporter.java b/lint/cli/src/com/android/tools/lint/TextReporter.java index c5059a7..8a71bbf 100644 --- a/lint/cli/src/com/android/tools/lint/TextReporter.java +++ b/lint/cli/src/com/android/tools/lint/TextReporter.java @@ -16,14 +16,20 @@ package com.android.tools.lint; +import com.android.tools.lint.detector.api.Location; +import com.android.tools.lint.detector.api.Position; + import java.io.IOException; import java.io.Writer; import java.util.List; /** A reporter which emits lint warnings as plain text strings */ class TextReporter extends Reporter { - TextReporter(Writer writer) { + private Main mClient; + + TextReporter(Main client, Writer writer) { super(writer); + mClient = client; } @Override @@ -64,6 +70,39 @@ class TextReporter extends Reporter { if (warning.errorLine != null) { output.append(warning.errorLine); } + + if (warning.location != null && warning.location.getSecondary() != null) { + Location location = warning.location.getSecondary(); + int count = 0; + while (location != null) { + if (location.getMessage() != null && location.getMessage().length() > 0) { + String path = mClient.getDisplayPath(warning.project, + location.getFile()); + output.append(path); + output.append(':'); + + Position start = location.getStart(); + if (start != null) { + int line = start.getLine(); + if (line >= 0) { + output.append(Integer.toString(line + 1)); + output.append(':'); + } + } + + output.append(' '); + output.append(location.getMessage()); + output.append('\n'); + count++; + if (count == 5) { + output.append("..."); + output.append('\n'); + } + } + + location = location.getSecondary(); + } + } } System.out.println(output.toString()); diff --git a/lint/cli/src/com/android/tools/lint/Warning.java b/lint/cli/src/com/android/tools/lint/Warning.java index 35b6d8e..19138bc 100644 --- a/lint/cli/src/com/android/tools/lint/Warning.java +++ b/lint/cli/src/com/android/tools/lint/Warning.java @@ -19,6 +19,7 @@ package com.android.tools.lint; 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.Project; import com.android.tools.lint.detector.api.Severity; import java.io.File; @@ -34,6 +35,7 @@ class Warning implements Comparable<Warning> { public final String message; public final Severity severity; public final Object data; + public final Project project; public Location location; public File file; public String path; @@ -42,11 +44,11 @@ class Warning implements Comparable<Warning> { public String errorLine; public String fileContents; - public Warning(Issue issue, String message, Severity severity, Object data) { - super(); + public Warning(Issue issue, String message, Severity severity, Project project, Object data) { this.issue = issue; this.message = message; this.severity = severity; + this.project = project; this.data = data; } 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 index f2130f3..168e759 100644 --- 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 @@ -126,7 +126,7 @@ public class DefaultConfiguration extends Configuration { List<String> paths = mSuppressed.get(id); if (paths != null && location != null) { File file = location.getFile(); - String relativePath = context.project.getRelativePath(file); + String relativePath = context.getProject().getRelativePath(file); for (String suppressedPath : paths) { if (suppressedPath.equals(relativePath)) { return true; diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/DefaultSdkInfo.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/DefaultSdkInfo.java new file mode 100644 index 0000000..3274ee0 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/DefaultSdkInfo.java @@ -0,0 +1,191 @@ +/* + * 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 static com.android.tools.lint.detector.api.LintConstants.ABSOLUTE_LAYOUT; +import static com.android.tools.lint.detector.api.LintConstants.ABS_LIST_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.ABS_SEEK_BAR; +import static com.android.tools.lint.detector.api.LintConstants.ABS_SPINNER; +import static com.android.tools.lint.detector.api.LintConstants.ADAPTER_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.BUTTON; +import static com.android.tools.lint.detector.api.LintConstants.CHECK_BOX; +import static com.android.tools.lint.detector.api.LintConstants.COMPOUND_BUTTON; +import static com.android.tools.lint.detector.api.LintConstants.EDIT_TEXT; +import static com.android.tools.lint.detector.api.LintConstants.EXPANDABLE_LIST_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.FRAME_LAYOUT; +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.IMAGE_BUTTON; +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.LIST_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.PROGRESS_BAR; +import static com.android.tools.lint.detector.api.LintConstants.RADIO_BUTTON; +import static com.android.tools.lint.detector.api.LintConstants.RADIO_GROUP; +import static com.android.tools.lint.detector.api.LintConstants.RELATIVE_LAYOUT; +import static com.android.tools.lint.detector.api.LintConstants.SCROLL_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.SEEK_BAR; +import static com.android.tools.lint.detector.api.LintConstants.SPINNER; +import static com.android.tools.lint.detector.api.LintConstants.SURFACE_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.TABLE_LAYOUT; +import static com.android.tools.lint.detector.api.LintConstants.TABLE_ROW; +import static com.android.tools.lint.detector.api.LintConstants.TAB_HOST; +import static com.android.tools.lint.detector.api.LintConstants.TAB_WIDGET; +import static com.android.tools.lint.detector.api.LintConstants.TEXT_VIEW; +import static com.android.tools.lint.detector.api.LintConstants.TOGGLE_BUTTON; +import static com.android.tools.lint.detector.api.LintConstants.VIEW; +import static com.android.tools.lint.detector.api.LintConstants.VIEW_ANIMATOR; +import static com.android.tools.lint.detector.api.LintConstants.VIEW_GROUP; +import static com.android.tools.lint.detector.api.LintConstants.VIEW_PKG_PREFIX; +import static com.android.tools.lint.detector.api.LintConstants.VIEW_STUB; +import static com.android.tools.lint.detector.api.LintConstants.VIEW_SWITCHER; +import static com.android.tools.lint.detector.api.LintConstants.WIDGET_PKG_PREFIX; + +import java.util.HashMap; +import java.util.Map; + +/** Default simple implementation of an {@link SdkInfo} */ +class DefaultSdkInfo extends SdkInfo { + @Override + public String getParentViewName(String name) { + return PARENTS.get(name); + } + + @Override + public String getParentViewClass(String fqcn) { + int index = fqcn.lastIndexOf('.'); + if (index != -1) { + fqcn = fqcn.substring(index + 1); + } + + String parent = PARENTS.get(fqcn); + if (parent == null) { + return null; + } + // The map only stores class names internally; correct for full package paths + if (parent.equals(VIEW) || parent.equals(VIEW_GROUP) || parent.equals(SURFACE_VIEW)) { + return VIEW_PKG_PREFIX + parent; + } else { + return WIDGET_PKG_PREFIX + parent; + } + } + + @Override + public boolean isSubViewOf(String parent, String child) { + // Do analysis just on non-fqcn paths + if (parent.indexOf('.') != -1) { + parent = parent.substring(parent.lastIndexOf('.') + 1); + } + if (child.indexOf('.') != -1) { + child = child.substring(child.lastIndexOf('.') + 1); + } + while (!child.equals(VIEW)) { + if (parent.equals(child)) { + return true; + } + child = PARENTS.get(child); + if (child == null) { + // Unknown view - err on the side of caution + return true; + } + } + + return false; + } + + private static final int CLASS_COUNT = 56; + private static final Map<String, String> PARENTS = new HashMap<String, String>(CLASS_COUNT); + static { + PARENTS.put(COMPOUND_BUTTON, VIEW); + PARENTS.put(ABS_SPINNER, ADAPTER_VIEW); + PARENTS.put(ABS_LIST_VIEW, ADAPTER_VIEW); + PARENTS.put(ABS_SEEK_BAR, ADAPTER_VIEW); + PARENTS.put(ADAPTER_VIEW, VIEW_GROUP); + PARENTS.put(VIEW_GROUP, VIEW); + + PARENTS.put(TEXT_VIEW, VIEW); + PARENTS.put(RADIO_BUTTON, COMPOUND_BUTTON); + PARENTS.put(SPINNER, ABS_SPINNER); + PARENTS.put(IMAGE_BUTTON, IMAGE_VIEW); + PARENTS.put(IMAGE_VIEW, VIEW); + PARENTS.put(EDIT_TEXT, TEXT_VIEW); + PARENTS.put(PROGRESS_BAR, VIEW); + PARENTS.put(TOGGLE_BUTTON, COMPOUND_BUTTON); + PARENTS.put(VIEW_STUB, VIEW); + PARENTS.put(BUTTON, TEXT_VIEW); + PARENTS.put(SEEK_BAR, ABS_SEEK_BAR); + PARENTS.put(CHECK_BOX, COMPOUND_BUTTON); + PARENTS.put(GALLERY, ABS_SPINNER); + PARENTS.put(SURFACE_VIEW, VIEW); + PARENTS.put(ABSOLUTE_LAYOUT, VIEW_GROUP); + PARENTS.put(LINEAR_LAYOUT, VIEW_GROUP); + PARENTS.put(RELATIVE_LAYOUT, VIEW_GROUP); + PARENTS.put(LIST_VIEW, ABS_LIST_VIEW); + PARENTS.put(VIEW_SWITCHER, VIEW_ANIMATOR); + PARENTS.put(FRAME_LAYOUT, VIEW_GROUP); + PARENTS.put(HORIZONTAL_SCROLL_VIEW, FRAME_LAYOUT); + PARENTS.put(VIEW_ANIMATOR, FRAME_LAYOUT); + PARENTS.put(TAB_HOST, FRAME_LAYOUT); + PARENTS.put(TABLE_ROW, LINEAR_LAYOUT); + PARENTS.put(RADIO_GROUP, LINEAR_LAYOUT); + PARENTS.put(TAB_WIDGET, LINEAR_LAYOUT); + PARENTS.put(EXPANDABLE_LIST_VIEW, LIST_VIEW); + PARENTS.put(TABLE_LAYOUT, LINEAR_LAYOUT); + PARENTS.put(SCROLL_VIEW, FRAME_LAYOUT); + PARENTS.put(GRID_VIEW, ABS_LIST_VIEW); + + PARENTS.put("CheckedTextView", TEXT_VIEW); //$NON-NLS-1$ + PARENTS.put("MediaController", FRAME_LAYOUT); //$NON-NLS-1$ + PARENTS.put("SlidingDrawer", VIEW_GROUP); //$NON-NLS-1$ + PARENTS.put("DialerFilter", RELATIVE_LAYOUT); //$NON-NLS-1$ + PARENTS.put("DigitalClock", TEXT_VIEW); //$NON-NLS-1$ + PARENTS.put("Chronometer", TEXT_VIEW); //$NON-NLS-1$ + PARENTS.put("ImageSwitcher", VIEW_SWITCHER); //$NON-NLS-1$ + PARENTS.put("TextSwitcher", VIEW_SWITCHER); //$NON-NLS-1$ + PARENTS.put("AnalogClock", VIEW); //$NON-NLS-1$ + PARENTS.put("TwoLineListItem", RELATIVE_LAYOUT); //$NON-NLS-1$ + PARENTS.put("ZoomControls", LINEAR_LAYOUT); //$NON-NLS-1$ + PARENTS.put("DatePicker", FRAME_LAYOUT); //$NON-NLS-1$ + PARENTS.put("TimePicker", FRAME_LAYOUT); //$NON-NLS-1$ + PARENTS.put("VideoView", SURFACE_VIEW); //$NON-NLS-1$ + PARENTS.put("ZoomButton", IMAGE_BUTTON); //$NON-NLS-1$ + PARENTS.put("AutoCompleteTextView", EDIT_TEXT); //$NON-NLS-1$ + PARENTS.put("RatingBar", ABS_SEEK_BAR); //$NON-NLS-1$ + PARENTS.put("ViewFlipper", VIEW_ANIMATOR); //$NON-NLS-1$ + PARENTS.put("NumberPicker", LINEAR_LAYOUT); //$NON-NLS-1$ + PARENTS.put("MultiAutoCompleteTextView", //$NON-NLS-1$ + "AutoCompleteTextView"); //$NON-NLS-1$ + + assert PARENTS.size() == CLASS_COUNT; + + /* + // Check that all widgets lead to the root view + boolean assertionsEnabled = false; + assert assertionsEnabled = true; // Intentional side-effect + if (assertionsEnabled) { + for (String key : PARENTS.keySet()) { + String parent = PARENTS.get(key); + if (!parent.equals(VIEW)) { + String grandParent = PARENTS.get(parent); + assert grandParent != null : parent; + } + } + } + */ + } +} diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IDomParser.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IDomParser.java index d1d9461..9c40bd8 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IDomParser.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IDomParser.java @@ -18,14 +18,13 @@ package com.android.tools.lint.client.api; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Location; -import com.android.tools.lint.detector.api.Position; +import com.android.tools.lint.detector.api.XmlContext; -import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Node; /** - * A wrapper for XML parser. This allows tools integrating lint to map directly + * A wrapper for an XML parser. This allows tools integrating lint to map directly * to builtin services, such as already-parsed data structures in XML editors. * <p/> * <b>NOTE: This is not a public or final API; if you rely on this be prepared @@ -41,41 +40,33 @@ public interface IDomParser { * editor buffer in the surrounding tool, etc) * @return the parsed DOM document, or null if parsing fails */ - public Document parse(Context context); + Document parseXml(XmlContext context); /** - * Returns the starting position of the given DOM node (which may not be - * just an element but can for example also be an {@link Attr} node). The - * node will *always* be from the same DOM document that was returned by - * this parser. - * - * @param context information about the file being parsed - * @param node the node to look up a starting position for - * @return the position of the beginning of the node - */ - public Position getStartPosition(Context context, Node node); - - /** - * Returns the ending position of the given DOM node. + * Returns a {@link Location} for the given DOM node * * @param context information about the file being parsed - * @param node the node to look up a ending position for - * @return the position of the end of the node + * @param node the node to create a location for + * @return a location for the given node */ - public Position getEndPosition(Context context, Node node); + Location getLocation(XmlContext context, Node node); /** - * Returns a {@link Location} for the given DOM node + * Creates a light-weight handle to a location for the given node. It can be + * turned into a full fledged location by + * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}. * - * @param context information about the file being parsed - * @param node the node to create a location for - * @return a location for the given node + * @param context the context providing the node + * @param node the node (element or attribute) to create a location handle + * for + * @return a location handle */ - public Location getLocation(Context context, Node node); + Location.Handle createLocationHandle(XmlContext context, Node node); /** * Dispose any data structures held for the given context. * @param context information about the file previously parsed + * @param document the document that was parsed and is now being disposed */ - public void dispose(Context context); + void dispose(XmlContext context, Document document); } 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 index c01f7c3..0c11ba9 100644 --- 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 @@ -83,35 +83,34 @@ public abstract class IssueRegistry { new HashMap<Class<? extends Detector>, EnumSet<Scope>>(); for (Issue issue : issues) { Class<? extends Detector> detectorClass = issue.getDetectorClass(); - if (detectorClasses.contains(detectorClass)) { - continue; - } + EnumSet<Scope> issueScope = issue.getScope(); + if (!detectorClasses.contains(detectorClass)) { + // Determine if the issue is enabled + if (!configuration.isEnabled(issue)) { + continue; + } - // Determine if the issue is enabled - if (!configuration.isEnabled(issue)) { - continue; - } + // Determine if the scope matches + if (!scope.containsAll(issueScope)) { + continue; + } - // Determine if the scope matches - if (!scope.containsAll(issue.getScope())) { - continue; - } + detectorClass = client.replaceDetector(detectorClass); - detectorClass = client.replaceDetector(detectorClass); + assert detectorClass != null : issue.getId(); + detectorClasses.add(detectorClass); + } if (scopeToDetectors != null) { EnumSet<Scope> s = detectorToScope.get(detectorClass); if (s == null) { - detectorToScope.put(detectorClass, issue.getScope()); - } else { + detectorToScope.put(detectorClass, issueScope); + } else if (!s.containsAll(issueScope)) { EnumSet<Scope> union = EnumSet.copyOf(s); - union.addAll(issue.getScope()); + union.addAll(issueScope); detectorToScope.put(detectorClass, union); } } - - assert detectorClass != null : issue.getId(); - detectorClasses.add(detectorClass); } List<Detector> detectors = new ArrayList<Detector>(detectorClasses.size()); diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java index 336e067..42c2ec7 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java @@ -31,6 +31,7 @@ import com.android.tools.lint.detector.api.Project; 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.XmlContext; import java.io.File; import java.util.ArrayList; @@ -332,10 +333,9 @@ public class Lint { // Look up manifest information File manifestFile = new File(project.getDir(), ANDROID_MANIFEST_XML); if (manifestFile.exists()) { - Context context = new Context(mClient, project, manifestFile, mScope); - context.location = new Location(manifestFile, null, null); - IDomParser parser = mClient.getParser(); - context.document = parser.parse(context); + XmlContext context = new XmlContext(mClient, project, manifestFile, mScope); + IDomParser parser = mClient.getDomParser(); + context.document = parser.parseXml(context); if (context.document != null) { project.readManifest(context.document); @@ -412,7 +412,6 @@ public class Lint { if (file.exists()) { 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)) { detector.beforeCheckFile(context); @@ -493,7 +492,7 @@ public class Lint { return null; } - mCurrentVisitor = new XmlVisitor(mClient.getParser(), applicableChecks); + mCurrentVisitor = new XmlVisitor(mClient.getDomParser(), applicableChecks); } return mCurrentVisitor; @@ -532,7 +531,7 @@ public class Lint { if (visitor != null) { // if not, there are no applicable rules in this folder for (File file : xmlFiles) { if (LintUtils.isXmlFile(file)) { - Context context = new Context(mClient, project, file, mScope); + XmlContext context = new XmlContext(mClient, project, file, mScope); fireEvent(EventType.SCANNING_FILE, context); visitor.visitFile(context, file); if (mCanceled) { @@ -569,7 +568,7 @@ public class Lint { if (type != null) { XmlVisitor visitor = getVisitor(type, xmlDetectors); if (visitor != null) { - Context context = new Context(mClient, project, file, mScope); + XmlContext context = new XmlContext(mClient, project, file, mScope); fireEvent(EventType.SCANNING_FILE, context); visitor.visitFile(context, file); } @@ -630,7 +629,7 @@ public class Lint { @Override public void report(Context context, Issue issue, Location location, String message, Object data) { - Configuration configuration = context.configuration; + Configuration configuration = context.getConfiguration(); if (!configuration.isEnabled(issue)) { if (issue != IssueRegistry.PARSER_ERROR) { mDelegate.log(null, "Incorrect detector reported disabled issue %1$s", @@ -665,11 +664,6 @@ public class Lint { } @Override - public IDomParser getParser() { - return mDelegate.getParser(); - } - - @Override public String readFile(File file) { return mDelegate.readFile(file); } @@ -683,5 +677,10 @@ public class Lint { public List<File> getJavaClassFolders(Project project) { return mDelegate.getJavaClassFolders(project); } + + @Override + public IDomParser getDomParser() { + return mDelegate.getDomParser(); + } } } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java index cae89b6..db138c2 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java @@ -89,6 +89,13 @@ public abstract class LintClient { 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 getDomParser(); + + /** * 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. @@ -101,13 +108,6 @@ public abstract class LintClient { } /** - * Returns a {@link IDomParser} to use to parse XML - * - * @return a new {@link IDomParser} - */ - public abstract IDomParser getParser(); - - /** * Reads the given text file and returns the content as a string * * @param file the file to read @@ -136,6 +136,17 @@ public abstract class LintClient { } /** + * Returns the {@link SdkInfo} to use for the given project. + * + * @param project the project to look up an {@link SdkInfo} for + * @return an {@link SdkInfo} for the project + */ + public SdkInfo getSdkInfo(Project project) { + // By default no per-platform SDK info + return new DefaultSdkInfo(); + } + + /** * Considers the given directory as an Eclipse project and returns either * its source or its output folders depending on the {@code attribute} parameter. */ 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 index 1852f87..6c5c049 100644 --- 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 @@ -24,22 +24,27 @@ public interface LintListener { public enum EventType { /** A lint check is about to begin */ STARTING, - /** Lint is about to check the given project, see {@link Context#project} */ + + /** Lint is about to check the given project, see {@link Context#getProject()} */ 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.) + * 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 diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/SdkInfo.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/SdkInfo.java new file mode 100644 index 0000000..6974f22 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/SdkInfo.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** Information about SDKs */ +public abstract class SdkInfo { + /** + * Returns true if the given child view is the same class or a sub class of + * the given parent view class + * + * @param parentViewFqcn the fully qualified class name of the parent view + * @param childViewFqcn the fully qualified class name of the child view + * @return true if the child view is a sub view of (or the same class as) + * the parent view + */ + public boolean isSubViewOf(String parentViewFqcn, String childViewFqcn) { + while (!childViewFqcn.equals("android.view.View")) { //$NON-NLS-1$ + if (parentViewFqcn.equals(childViewFqcn)) { + return true; + } + childViewFqcn = getParentViewClass(childViewFqcn); + if (childViewFqcn == null) { + // Unknown view - err on the side of caution + return true; + } + } + + return false; + } + + + /** + * Returns the fully qualified name of the parent view, or null if the view + * is the root android.view.View class. + * + * @param fqcn the fully qualified class name of the view + * @return the fully qualified class name of the parent view, or null + */ + public abstract String getParentViewClass(String fqcn); + + /** + * Returns the class name of the parent view, or null if the view is the + * root android.view.View class. This is the same as the + * {@link #getParentViewClass(String)} but without the package. + * + * @param name the view class name to look up the parent for (not including + * package) + * @return the view name of the parent + */ + public abstract String getParentViewName(String name); + + // TODO: Add access to resource resolution here. +} diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java index a209e4c..ce0a03e 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java @@ -16,10 +16,10 @@ 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.LintUtils; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -112,15 +112,13 @@ class XmlVisitor { } } - void visitFile(Context context, File file) { + void visitFile(XmlContext context, File file) { assert LintUtils.isXmlFile(file); - - context.location = null; context.parser = mParser; try { if (context.document == null) { - context.document = mParser.parse(context); + context.document = mParser.parseXml(context); if (context.document == null) { // No need to log this; the parser should be reporting // a full warning (such as IssueRegistry#PARSER_ERROR) @@ -150,13 +148,14 @@ class XmlVisitor { check.afterCheckFile(context); } } finally { - mParser.dispose(context); + if (context.document != null) { + mParser.dispose(context, context.document); + context.document = null; + } } } - private void visitElement(Context context, Element element) { - context.element = element; - + private void visitElement(XmlContext context, Element element) { List<Detector.XmlScanner> elementChecks = mElementToCheck.get(element.getTagName()); if (elementChecks != null) { assert elementChecks instanceof RandomAccess; 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 d80144a..9bf9a79 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 @@ -17,12 +17,8 @@ package com.android.tools.lint.detector.api; 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; -import org.w3c.dom.Node; +import com.android.tools.lint.client.api.SdkInfo; import java.io.File; import java.util.EnumSet; @@ -33,84 +29,193 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Context passed to the detectors during an analysis run. It provides * information about the file being analyzed, it allows shared properties (so - * the detectors can share results), it contains the current location in the - * document, etc. + * the detectors can share results), etc. * <p> - * TODO: This needs some cleanup. Perhaps we should split up into a FileContext - * and a ProjectContext. - * <p/> * <b>NOTE: This is not a public or final API; if you rely on this be prepared * to adjust your code for the next tools release.</b> */ public class Context { - public final Project project; + /** + * The file being checked. Note that this may not always be to a concrete + * file. For example, in the {@link Detector#beforeCheckProject(Context)} + * method, the context file is the directory of the project. + */ public final File file; - public final LintClient client; - public final Configuration configuration; - public final EnumSet<Scope> scope; - public Document document; - public Location location; - public Element element; - public IDomParser parser; - private String contents; + + /** The client requesting a lint check */ + private final LintClient mClient; + + /** The project containing the file being checked */ + private final Project mProject; + + /** The current configuration controlling which checks are enabled etc */ + private final Configuration mConfiguration; + + /** The contents of the file */ + private String mContents; + + /** The scope of the current lint check */ + private final EnumSet<Scope> mScope; + + /** The SDK info, if any */ + private SdkInfo mSdkInfo; /** + * Whether the lint job has been canceled. + * <p> * Slow-running detectors should check this flag via * {@link AtomicBoolean#get()} and abort if canceled */ public final AtomicBoolean canceled = new AtomicBoolean(); - private Map<String, Object> properties; + /** Map of properties to share results between detectors */ + private Map<String, Object> mProperties; + /** + * Construct a new {@link Context} + * + * @param client the client requesting a lint check + * @param project the project containing the file being checked + * @param file the file being checked + * @param scope the scope for the lint job + */ 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(); + mClient = client; + mProject = project; + mScope = scope; + mConfiguration = project.getConfiguration(); } - public Location getLocation(Node node) { - if (parser != null) { - return new Location(file, - parser.getStartPosition(this, node), - parser.getEndPosition(this, node)); - } + /** + * Returns the scope for the lint job + * + * @return the scope, never null + */ + public EnumSet<Scope> getScope() { + return mScope; + } - return location; + /** + * Returns the configuration for this project. + * + * @return the configuration, never null + */ + public Configuration getConfiguration() { + return mConfiguration; } - public Location getLocation(Context context) { - if (location == null && element != null && parser != null) { - return getLocation(element); - } - return location; + /** + * Returns the project containing the file being checked + * + * @return the project, never null + */ + public Project getProject() { + return mProject; + } + + /** + * Returns the lint client requesting the lint check + * + * @return the client, never null + */ + public LintClient getClient() { + return mClient; } - // TODO: This should be delegated to the tool context! + /** + * Returns the contents of the file. This may not be the contents of the + * file on disk, since it delegates to the {@link LintClient}, which in turn + * may decide to return the current edited contents of the file open in an + * editor. + * + * @return the contents of the given file, or null if an error occurs. + */ public String getContents() { - if (contents == null) { - contents = client.readFile(file); + if (mContents == null) { + mContents = mClient.readFile(file); } - return contents; + return mContents; } + /** + * Returns the value of the given named property, or null. + * + * @param name the name of the property + * @return the corresponding value, or null + */ public Object getProperty(String name) { - if (properties == null) { + if (mProperties == null) { return null; } - return properties.get(name); + return mProperties.get(name); } + /** + * Sets the value of the given named property. + * + * @param name the name of the property + * @param value the corresponding value + */ public void setProperty(String name, Object value) { - if (properties == null) { - properties = new HashMap<String, Object>(); + if (mProperties == null) { + mProperties = new HashMap<String, Object>(); } - properties.put(name, value); + mProperties.put(name, value); } + + /** + * Gets the SDK info for the current project. + * + * @return the SDK info for the current project, never null + */ + public SdkInfo getSdkInfo() { + if (mSdkInfo == null) { + mSdkInfo = mClient.getSdkInfo(mProject); + } + + return mSdkInfo; + } + + // ---- Convenience wrappers ---- (makes the detector code a bit leaner) + + /** + * Returns false if the given issue has been disabled. Convenience wrapper + * around {@link Configuration#getSeverity(Issue)}. + * + * @param issue the issue to check + * @return false if the issue has been disabled + */ + public boolean isEnabled(Issue issue) { + return mConfiguration.isEnabled(issue); + } + + /** + * Reports an issue. Convenience wrapper around {@link LintClient#report} + * + * @param issue the issue to report + * @param location the location of the issue, or null if not known + * @param message the message for this warning + * @param data any associated data, or null + */ + public void report(Issue issue, Location location, String message, Object data) { + mClient.report(this, issue, location, message, data); + } + + /** + * Send an exception to the log. Convenience wrapper around {@link LintClient#log}. + * + * @param exception the exception, possibly null + * @param format the error message using {@link String#format} syntax + * @param args any arguments for the format string + */ + public void log(Throwable exception, String format, Object... args) { + mClient.log(exception, format, args); + } + } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/DefaultPosition.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/DefaultPosition.java new file mode 100644 index 0000000..6d7c6f3 --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/DefaultPosition.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.lint.detector.api; + +/** A simple offset-based position */ +public class DefaultPosition extends Position { + /** The line number (0-based where the first line is line 0) */ + private final int mLine; + + /** + * The column number (where the first character on the line is 0), or -1 if + * unknown + */ + private final int mColumn; + + /** The character offset */ + private final int mOffset; + + /** + * Creates a new {@link DefaultPosition} + * + * @param line the 0-based line number, or -1 if unknown + * @param column the 0-based column number, or -1 if unknown + * @param offset the offset, or -1 if unknown + */ + public DefaultPosition(int line, int column, int offset) { + this.mLine = line; + this.mColumn = column; + this.mOffset = offset; + } + + @Override + public int getLine() { + return mLine; + } + + @Override + public int getOffset() { + return mOffset; + } + + @Override + public int getColumn() { + return mColumn; + } +}
\ No newline at end of file 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 b48af6a..aaeee5a 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 @@ -42,13 +42,18 @@ import java.util.List; * to adjust your code for the next tools release.</b> */ public abstract class Detector { - /** Specialized interface for detectors that scan Java source files */ + /** Specialized interface for detectors that scan Java source file parse trees */ public interface JavaScanner { + // TODO: Java scanning support not yet implemented. This + // is a placeholder. void checkJavaSources(Context context, List<File> sourceFolders); } /** Specialized interface for detectors that scan Java class files */ public interface ClassScanner { + // TODO: Class file / bytecode scanning support not yet implemented. This + // is a placeholder. + @SuppressWarnings("javadoc") void checkJavaClasses(Context context); } @@ -60,28 +65,28 @@ public abstract class Detector { * @param context information about the document being analyzed * @param document the document to examine */ - void visitDocument(Context context, Document document); + void visitDocument(XmlContext context, Document document); /** * Visit the given element. * @param context information about the document being analyzed * @param element the element to examine */ - void visitElement(Context context, Element element); + void visitElement(XmlContext context, Element element); /** * Visit the given element after its children have been analyzed. * @param context information about the document being analyzed * @param element the element to examine */ - void visitElementAfter(Context context, Element element); + void visitElementAfter(XmlContext context, Element element); /** * Visit the given attribute. * @param context information about the document being analyzed * @param attribute the attribute node to examine */ - void visitAttribute(Context context, Attr attribute); + void visitAttribute(XmlContext context, Attr attribute); /** * Returns the list of elements that this detector wants to analyze. If non @@ -119,83 +124,63 @@ public abstract class Detector { public static final List<String> ALL = new ArrayList<String>(0); } - /** Concrete implementation of a detector that is a {@link Detector.XmlScanner} */ - public static abstract class XmlDetectorAdapter extends Detector - implements Detector.XmlScanner { - - @Override - public void run(Context context) { - } - - @Override - public boolean appliesTo(Context context, File file) { - return false; - } - - public void visitDocument(Context context, Document document) { - // This method must be overridden if your detector does - // not return something from getApplicableElements or - // getApplicableATtributes - assert false; - } - - public void visitElement(Context context, Element element) { - // This method must be overridden if your detector returns - // tag names from getApplicableElements - assert false; - } - - public void visitElementAfter(Context context, Element element) { - } - - public void visitAttribute(Context context, Attr attribute) { - // This method must be overridden if your detector returns - // attribute names from getApplicableAttributes - assert false; - } - - public Collection<String> getApplicableElements() { - return null; - } - - public Collection<String> getApplicableAttributes() { - return null; - } - } - /** - * Runs the detector + * Runs the detector. This method will not be called for certain specialized + * detectors, such as {@link XmlScanner} and {@link JavaScanner}, where + * there are specialized analysis methods instead such as + * {@link XmlScanner#visitElement(XmlContext, Element)}. * * @param context the context describing the work to be done */ - public abstract void run(Context context); + public void run(Context context) { + } - /** Returns true if this detector applies to the given file */ - public abstract boolean appliesTo(Context context, File file); + /** + * Returns true if this detector applies to the given file + * + * @param context the context to check + * @param file the file in the context to check + * @return true if this detector applies to the given context and file + */ + public boolean appliesTo(Context context, File file) { + return false; + } /** * Analysis is about to begin, perform any setup steps. - * <p> - * TODO: Rename "check" to "scan" here? beforeScanProject, beforeScanFile - * etc? + * + * @param context the context for the check referencing the project, lint + * client, etc */ public void beforeCheckProject(Context context) { } /** * Analysis has just been finished for the whole project, perform any - * cleanup or report issues found + * cleanup or report issues that require project-wide analysis. + * + * @param context the context for the check referencing the project, lint + * client, etc */ public void afterCheckProject(Context context) { } - /** Analysis is about to be performed on a specific file, perform any setup steps. */ + /** + * Analysis is about to be performed on a specific file, perform any setup + * steps. + * + * @param context the context for the check referencing the file to be + * checked, the project, etc. + */ public void beforeCheckFile(Context context) { } /** * Analysis has just been finished for a specific file, perform any cleanup * or report issues found + * + * @param context the context for the check referencing the file to be + * checked, the project, etc. */ public void afterCheckFile(Context context) { } @@ -206,4 +191,49 @@ public abstract class Detector { * @return the expected speed of this detector */ public abstract Speed getSpeed(); + + // ---- Dummy implementations to make implementing XmlScanner easier: ---- + + @SuppressWarnings("javadoc") + public void visitDocument(XmlContext context, Document document) { + // This method must be overridden if your detector does + // not return something from getApplicableElements or + // getApplicableATtributes + assert false; + } + + @SuppressWarnings("javadoc") + public void visitElement(XmlContext context, Element element) { + // This method must be overridden if your detector returns + // tag names from getApplicableElements + assert false; + } + + @SuppressWarnings("javadoc") + public void visitElementAfter(XmlContext context, Element element) { + } + + @SuppressWarnings("javadoc") + public void visitAttribute(XmlContext context, Attr attribute) { + // This method must be overridden if your detector returns + // attribute names from getApplicableAttributes + assert false; + } + + @SuppressWarnings("javadoc") + public Collection<String> getApplicableElements() { + return null; + } + + @SuppressWarnings("javadoc") + public Collection<String> getApplicableAttributes() { + return null; + } + + // ---- Dummy implementations to make implementing JavaScanner easier: ---- + + @SuppressWarnings("javadoc") + public void checkJavaSources(Context context, List<File> sourceFolders) { + } + } 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 5320362..de5cde3 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 @@ -46,8 +46,9 @@ public final class Issue implements Comparable<Issue> { private final Class<? extends Detector> mClass; // Use factory methods - private Issue(String id, String description, String explanation, Category category, int priority, - Severity severity, Class<? extends Detector> detectorClass, 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; 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 index c0b382f..268fd18 100644 --- 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 @@ -46,24 +46,47 @@ public class LintConstants { // Tags: Layouts public static final String VIEW_TAG = "view"; //$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 REQUEST_FOCUS = "requestFocus"; //$NON-NLS-1$ + + public static final String VIEW = "View"; //$NON-NLS-1$ + public static final String VIEW_GROUP = "ViewGroup"; //$NON-NLS-1$ public static final String FRAME_LAYOUT = "FrameLayout"; //$NON-NLS-1$ public static final String LINEAR_LAYOUT = "LinearLayout"; //$NON-NLS-1$ public static final String RELATIVE_LAYOUT = "RelativeLayout"; //$NON-NLS-1$ public static final String GRID_LAYOUT = "GridLayout"; //$NON-NLS-1$ public static final String SCROLL_VIEW = "ScrollView"; //$NON-NLS-1$ + public static final String BUTTON = "Button"; //$NON-NLS-1$ + public static final String COMPOUND_BUTTON = "CompoundButton"; //$NON-NLS-1$ + public static final String ADAPTER_VIEW = "AdapterView"; //$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 TAB_HOST = "TabHost"; //$NON-NLS-1$ + public static final String RADIO_GROUP = "RadioGroup"; //$NON-NLS-1$ + public static final String RADIO_BUTTON = "RadioButton"; //$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 SURFACE_VIEW = "SurfaceView"; //$NON-NLS-1$ public static final String ABSOLUTE_LAYOUT = "AbsoluteLayout"; //$NON-NLS-1$ public static final String TABLE_LAYOUT = "TableLayout"; //$NON-NLS-1$ public static final String TABLE_ROW = "TableRow"; //$NON-NLS-1$ + public static final String TAB_WIDGET = "TabWidget"; //$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 REQUEST_FOCUS = "requestFocus"; //$NON-NLS-1$ + public static final String SEEK_BAR = "SeekBar"; //$NON-NLS-1$ + public static final String VIEW_STUB = "ViewStub"; //$NON-NLS-1$ + public static final String SPINNER = "Spinner"; //$NON-NLS-1$ + public static final String TOGGLE_BUTTON = "ToggleButton"; //$NON-NLS-1$ + public static final String CHECK_BOX = "CheckBox"; //$NON-NLS-1$ + public static final String ABS_LIST_VIEW = "AbsListView"; //$NON-NLS-1$ + public static final String PROGRESS_BAR = "ProgressBar"; //$NON-NLS-1$ + public static final String ABS_SPINNER = "AbsSpinner"; //$NON-NLS-1$ + public static final String ABS_SEEK_BAR = "AbsSeekBar"; //$NON-NLS-1$ + public static final String VIEW_ANIMATOR = "ViewAnimator"; //$NON-NLS-1$ + public static final String VIEW_SWITCHER = "ViewSwitcher"; //$NON-NLS-1$ + public static final String EXPANDABLE_LIST_VIEW = "ExpandableListView"; //$NON-NLS-1$ public static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; //$NON-NLS-1$ // Tags: Drawables @@ -183,7 +206,9 @@ public class LintConstants { public static final String DRAWABLE_LDPI = "drawable-ldpi"; //$NON-NLS-1$ // Resources - public static final String ANDROID_RESOURCE_PREFIX = "@android:"; //$NON-NLS-1$ + public static final String ANDROID_RESOURCE_PREFIX = "@android:"; //$NON-NLS-1$ + public static final String ID_RESOURCE_PREFIX = "@id/"; //$NON-NLS-1$ + public static final String NEW_ID_RESOURCE_PREFIX = "@+id/"; //$NON-NLS-1$ public static final String DRAWABLE_RESOURCE_PREFIX = "@drawable/"; //$NON-NLS-1$ public static final String LAYOUT_RESOURCE_PREFIX = "@layout/"; //$NON-NLS-1$ public static final String STYLE_RESOURCE_PREFIX = "@style/"; //$NON-NLS-1$ @@ -196,4 +221,7 @@ public class LintConstants { public static final String TRANSPARENT_COLOR = "@android:color/transparent"; //$NON-NLS-1$ public static final String ANDROID_STYLE_RESOURCE_PREFIX = "@android:style/"; //$NON-NLS-1$ + // Packages + public static final String WIDGET_PKG_PREFIX = "android.widget."; //$NON-NLS-1$ + public static final String VIEW_PKG_PREFIX = "android.view."; //$NON-NLS-1$ } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java index feac79e..68e7c23 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java @@ -28,9 +28,15 @@ public class Location { private final File mFile; private final Position mStart; private final Position mEnd; + private String mMessage; private Location mSecondary; /** + * (Private constructor, use one of the factory methods + * {@link Location#create(File)}, + * {@link Location#create(File, Position, Position)}, or + * {@link Location#create(File, String, int, int)}. + * <p> * Constructs a new location range for the given file, from start to end. If * the length of the range is not known, end may be null. * @@ -40,7 +46,7 @@ public class Location { * @param start the starting position, never null * @param end the ending position, or null */ - public Location(File file, Position start, Position end) { + protected Location(File file, Position start, Position end) { super(); this.mFile = file; this.mStart = start; @@ -97,4 +103,170 @@ public class Location { public void setSecondary(Location secondary) { this.mSecondary = secondary; } + + /** + * Sets a custom message for this location. This is typically used for + * secondary locations, to describe the significance of this alternate + * location. For example, for a duplicate id warning, the primary location + * might say "This is a duplicate id", pointing to the second occurrence of + * id declaration, and then the secondary location could point to the + * original declaration with the custom message "Originally defined here". + * + * @param message the message to apply to this location + */ + public void setMessage(String message) { + mMessage = message; + } + + /** + * Returns the custom message for this location, if any. This is typically + * used for secondary locations, to describe the significance of this + * alternate location. For example, for a duplicate id warning, the primary + * location might say "This is a duplicate id", pointing to the second + * occurrence of id declaration, and then the secondary location could point + * to the original declaration with the custom message + * "Originally defined here". + * + * @return the custom message for this location, or null + */ + public String getMessage() { + return mMessage; + } + + /** + * Creates a new location for the given file + * + * @param file the file to create a location for + * @return a new location + */ + public static Location create(File file) { + return new Location(file, null /*start*/, null /*end*/); + } + + /** + * Creates a new location for the given file and starting and ending + * positions. + * + * @param file the file containing the positions + * @param start the starting position + * @param end the ending position + * @return a new location + */ + public static Location create(File file, Position start, Position end) { + return new Location(file, start, end); + } + + /** + * Creates a new location for the given file, with the given contents, for + * the given offset range. + * + * @param file the file containing the location + * @param contents the current contents of the file + * @param startOffset the starting offset + * @param endOffset the ending offset + * @return a new location + */ + public static Location create(File file, String contents, int startOffset, int endOffset) { + if (startOffset < 0 || endOffset < startOffset) { + throw new IllegalArgumentException("Invalid offsets"); + } + + if (contents == null) { + return new Location(file, + new DefaultPosition(-1, -1, startOffset), + new DefaultPosition(-1, -1, endOffset)); + } + + int size = contents.length(); + endOffset = Math.min(endOffset, size); + startOffset = Math.min(startOffset, endOffset); + Position start = null; + int line = 0; + int lineOffset = 0; + for (int offset = 0; offset <= size; offset++) { + if (offset == startOffset) { + start = new DefaultPosition(line, offset - lineOffset, offset); + } + if (offset == endOffset) { + Position end = new DefaultPosition(line, offset - lineOffset, offset); + return new Location(file, start, end); + } + char c = contents.charAt(offset); + if (c == '\n') { + lineOffset = offset; + line++; + } + } + return Location.create(file); + } + + /** + * Creates a new location for the given file, with the given contents, for + * the given line number. + * + * @param file the file containing the location + * @param contents the current contents of the file + * @param line the line number (0-based) for the position + * @return a new location + */ + public static Location create(File file, String contents, int line) { + int currentLine = 0; + int offset = 0; + while (currentLine < line) { + offset = contents.indexOf('\n', offset); + if (offset == -1) { + return Location.create(file); + } + currentLine++; + offset++; + } + + if (line == currentLine) { + Position position = new DefaultPosition(line, -1, offset); + return new Location(file, position, position); + } + + return Location.create(file); + } + + /** + * A {@link Handle} is a reference to a location. The point of a location + * handle is to be able to create them cheaply, and then resolve them into + * actual locations later (if needed). This makes it possible to for example + * delay looking up line numbers, for locations that are offset based. + */ + public static interface Handle { + /** + * Compute a full location for the given handle + * + * @return create a location for this handle + */ + Location resolve(); + } + + /** A default {@link Handle} implementation for simple file offsets */ + public static class DefaultLocationHandle implements Handle { + private File mFile; + private String mContents; + private int mStartOffset; + private int mEndOffset; + + /** + * Constructs a new {@link DefaultLocationHandle} + * + * @param context the context pointing to the file and its contents + * @param startOffset the start offset within the file + * @param endOffset the end offset within the file + */ + public DefaultLocationHandle(Context context, int startOffset, int endOffset) { + mFile = context.file; + mContents = context.getContents(); + mStartOffset = startOffset; + mEndOffset = endOffset; + } + + public Location resolve() { + return Location.create(mFile, mContents, mStartOffset, mEndOffset); + } + } } 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 828f946..ca18fc3 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 @@ -28,7 +28,7 @@ import java.io.File; * <b>NOTE: This is not a public or final API; if you rely on this be prepared * to adjust your code for the next tools release.</b> */ -public abstract class ResourceXmlDetector extends Detector.XmlDetectorAdapter { +public abstract class ResourceXmlDetector extends Detector implements Detector.XmlScanner { @Override public boolean appliesTo(Context context, File file) { return LintUtils.isXmlFile(file); diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java index 20e4864..18a369c 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java @@ -81,5 +81,5 @@ public enum Scope { /** Scope-set used for detectors which scan all resources */ public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES); /** Scope-set used for detectors which are affected by a single Java source file */ - public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(RESOURCE_FILE); + public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE); } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java new file mode 100644 index 0000000..2c282ed --- /dev/null +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.lint.detector.api; + +import com.android.tools.lint.client.api.IDomParser; +import com.android.tools.lint.client.api.LintClient; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import java.io.File; +import java.util.EnumSet; + +/** + * A {@link Context} used when checking XML files. + * <p/> + * <b>NOTE: This is not a public or final API; if you rely on this be prepared + * to adjust your code for the next tools release.</b> + */ +public class XmlContext extends Context { + /** The XML parser */ + public IDomParser parser; + /** The XML document */ + public Document document; + + /** + * Construct a new {@link XmlContext} + * + * @param client the client requesting a lint check + * @param project the project containing the file being checked + * @param file the file being checked + * @param scope the scope for the lint job + */ + public XmlContext(LintClient client, Project project, File file, + EnumSet<Scope> scope) { + super(client, project, file, scope); + } + + /** + * Returns the location for the given node, which may be an element or an attribute. + * + * @param node the node to look up the location for + * @return the location for the node + */ + public Location getLocation(Node node) { + if (parser != null) { + return parser.getLocation(this, node); + } + + return Location.create(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 8ef8e02..c61dbcd 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 @@ -22,12 +22,12 @@ 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; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -75,15 +75,15 @@ public class AccessibilityDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { if (!element.hasAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION)) { - context.client.report(context, ISSUE, context.getLocation(element), + context.report(ISSUE, context.getLocation(element), "[Accessibility] Missing contentDescription attribute on image", null); } else { Attr attributeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION); String attribute = attributeNode.getValue(); if (attribute.length() == 0 || attribute.equals("TODO")) { //$NON-NLS-1$ - context.client.report(context, ISSUE, context.getLocation(attributeNode), + context.report(ISSUE, context.getLocation(attributeNode), "[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 b2b77a0..01faa2f 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 @@ -32,6 +32,7 @@ import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import com.android.util.Pair; import org.w3c.dom.Attr; @@ -130,14 +131,16 @@ public class ArraySizeDetector extends ResourceXmlDetector { countMap.put(name, count); fileMap.put(name, file); } else if (!count.equals(current)) { - //Location location = getLocation(language, parentFolderToLanguage); String thisName = file.getParentFile().getName() + File.separator + file.getName(); File otherFile = fileMap.get(name); - Location location = new Location(otherFile, null, null); - location.setSecondary(new Location(file, null, null)); + Location location = Location.create(otherFile); + Location secondary = Location.create(file); + secondary.setMessage("Declaration with conflicting size"); + location.setSecondary(secondary); + String otherName = otherFile.getParentFile().getName() + File.separator + otherFile.getName(); - context.client.report(context, INCONSISTENT, location, + context.report(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,10 +152,10 @@ public class ArraySizeDetector extends ResourceXmlDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { Attr attribute = element.getAttributeNode(ATTR_NAME); if (attribute == null || attribute.getValue().length() == 0) { - context.client.report(context, INCONSISTENT, context.getLocation(element), + context.report(INCONSISTENT, context.getLocation(element), String.format("Missing name attribute in %1$s declaration", element.getTagName()), null); } else { 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 6697bf3..cd0585e 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 @@ -23,13 +23,13 @@ import static com.android.tools.lint.detector.api.LintConstants.REQUEST_FOCUS; 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; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -90,19 +90,19 @@ public class ChildCountDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { int childCount = LintUtils.getChildCount(element); String tagName = element.getTagName(); if (tagName.equals(SCROLL_VIEW) || tagName.equals(HORIZONTAL_SCROLL_VIEW)) { if (childCount > 1 && getAccurateChildCount(element) > 1) { - context.client.report(context, SCROLLVIEW_ISSUE, + context.report(SCROLLVIEW_ISSUE, context.getLocation(element), "A scroll view can have only one child", null); } } else { // Adapter view if (childCount > 0 && getAccurateChildCount(element) > 0) { - context.client.report(context, ADAPTERVIEW_ISSUE, + context.report(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/DeprecationDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DeprecationDetector.java index bc2f3ad..8fdc1cd 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DeprecationDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DeprecationDetector.java @@ -19,12 +19,12 @@ package com.android.tools.lint.checks; import static com.android.tools.lint.detector.api.LintConstants.ABSOLUTE_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; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; @@ -64,8 +64,8 @@ public class DeprecationDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { - context.client.report(context, ISSUE, context.getLocation(element), + public void visitElement(XmlContext context, Element element) { + context.report(ISSUE, context.getLocation(element), String.format("%1$s is deprecated", element.getTagName()), null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java index 670b3fa..5af5606 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java @@ -23,12 +23,12 @@ import static com.android.tools.lint.detector.api.LintConstants.VIEW_TAG; import static com.android.tools.lint.detector.api.LintConstants.XMLNS_PREFIX; 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.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -79,7 +79,7 @@ public class DetectMissingPrefix extends LayoutDetector { } @Override - public void visitAttribute(Context context, Attr attribute) { + public void visitAttribute(XmlContext context, Attr attribute) { String uri = attribute.getNamespaceURI(); if (uri == null || uri.length() == 0) { String name = attribute.getName(); @@ -99,7 +99,7 @@ public class DetectMissingPrefix extends LayoutDetector { return; } - context.client.report(context, MISSING_NAMESPACE, + context.report(MISSING_NAMESPACE, context.getLocation(attribute), "Attribute is missing the Android namespace prefix", 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 6be3ed9..bea442e 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,10 +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_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.LAYOUT_RESOURCE_PREFIX; +import static com.android.tools.lint.detector.api.LintConstants.NEW_ID_RESOURCE_PREFIX; import com.android.resources.ResourceFolderType; import com.android.tools.lint.detector.api.Category; @@ -30,9 +32,12 @@ import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import java.io.File; import java.util.ArrayList; @@ -132,13 +137,10 @@ public class DuplicateIdDetector extends LayoutDetector { // First perform a topological sort such such checkForIncludeDuplicates(context); } - - mFileToIds = null; - mIncludes = null; } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { // Record include graph such that we can look for inter-layout duplicates after the // project has been fully checked @@ -156,8 +158,8 @@ public class DuplicateIdDetector extends LayoutDetector { } private void checkForIncludeDuplicates(Context context) { - if (!context.configuration.isEnabled(CROSS_LAYOUT) || - !context.scope.contains(Scope.ALL_RESOURCE_FILES)) { + if (!context.isEnabled(CROSS_LAYOUT) || + !context.getScope().contains(Scope.ALL_RESOURCE_FILES)) { return; } @@ -329,7 +331,7 @@ public class DuplicateIdDetector extends LayoutDetector { private void reportError(Context context, String id, File first, File second, String includer, List<String> chain) { String msg = null; - if (chain.size() > 2) { // < 2: it's a directly include & obvious + if (chain.size() > 2) { // < 2: it's a direct include and therefore obvious StringBuilder sb = new StringBuilder(); for (String layout : chain) { if (sb.length() > 0) { @@ -346,12 +348,14 @@ public class DuplicateIdDetector extends LayoutDetector { id, includer); } - Location location = new Location(first, null, null); + Location location = Location.create(first); if (second != null) { // Also record the secondary location - location.setSecondary(new Location(second, null, null)); + Location secondLocation = Location.create(second); + secondLocation.setMessage(String.format("%1$s originally defined here", id)); + location.setSecondary(secondLocation); } - context.client.report(context, CROSS_LAYOUT, location, msg, null); + context.report(CROSS_LAYOUT, location, msg, null); } /** @@ -395,15 +399,45 @@ public class DuplicateIdDetector extends LayoutDetector { } @Override - public void visitAttribute(Context context, Attr attribute) { + public void visitAttribute(XmlContext context, Attr attribute) { assert attribute.getLocalName().equals(ATTR_ID); String id = attribute.getValue(); if (mIds.contains(id)) { - context.client.report(context, WITHIN_LAYOUT, context.getLocation(attribute), + Location location = context.getLocation(attribute); + + Attr first = findIdAttribute(attribute.getOwnerDocument(), id); + if (first != null && first != attribute) { + Location secondLocation = context.getLocation(first); + secondLocation.setMessage(String.format("%1$s originally defined here", id)); + location.setSecondary(secondLocation); + } + + context.report(WITHIN_LAYOUT, location, String.format("Duplicate id %1$s, already defined earlier in this layout", id), null); - } else if (id.startsWith("@+id/")) { //$NON-NLS-1$ + } else if (id.startsWith(NEW_ID_RESOURCE_PREFIX)) { mIds.add(id); } } + + /** Find the first id attribute with the given value below the given node */ + private Attr findIdAttribute(Node node, String targetValue) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Attr attribute = ((Element) node).getAttributeNodeNS(ANDROID_URI, ATTR_ID); + if (attribute != null && attribute.getValue().equals(targetValue)) { + return attribute; + } + } + + NodeList children = node.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node child = children.item(i); + Attr result = findIdAttribute(child, targetValue); + if (result != null) { + return result; + } + } + + return 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 f8cc3c9..b26c88b 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 @@ -23,13 +23,13 @@ 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; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -85,7 +85,7 @@ public class GridLayoutDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { int declaredRowCount = getInt(element, ATTR_ROW_COUNT, -1); int declaredColumnCount = getInt(element, ATTR_COLUMN_COUNT, -1); @@ -95,7 +95,7 @@ public class GridLayoutDetector extends LayoutDetector { int column = getInt(child, ATTR_LAYOUT_COLUMN, -1); if (column >= declaredColumnCount) { Attr node = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_COLUMN); - context.client.report(context, ISSUE, context.getLocation(node), + context.report(ISSUE, context.getLocation(node), String.format("Column attribute (%1$d) exceeds declared grid column count (%2$d)", column, declaredColumnCount), null); } @@ -104,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.client.report(context, ISSUE, context.getLocation(node), + context.report(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 fd43ecd..e629a7c 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 @@ -24,12 +24,12 @@ 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; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; @@ -82,7 +82,7 @@ public class HardcodedValuesDetector extends LayoutDetector { } @Override - public void visitAttribute(Context context, Attr attribute) { + public void visitAttribute(XmlContext context, Attr attribute) { String value = attribute.getValue(); if (value.length() > 0 && (value.charAt(0) != '@' && value.charAt(0) != '?')) { // Make sure this is really one of the android: attributes @@ -90,7 +90,7 @@ public class HardcodedValuesDetector extends LayoutDetector { return; } - context.client.report(context, ISSUE, context.getLocation(attribute), + context.report(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/IconDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java index f2f91ec..1717225 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java @@ -36,7 +36,6 @@ import static com.android.tools.lint.detector.api.LintUtils.difference; import static com.android.tools.lint.detector.api.LintUtils.endsWith; import static com.android.tools.lint.detector.api.LintUtils.intersection; -import com.android.tools.lint.client.api.Configuration; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Detector; @@ -46,6 +45,7 @@ import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; @@ -75,7 +75,7 @@ import javax.imageio.stream.ImageInputStream; * Checks for common icon problems, such as wrong icon sizes, placing icons in the * density independent drawable folder, etc. */ -public class IconDetector extends Detector.XmlDetectorAdapter { +public class IconDetector extends Detector implements Detector.XmlScanner { private static final boolean INCLUDE_LDPI; static { @@ -271,17 +271,16 @@ public class IconDetector extends Detector.XmlDetectorAdapter { @Override public void afterCheckProject(Context context) { // Make sure no - File res = new File(context.project.getDir(), RES_FOLDER); + File res = new File(context.getProject().getDir(), RES_FOLDER); if (res.isDirectory()) { File[] folders = res.listFiles(); if (folders != null) { - Configuration configuration = context.configuration; - boolean checkFolders = configuration.isEnabled(ICON_DENSITIES) || - configuration.isEnabled(ICON_MISSING_FOLDER) || - configuration.isEnabled(ICON_NODPI); - boolean checkDipSizes = configuration.isEnabled(ICON_DIP_SIZE); - boolean checkDuplicates = configuration.isEnabled(DUPLICATES_NAMES) - || configuration.isEnabled(DUPLICATES_CONFIGURATIONS); + boolean checkFolders = context.isEnabled(ICON_DENSITIES) + || context.isEnabled(ICON_MISSING_FOLDER) + || context.isEnabled(ICON_NODPI); + boolean checkDipSizes = context.isEnabled(ICON_DIP_SIZE); + boolean checkDuplicates = context.isEnabled(DUPLICATES_NAMES) + || context.isEnabled(DUPLICATES_CONFIGURATIONS); Map<File, Dimension> pixelSizes = null; Map<File, Long> fileSizes = null; @@ -417,7 +416,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { bits = LintUtils.readBytes(file); fileContents.put(file, bits); } catch (IOException e) { - context.client.log(e, null); + context.log(e, null); } } } @@ -512,7 +511,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { lastName = file.getName(); // Chain locations together Location linkedLocation = location; - location = new Location(file, null, null); + location = Location.create(file); location.setSecondary(linkedLocation); } @@ -527,11 +526,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { String message = String.format( "The %1$s icon has identical contents in the following configuration folders: %2$s", lastName, sb.toString()); - context.client.report(context, - DUPLICATES_CONFIGURATIONS, - location, - message, - null); + context.report(DUPLICATES_CONFIGURATIONS, location, message, null); } else { StringBuilder sb = new StringBuilder(); for (File file : sameFiles) { @@ -543,11 +538,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { String message = String.format( "The following unrelated icon files have identical contents: %1$s", sb.toString()); - context.client.report(context, - DUPLICATES_NAMES, - location, - message, - null); + context.report(DUPLICATES_NAMES, location, message, null); } } } @@ -705,7 +696,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { // Chain locations together Location linkedLocation = location; - location = new Location(file, null, null); + location = Location.create(file); location.setSecondary(linkedLocation); Dimension dip = entry2.getValue(); Dimension px = pixelSizes.get(file); @@ -718,11 +709,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { "The image %1$s varies significantly in its density-independent (dip) " + "size across the various density versions: %2$s", name, sb.toString()); - context.client.report(context, - ICON_DIP_SIZE, - location, - message, - null); + context.report(ICON_DIP_SIZE, location, message, null); } } } @@ -743,7 +730,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { // Look for missing folders -- if you define say drawable-mdpi then you // should also define -hdpi and -xhdpi. - if (context.configuration.isEnabled(ICON_MISSING_FOLDER)) { + if (context.isEnabled(ICON_MISSING_FOLDER)) { List<String> missing = new ArrayList<String>(); for (String density : REQUIRED_DENSITIES) { if (!definedDensities.contains(density)) { @@ -751,18 +738,17 @@ public class IconDetector extends Detector.XmlDetectorAdapter { } } if (missing.size() > 0 ) { - context.client.report( - context, + context.report( ICON_MISSING_FOLDER, null /* location */, String.format("Missing density variation folders in %1$s: %2$s", - context.project.getDisplayPath(res), + context.getProject().getDisplayPath(res), LintUtils.formatList(missing, missing.size())), null); } } - if (context.configuration.isEnabled(ICON_NODPI)) { + if (context.isEnabled(ICON_NODPI)) { Set<String> noDpiNames = new HashSet<String>(); for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) { if (isNoDpiFolder(entry.getKey())) { @@ -795,13 +781,11 @@ public class IconDetector extends Detector.XmlDetectorAdapter { Location location = null; for (File file : files) { Location linkedLocation = location; - location = new Location(file, null, null); + location = Location.create(file); location.setSecondary(linkedLocation); } - context.client.report(context, - ICON_DENSITIES, - location, + context.report(ICON_DENSITIES, location, String.format( "The following images appear in both -nodpi and in a density folder: %1$s", LintUtils.formatList(list, 10)), @@ -810,7 +794,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { } } - if (context.configuration.isEnabled(ICON_DENSITIES)) { + if (context.isEnabled(ICON_DENSITIES)) { // Look for folders missing some of the specific assets Set<String> allNames = new HashSet<String>(); for (Entry<File,Set<String>> entry : folderToNames.entrySet()) { @@ -846,9 +830,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { } } - context.client.report(context, - ICON_DENSITIES, - new Location(file, null, null), + context.report(ICON_DENSITIES, Location.create(file), String.format( "Missing the following drawables in %1$s: %2$s%3$s", file.getName(), @@ -867,7 +849,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { private void checkDrawableDir(Context context, File folder, File[] files, Map<File, Dimension> pixelSizes, Map<File, Long> fileSizes) { if (folder.getName().equals(DRAWABLE_FOLDER) - && context.configuration.isEnabled(ICON_LOCATION)) { + && context.isEnabled(ICON_LOCATION)) { for (File file : files) { String name = file.getName(); if (name.endsWith(DOT_XML)) { @@ -875,9 +857,8 @@ public class IconDetector extends Detector.XmlDetectorAdapter { } else if (endsWith(name, DOT_PNG) || endsWith(name, DOT_JPG) || endsWith(name, DOT_GIF)) { - context.client.report(context, - ICON_LOCATION, - new Location(file, null, null), + context.report(ICON_LOCATION, + Location.create(file), String.format("Found bitmap drawable res/drawable/%1$s in " + "densityless folder", file.getName()), @@ -886,13 +867,11 @@ public class IconDetector extends Detector.XmlDetectorAdapter { } } - if (context.configuration.isEnabled(GIF_USAGE)) { + if (context.isEnabled(GIF_USAGE)) { for (File file : files) { String name = file.getName(); if (endsWith(name, DOT_GIF)) { - context.client.report(context, - GIF_USAGE, - new Location(file, null, null), + context.report(GIF_USAGE, Location.create(file), "Using the .gif format for bitmaps is discouraged", null); } @@ -900,7 +879,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { } // Check icon sizes - if (context.configuration.isEnabled(ICON_EXPECTED_SIZE)) { + if (context.isEnabled(ICON_EXPECTED_SIZE)) { checkExpectedSizes(context, folder, files); } @@ -1000,7 +979,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { * manifest is at least 11. */ private boolean isAndroid30(Context context, int folderVersion) { - return folderVersion >= 11 || context.project.getMinSdk() >= 11; + return folderVersion >= 11 || context.getProject().getMinSdk() >= 11; } /** @@ -1017,7 +996,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { return true; } - int minSdk = context.project.getMinSdk(); + int minSdk = context.getProject().getMinSdk(); return minSdk == 9 || minSdk == 10; } @@ -1073,18 +1052,18 @@ public class IconDetector extends Detector.XmlDetectorAdapter { Dimension size = getSize(file); if (size != null) { if (exactMatch && size.width != width || size.height != height) { - context.client.report(context, + context.report( ICON_EXPECTED_SIZE, - new Location(file, null, null), + Location.create(file), String.format( "Incorrect icon size for %1$s: expected %2$dx%3$d, but was %4$dx%5$d", folderName + File.separator + file.getName(), width, height, size.width, size.height), null); } else if (!exactMatch && size.width > width || size.height > height) { - context.client.report(context, + context.report( ICON_EXPECTED_SIZE, - new Location(file, null, null), + Location.create(file), String.format( "Incorrect icon size for %1$s: icon size should be at most %2$dx%3$d, but was %4$dx%5$d", folderName + File.separator + file.getName(), @@ -1142,7 +1121,7 @@ public class IconDetector extends Detector.XmlDetectorAdapter { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { assert element.getTagName().equals(TAG_APPLICATION); mApplicationIcon = element.getAttributeNS(ANDROID_URI, ATTR_ICON); if (mApplicationIcon.startsWith(DRAWABLE_RESOURCE_PREFIX)) { 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 2925125..73cd87d 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 @@ -26,13 +26,13 @@ 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; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -110,12 +110,12 @@ public class InefficientWeightDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { List<Element> children = LintUtils.getChildren(element); // See if there is exactly one child with a weight boolean multipleWeights = false; Element weightChild = null; - boolean checkNesting = context.configuration.isEnabled(NESTED_WEIGHTS); + boolean checkNesting = context.isEnabled(NESTED_WEIGHTS); Node parent = element.getParentNode(); for (Element child : children) { if (child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) { @@ -134,7 +134,7 @@ public class InefficientWeightDetector extends LayoutDetector { mInsideWeight.put(parent, Boolean.FALSE); } else if (inside) { Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT); - context.client.report(context, NESTED_WEIGHTS, + context.report(NESTED_WEIGHTS, context.getLocation(sizeNode), "Nested weights are bad for performance", null); // Don't warn again @@ -144,7 +144,7 @@ public class InefficientWeightDetector extends LayoutDetector { } } - if (context.configuration.isEnabled(BASELINE_WEIGHTS) && weightChild != null && + if (context.isEnabled(BASELINE_WEIGHTS) && weightChild != null && !element.hasAttributeNS(ANDROID_URI, ATTR_BASELINE_ALIGNED)) { // See if all the children are layouts boolean allChildrenAreLayouts = children.size() > 0; @@ -155,14 +155,14 @@ public class InefficientWeightDetector extends LayoutDetector { } } if (allChildrenAreLayouts) { - context.client.report(context, BASELINE_WEIGHTS, + context.report(BASELINE_WEIGHTS, context.getLocation(element), "Set android:baselineAligned=\"false\" on this element for better performance", null); } } - if (context.configuration.isEnabled(INEFFICIENT_WEIGHT) + if (context.isEnabled(INEFFICIENT_WEIGHT) && weightChild != null && !multipleWeights) { String dimension; if (VALUE_VERTICAL.equals(element.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION))) { @@ -176,7 +176,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.client.report(context, INEFFICIENT_WEIGHT, + context.report(INEFFICIENT_WEIGHT, context.getLocation(sizeNode != null ? sizeNode : weightChild), msg, null); } 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 bb82541..bc0e401 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 @@ -28,6 +28,7 @@ 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 com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; @@ -40,7 +41,7 @@ import java.util.EnumSet; * Checks for issues in AndroidManifest files such as declaring elements in the * wrong order. */ -public class ManifestOrderDetector extends Detector.XmlDetectorAdapter { +public class ManifestOrderDetector extends Detector implements Detector.XmlScanner { /** The main issue discovered by this detector */ public static final Issue ISSUE = Issue.create( @@ -99,12 +100,12 @@ public class ManifestOrderDetector extends Detector.XmlDetectorAdapter { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { String tag = element.getTagName(); if (tag.equals(TAG_APPLICATION)) { mSeenApplication = true; } else if (mSeenApplication) { - context.client.report(context, ISSUE, context.getLocation(element), + context.report(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 d5fb886..565c45d 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 @@ -23,12 +23,12 @@ import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_GRAV 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; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -61,7 +61,7 @@ public class MergeRootFrameLayoutDetector extends LayoutDetector { } @Override - public void visitDocument(Context context, Document document) { + public void visitDocument(XmlContext context, Document document) { Element root = document.getDocumentElement(); if (root.getTagName().equals(FRAME_LAYOUT) && ((isWidthFillParent(root) && isHeightFillParent(root)) || @@ -69,7 +69,7 @@ public class MergeRootFrameLayoutDetector extends LayoutDetector { && !root.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND) && !root.hasAttributeNS(ANDROID_URI, ATTR_FOREGROUND) && !hasPadding(root)) { - context.client.report(context, ISSUE, context.getLocation(root), + context.report(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 8528e90..099efeb 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 @@ -29,6 +29,7 @@ import com.android.tools.lint.detector.api.LayoutDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -102,7 +103,7 @@ public class NestedScrollingWidgetDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { boolean vertical = isVerticalScroll(element); if (vertical) { mVisitingVerticalScroll++; @@ -122,13 +123,13 @@ public class NestedScrollingWidgetDetector extends LayoutDetector { "horizontally scrolling widget (%2$s)"; } String msg = String.format(format, parent.getTagName(), element.getTagName()); - context.client.report(context, ISSUE, context.getLocation(element), msg, null); + context.report(ISSUE, context.getLocation(element), msg, null); } } } @Override - public void visitElementAfter(Context context, Element element) { + public void visitElementAfter(XmlContext context, Element element) { if (isVerticalScroll(element)) { mVisitingVerticalScroll--; assert mVisitingVerticalScroll >= 0; diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java index ea5d978..487f6b1 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java @@ -59,10 +59,11 @@ import static com.android.tools.lint.detector.api.LintConstants.LAYOUT_RESOURCE_ 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.RELATIVE_LAYOUT; -import static com.android.tools.lint.detector.api.LintConstants.TABLE_LAYOUT; import static com.android.tools.lint.detector.api.LintConstants.TABLE_ROW; import static com.android.tools.lint.detector.api.LintConstants.VIEW_TAG; +import com.android.tools.lint.client.api.IDomParser; +import com.android.tools.lint.client.api.SdkInfo; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; @@ -71,6 +72,7 @@ import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import com.android.util.Pair; import org.w3c.dom.Attr; @@ -112,7 +114,7 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { * Set of layout parameter names that are considered valid no matter what so * no other checking is necessary - such as layout_width and layout_height. */ - private static final Set<String> VALID = new HashSet<String>(); + private static final Set<String> VALID = new HashSet<String>(10); /** * Mapping from a layout parameter name (local name only) to the defining @@ -123,16 +125,7 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { * every single layout attribute pointing to a list, this is just special * cased instead. */ - private static final Map<String, String> PARAM_TO_VIEW = new HashMap<String, String>(); - - /** - * Mapping from a layout to its parent view layout (in terms of class - * inheritance, not view hierarchies). This is used to see whether a layout - * parameter is inherited. For example, the "orientation" attribute is valid - * on a TableRow because TableRow extends TableLayout which in turn extends - * LinearLayout. - */ - private static final Map<String, String> VIEW_PARENTS = new HashMap<String, String>(); + private static final Map<String, String> PARAM_TO_VIEW = new HashMap<String, String>(28); static { // Available (mostly) everywhere: No check @@ -195,45 +188,6 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { PARAM_TO_VIEW.put(ATTR_LAYOUT_TO_LEFT_OF, RELATIVE_LAYOUT); PARAM_TO_VIEW.put(ATTR_LAYOUT_BELOW, RELATIVE_LAYOUT); PARAM_TO_VIEW.put(ATTR_LAYOUT_ABOVE, RELATIVE_LAYOUT); - - // Ancestry: Need to track which layouts extend which other layouts since params - // will be inherited - - // AbsoluteLayout - VIEW_PARENTS.put("WebView", ABSOLUTE_LAYOUT); //$NON-NLS-1$ - - // LinearLayout - VIEW_PARENTS.put("NumberPicker", LINEAR_LAYOUT); //$NON-NLS-1$ - VIEW_PARENTS.put("RadioGroup", LINEAR_LAYOUT); //$NON-NLS-1$ - VIEW_PARENTS.put("SearchView", LINEAR_LAYOUT); //$NON-NLS-1$ - VIEW_PARENTS.put("TabWidget", LINEAR_LAYOUT); //$NON-NLS-1$ - VIEW_PARENTS.put(TABLE_LAYOUT, LINEAR_LAYOUT); - VIEW_PARENTS.put(TABLE_ROW, LINEAR_LAYOUT); - VIEW_PARENTS.put("ZoomControls", LINEAR_LAYOUT); //$NON-NLS-1$ - - // RelativeLayout - VIEW_PARENTS.put("DialerFilter", RELATIVE_LAYOUT); //$NON-NLS-1$ - VIEW_PARENTS.put("TwoLineListItem", RELATIVE_LAYOUT); //$NON-NLS-1$ - - // FrameLayout - /* We don't actually have any FrameLayout layout params to check since we're not - * enforcing layout_gravity, so we don't need to track the ancestry of FrameLayout - * subclasses... - VIEW_PARENTS.put("AppWidgetHostView", FRAME_LAYOUT); - VIEW_PARENTS.put("CalendarView", FRAME_LAYOUT); - VIEW_PARENTS.put("DatePicker", FRAME_LAYOUT); - VIEW_PARENTS.put("GestureOverlayView", FRAME_LAYOUT); - VIEW_PARENTS.put("HorizontalScrollView", FRAME_LAYOUT); - VIEW_PARENTS.put("MediaController", FRAME_LAYOUT); - VIEW_PARENTS.put("ScrollView", FRAME_LAYOUT); - VIEW_PARENTS.put("TabHost", FRAME_LAYOUT); - VIEW_PARENTS.put("TimePicker", FRAME_LAYOUT); - VIEW_PARENTS.put("ViewAnimator", FRAME_LAYOUT); - VIEW_PARENTS.put("ImageSwitcher", FRAME_LAYOUT); - VIEW_PARENTS.put("TextSwitcher", FRAME_LAYOUT); - VIEW_PARENTS.put("ViewFlipper", FRAME_LAYOUT); - VIEW_PARENTS.put("ViewSwitcher", FRAME_LAYOUT); - */ } /** @@ -254,7 +208,8 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { * pair is a pair of an attribute name to be checked, and the file that * attribute is referenced in. */ - private List<Pair<String, Location>> mPending = new ArrayList<Pair<String,Location>>(); + private List<Pair<String, Location.Handle>> mPending = + new ArrayList<Pair<String,Location.Handle>>(); /** Constructs a new {@link ObsoleteLayoutParamsDetector} */ public ObsoleteLayoutParamsDetector() { @@ -276,7 +231,7 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { } @Override - public void visitAttribute(Context context, Attr attribute) { + public void visitAttribute(XmlContext context, Attr attribute) { String name = attribute.getLocalName(); if (name != null && name.startsWith("layout_") && //$NON-NLS-1$ ANDROID_URI.equals(attribute.getNamespaceURI())) { @@ -294,9 +249,10 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { // sure at least one included context is valid for this layout_param. // We can't do that yet since we may be processing the include tag to // this layout after the layout itself. Instead, stash a work order... - if (context.scope.contains(Scope.ALL_RESOURCE_FILES)) { - Location location = context.parser.getLocation(context, attribute); - mPending.add(Pair.of(name, location)); + if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) { + IDomParser parser = context.parser; + Location.Handle handle = parser.createLocationHandle(context, attribute); + mPending.add(Pair.of(name, handle)); } return; @@ -307,20 +263,21 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { // This is a merge which means we need to check the including contexts, // wherever they are. This has to be done after all the files have been // scanned since we are not processing the files in any particular order. - if (context.scope.contains(Scope.ALL_RESOURCE_FILES)) { - Location location = context.parser.getLocation(context, attribute); - mPending.add(Pair.of(name, location)); + if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) { + IDomParser parser = context.parser; + Location.Handle handle = parser.createLocationHandle(context, attribute); + mPending.add(Pair.of(name, handle)); } return; } - if (!isValidParamForParent(name, parent, parentTag)) { + if (!isValidParamForParent(context, name, parent, parentTag)) { if (name.equals(ATTR_LAYOUT_COLUMN) - && isValidParamForParent(name, TABLE_ROW, parentTag)) { + && isValidParamForParent(context, name, TABLE_ROW, parentTag)) { return; } - context.client.report(context, ISSUE, context.getLocation(attribute), + context.report(ISSUE, context.getLocation(attribute), String.format("Invalid layout param in a %1$s: %2$s", parentTag, name), null); } @@ -335,7 +292,7 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { String layout = element.getAttribute(ATTR_LAYOUT); if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { // Ignore @android:layout/ layouts layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length()); @@ -364,8 +321,8 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { return; } - for (Pair<String, Location> pending : mPending) { - Location location = pending.getSecond(); + for (Pair<String, Location.Handle> pending : mPending) { + Location location = pending.getSecond().resolve(); File file = location.getFile(); String layout = file.getName(); if (layout.endsWith(DOT_XML)) { @@ -387,11 +344,11 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { boolean isValid = false; for (Pair<File, String> include : includes) { String parentTag = include.getSecond(); - if (isValidParamForParent(name, parent, parentTag)) { + if (isValidParamForParent(context, name, parent, parentTag)) { isValid = true; break; } else if (!isValid && name.equals(ATTR_LAYOUT_COLUMN) - && isValidParamForParent(name, TABLE_ROW, parentTag)) { + && isValidParamForParent(context, name, TABLE_ROW, parentTag)) { isValid = true; break; } @@ -411,7 +368,7 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { } String message = String.format("Invalid layout param '%1$s' (%2$s)", name, sb.toString()); - context.client.report(context, ISSUE, location, message, null); + context.report(ISSUE, location, message, null); } } } @@ -420,20 +377,23 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { * Checks whether the given layout parameter name is valid for the given * parent tag assuming it has the given current parent tag */ - private static boolean isValidParamForParent(String name, String parent, String parentTag) { + private static boolean isValidParamForParent(Context context, String name, String parent, + String parentTag) { if (parentTag.indexOf('.') != -1 || parentTag.equals(VIEW_TAG)) { // Custom tag: We don't know whether it extends one of the builtin // types where the layout param is valid, so don't complain return true; } + SdkInfo sdk = context.getSdkInfo(); + if (!parentTag.equals(parent)) { - String tag = VIEW_PARENTS.get(parentTag); + String tag = sdk.getParentViewName(parentTag); while (tag != null) { if (tag.equals(parent)) { return true; } - tag = VIEW_PARENTS.get(tag); + tag = sdk.getParentViewName(tag); } return false; diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java index 278ef71..eb79ee7 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java @@ -43,12 +43,14 @@ 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.LayoutDetector; +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.Project; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import com.android.util.Pair; import org.w3c.dom.Attr; @@ -148,25 +150,30 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca } @Override + public boolean appliesTo(Context context, File file) { + return LintUtils.isXmlFile(file) || LintUtils.endsWith(file.getName(), DOT_JAVA); + } + + @Override public Speed getSpeed() { return Speed.FAST; } /** Is the given theme a "blank" theme (one not painting its background) */ private boolean isBlankTheme(String name) { - if (name.startsWith("@android:style/Theme.")) { //$NON-NLS-1$ + if (name.startsWith("@android:style/Theme_")) { //$NON-NLS-1$ if (name.contains("NoFrame") //$NON-NLS-1$ - || name.contains("Theme.Wallpaper") //$NON-NLS-1$ - || name.contains("Theme.Holo.Wallpaper") //$NON-NLS-1$ - || name.contains("Theme.Translucent") //$NON-NLS-1$ - || name.contains("Theme.Dialog.NoFrame") //$NON-NLS-1$ - || name.contains("Theme.Holo.Dialog.Alert") //$NON-NLS-1$ - || name.contains("Theme.Holo.Light.Dialog.Alert") //$NON-NLS-1$ - || name.contains("Theme.Dialog.Alert") //$NON-NLS-1$ - || name.contains("Theme.Panel") //$NON-NLS-1$ - || name.contains("Theme.Light.Panel") //$NON-NLS-1$ - || name.contains("Theme.Holo.Panel") //$NON-NLS-1$ - || name.contains("Theme.Holo.Light.Panel")) { //$NON-NLS-1$ + || name.contains("Theme_Wallpaper") //$NON-NLS-1$ + || name.contains("Theme_Holo_Wallpaper") //$NON-NLS-1$ + || name.contains("Theme_Translucent") //$NON-NLS-1$ + || name.contains("Theme_Dialog_NoFrame") //$NON-NLS-1$ + || name.contains("Theme_Holo_Dialog_Alert") //$NON-NLS-1$ + || name.contains("Theme_Holo_Light_Dialog_Alert") //$NON-NLS-1$ + || name.contains("Theme_Dialog_Alert") //$NON-NLS-1$ + || name.contains("Theme_Panel") //$NON-NLS-1$ + || name.contains("Theme_Light_Panel") //$NON-NLS-1$ + || name.contains("Theme_Holo_Panel") //$NON-NLS-1$ + || name.contains("Theme_Holo_Light_Panel")) { //$NON-NLS-1$ return true; } } @@ -196,7 +203,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca "Possible overdraw: Root element paints background %1$s with " + "a theme that also paints a background (inferred theme is %2$s)", drawable, theme); - context.client.report(context, ISSUE, location, message, null); + context.report(ISSUE, location, message, null); } } @@ -221,7 +228,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca return mManifestTheme; } - Project project = context.project; + Project project = context.getProject(); int apiLevel = project.getTargetSdk(); if (apiLevel == -1) { apiLevel = project.getMinSdk(); @@ -237,7 +244,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca // ---- Implements XmlScanner ---- @Override - public void visitAttribute(Context context, Attr attribute) { + public void visitAttribute(XmlContext context, Attr attribute) { // Only consider the root element's background if (attribute.getOwnerDocument().getDocumentElement() == attribute.getOwnerElement()) { // If the drawable is a non-repeated pattern then the overdraw might be @@ -265,13 +272,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca return; } - IDomParser parser = context.client.getParser(); - Position start = parser.getStartPosition(context, attribute); - Position end = null; - if (start != null) { - end = parser.getEndPosition(context, attribute); - } - Location location = new Location(context.file, start, end); + Location location = context.getLocation(attribute); if (mRootAttributes == null) { mRootAttributes = new ArrayList<Pair<Location,String>>(); } @@ -321,7 +322,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { String tag = element.getTagName(); if (tag.equals(TAG_STYLE)) { scanTheme(element); @@ -360,7 +361,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca name = name.replace('$', '.'); } if (name.startsWith(".")) { //$NON-NLS-1$ - String pkg = context.project.getPackage(); + String pkg = context.getProject().getPackage(); if (pkg != null && pkg.length() > 0) { name = pkg + name; } @@ -376,7 +377,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca if (mActivityToTheme == null) { mActivityToTheme = new HashMap<String, String>(); } - mActivityToTheme.put(name, theme); + mActivityToTheme.put(name, theme.replace('.', '_')); } } @@ -391,8 +392,9 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca parent = styleName.substring(0, index); } } + parent = parent.replace('.', '_'); - String resource = STYLE_RESOURCE_PREFIX + styleName; + String resource = STYLE_RESOURCE_PREFIX + styleName.replace('.', '_'); NodeList items = element.getChildNodes(); for (int i = 0, n = items.getLength(); i < n; i++) { @@ -436,6 +438,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca // ---- Implements JavaScanner ---- + @Override public void checkJavaSources(Context context, List<File> sourceFolders) { if (mActivities == null) { return; @@ -457,7 +460,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca String fqn = pkg + '.' + clz; if (mActivities.contains(fqn) || fqn.endsWith("Activity")) { //$NON-NLS-1$ - String code = context.client.readFile(file); + String code = context.getClient().readFile(file); scanLayoutReferences(code, fqn); scanThemeReferences(code, fqn); } 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 841e7d6..0d818e4 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 @@ -20,6 +20,7 @@ import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; @@ -59,7 +60,8 @@ public class ProguardDetector extends Detector { "-keepclasseswithmembernames class * {\n" + //$NON-NLS-1$ " public <init>(android."); //$NON-NLS-1$ if (index != -1) { - context.client.report(context, ISSUE, context.getLocation(context), + context.report(ISSUE, + Location.create(context.file, contents, index, index), "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 2e8d41e..2caec16 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 @@ -17,12 +17,12 @@ 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; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; @@ -69,14 +69,14 @@ public class PxUsageDetector extends LayoutDetector { } @Override - public void visitAttribute(Context context, Attr attribute) { + public void visitAttribute(XmlContext context, Attr attribute) { String value = attribute.getValue(); if (value.endsWith("px") && value.matches("\\d+px")) { //$NON-NLS-1$ if (value.charAt(0) == '0') { // 0px is fine. 0px is 0dp regardless of density... return; } - context.client.report(context, ISSUE, context.getLocation(attribute), + context.report(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 62db541..dcdd0f8 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 @@ -25,13 +25,13 @@ import static com.android.tools.lint.detector.api.LintConstants.VALUE_FILL_PAREN 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; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -77,7 +77,7 @@ public class ScrollViewChildDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { List<Element> children = LintUtils.getChildren(element); boolean isHorizontal = HORIZONTAL_SCROLL_VIEW.equals(element.getTagName()); String attributeName = isHorizontal ? ATTR_LAYOUT_WIDTH : ATTR_LAYOUT_HEIGHT; @@ -87,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.client.report(context, ISSUE, context.getLocation(sizeNode), msg, + context.report(ISSUE, context.getLocation(sizeNode), msg, null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java index ef76e51..54b5f62 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java @@ -36,6 +36,7 @@ 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; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -49,7 +50,7 @@ import java.util.EnumSet; /** * Checks that exported services request a permission. */ -public class SecurityDetector extends Detector.XmlDetectorAdapter { +public class SecurityDetector extends Detector implements Detector.XmlScanner { /** Exported services */ public static final Issue EXPORTED_SERVICE = Issue.create( @@ -102,7 +103,7 @@ public class SecurityDetector extends Detector.XmlDetectorAdapter { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { String tag = element.getTagName(); if (tag.equals(TAG_SERVICE)) { checkService(context, element); @@ -111,7 +112,7 @@ public class SecurityDetector extends Detector.XmlDetectorAdapter { } } - private void checkService(Context context, Element element) { + private void checkService(XmlContext context, Element element) { String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED); boolean exported; if (exportValue != null && exportValue.length() > 0) { @@ -138,7 +139,7 @@ public class SecurityDetector 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.client.report(context, EXPORTED_SERVICE, + context.report(EXPORTED_SERVICE, context.getLocation(element), "Exported service does not require permission", null); } @@ -147,21 +148,21 @@ public class SecurityDetector extends Detector.XmlDetectorAdapter { } } - private void checkGrantPermission(Context context, Element element) { + private void checkGrantPermission(XmlContext context, Element element) { Attr path = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH); Attr prefix = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PREFIX); Attr pattern = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PATTERN); String msg = "Content provider shares everything; this is potentially dangerous."; if (path != null && path.getValue().equals("/")) { //$NON-NLS-1$ - context.client.report(context, OPEN_PROVIDER, context.getLocation(path), msg, null); + context.report(OPEN_PROVIDER, context.getLocation(path), msg, null); } if (prefix != null && prefix.getValue().equals("/")) { //$NON-NLS-1$ - context.client.report(context, OPEN_PROVIDER, context.getLocation(prefix), msg, null); + context.report(OPEN_PROVIDER, context.getLocation(prefix), msg, null); } if (pattern != null && (pattern.getValue().equals("/") //$NON-NLS-1$ /* || pattern.getValue().equals(".*")*/)) { - context.client.report(context, OPEN_PROVIDER, context.getLocation(pattern), msg, null); + context.report(OPEN_PROVIDER, context.getLocation(pattern), 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 ab42d03..85d3bfd 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 @@ -20,13 +20,13 @@ import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; 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; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Document; @@ -67,7 +67,7 @@ public class StateListDetector extends ResourceXmlDetector { } @Override - public void visitDocument(Context context, Document document) { + public void visitDocument(XmlContext context, Document document) { // TODO: Look for views that don't specify // Display the error token somewhere so it can be suppressed // Emit warning at the end "run with --help to learn how to suppress types of errors/checks"; @@ -98,7 +98,7 @@ public class StateListDetector extends ResourceXmlDetector { } } if (!hasState) { - context.client.report(context, ISSUE, context.getLocation(child), + context.report(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 f06c5fb..0d52b83 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 @@ -23,12 +23,12 @@ 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; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; @@ -73,7 +73,7 @@ public class TextFieldDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { if (!element.hasAttributeNS(ANDROID_URI, ATTR_INPUT_TYPE) && !element.hasAttributeNS(ANDROID_URI, ATTR_HINT)) { // Also make sure the EditText does not set an inputMethod in which case @@ -82,7 +82,7 @@ public class TextFieldDetector extends LayoutDetector { return; } - context.client.report(context, ISSUE, context.getLocation(element), + context.report(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 116cdfe..42e867b 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 @@ -23,6 +23,7 @@ import com.android.tools.lint.detector.api.LayoutDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; @@ -117,7 +118,7 @@ public class TooManyViewsDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { mViewCount++; mDepth++; @@ -129,17 +130,17 @@ 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.client.report(context, TOO_DEEP, context.getLocation(element), msg, null); + context.report(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.client.report(context, TOO_MANY, context.getLocation(element), msg, null); + context.report(TOO_MANY, context.getLocation(element), msg, null); } } @Override - public void visitElementAfter(Context context, Element element) { + public void visitElementAfter(XmlContext context, Element element) { mDepth--; } } 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 1f0c288..24fb653 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 @@ -35,6 +35,7 @@ import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -171,8 +172,8 @@ public class TranslationDetector extends ResourceXmlDetector { return; } - boolean reportMissing = context.configuration.isEnabled(MISSING); - boolean reportExtra = context.configuration.isEnabled(EXTRA); + boolean reportMissing = context.isEnabled(MISSING); + boolean reportExtra = context.isEnabled(EXTRA); // res/strings.xml etc String defaultLanguage = "Default"; @@ -312,7 +313,7 @@ public class TranslationDetector extends ResourceXmlDetector { List<String> sorted = new ArrayList<String>(difference); Collections.sort(sorted); Location location = getLocation(language, parentFolderToLanguage); - context.client.report(context, MISSING, location, + context.report(MISSING, location, String.format("Locale %1$s is missing translations for: %2$s", language, LintUtils.formatList(sorted, 4)), null); } @@ -324,7 +325,7 @@ public class TranslationDetector extends ResourceXmlDetector { List<String> sorted = new ArrayList<String>(difference); Collections.sort(sorted); Location location = getLocation(language, parentFolderToLanguage); - context.client.report(context, EXTRA, location, String.format( + context.report(EXTRA, location, String.format( "Locale %1$s is translating names not found in default locale: %2$s", language, LintUtils.formatList(sorted, 4)), null); } @@ -338,7 +339,7 @@ public class TranslationDetector extends ResourceXmlDetector { for (Entry<File, String> e : parentFolderToLanguage.entrySet()) { if (e.getValue().equals(language)) { // Use the location of the parent folder for this language - location = new Location(e.getKey(), null, null); + location = Location.create(e.getKey()); break; } } @@ -346,14 +347,14 @@ public class TranslationDetector extends ResourceXmlDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { if (mIgnoreFile) { return; } Attr attribute = element.getAttributeNode(ATTR_NAME); if (attribute == null || attribute.getValue().length() == 0) { - context.client.report(context, MISSING, context.getLocation(element), + context.report(MISSING, context.getLocation(element), "Missing name attribute in <string> declaration", null); } else { String name = attribute.getValue(); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java index 4a0fde6..f747996 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java @@ -21,7 +21,6 @@ import static com.android.tools.lint.detector.api.LintConstants.TAG_STRING_ARRAY import com.android.annotations.VisibleForTesting; import com.android.resources.ResourceFolderType; -import com.android.tools.lint.client.api.Configuration; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Issue; @@ -29,6 +28,7 @@ import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -208,16 +208,15 @@ public class TypographyDetector extends ResourceXmlDetector { @Override public void beforeCheckProject(Context context) { - Configuration configuration = context.configuration; - mCheckDashes = configuration.isEnabled(DASHES); - mCheckQuotes = configuration.isEnabled(QUOTES); - mCheckFractions = configuration.isEnabled(FRACTIONS); - mCheckEllipsis = configuration.isEnabled(ELLIPSIS); - mCheckMisc = configuration.isEnabled(OTHER); + mCheckDashes = context.isEnabled(DASHES); + mCheckQuotes = context.isEnabled(QUOTES); + mCheckFractions = context.isEnabled(FRACTIONS); + mCheckEllipsis = context.isEnabled(ELLIPSIS); + mCheckMisc = context.isEnabled(OTHER); } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { NodeList childNodes = element.getChildNodes(); for (int i = 0, n = childNodes.getLength(); i < n; i++) { Node child = childNodes.item(i); @@ -239,12 +238,12 @@ public class TypographyDetector extends ResourceXmlDetector { } } - private void checkText(Context context, Node element, String text) { + private void checkText(XmlContext context, Node element, String text) { if (mCheckEllipsis) { // Replace ... with ellipsis character? int ellipsis = text.indexOf("..."); //$NON-NLS-1$ if (ellipsis != -1 && !text.startsWith(".", ellipsis + 3)) { //$NON-NLS-1$ - context.client.report(context, ELLIPSIS, context.getLocation(element), + context.report(ELLIPSIS, context.getLocation(element), ELLIPSIS_MESSAGE, null); } } @@ -264,7 +263,7 @@ public class TypographyDetector extends ResourceXmlDetector { Character.isWhitespace(matcher.group(1).charAt( matcher.group(1).length() - 1)); if (!isNegativeNumber) { - context.client.report(context, DASHES, context.getLocation(element), + context.report(DASHES, context.getLocation(element), EN_DASH_MESSAGE, null); } @@ -275,7 +274,7 @@ public class TypographyDetector extends ResourceXmlDetector { // Don't suggest replacing -- or "--" with an m dash since these are sometimes // used as digit marker strings if (emdash > 1 && !text.startsWith("-", emdash + 2)) { //$NON-NLS-1$ - context.client.report(context, DASHES, context.getLocation(element), + context.report(DASHES, context.getLocation(element), EM_DASH_MESSAGE, null); } } @@ -289,7 +288,7 @@ public class TypographyDetector extends ResourceXmlDetector { if (quoteEnd != -1 && quoteEnd > quoteStart + 1 && (quoteEnd < text.length() -1 || quoteStart > 0) && SINGLE_QUOTE.matcher(text).matches()) { - context.client.report(context, QUOTES, context.getLocation(element), + context.report(QUOTES, context.getLocation(element), SINGLE_QUOTE_MESSAGE, null); return; } @@ -297,7 +296,7 @@ public class TypographyDetector extends ResourceXmlDetector { // Check for apostrophes that can be replaced by typographic apostrophes if (quoteEnd == -1 && quoteStart > 0 && Character.isLetterOrDigit(text.charAt(quoteStart - 1))) { - context.client.report(context, QUOTES, context.getLocation(element), + context.report(QUOTES, context.getLocation(element), TYPOGRAPHIC_APOSTROPHE_MESSAGE, null); return; } @@ -309,7 +308,7 @@ public class TypographyDetector extends ResourceXmlDetector { int quoteEnd = text.indexOf('"', quoteStart + 1); if (quoteEnd != -1 && quoteEnd > quoteStart + 1) { if (quoteEnd < text.length() -1 || quoteStart > 0) { - context.client.report(context, QUOTES, context.getLocation(element), + context.report(QUOTES, context.getLocation(element), DBL_QUOTES_MESSAGE, null); return; } @@ -319,7 +318,7 @@ public class TypographyDetector extends ResourceXmlDetector { // Check for grave accent quotations if (text.indexOf('`') != -1 && GRAVE_QUOTATION.matcher(text).matches()) { // Are we indenting ``like this'' or `this' ? If so, complain - context.client.report(context, QUOTES, context.getLocation(element), + context.report(QUOTES, context.getLocation(element), GRAVE_QUOTE_MESSAGE, null); return; } @@ -338,19 +337,19 @@ public class TypographyDetector extends ResourceXmlDetector { String top = matcher.group(1); // Numerator String bottom = matcher.group(2); // Denominator if (top.equals("1") && bottom.equals("2")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.client.report(context, FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u00BD', "½", "1/2"), null); } else if (top.equals("1") && bottom.equals("4")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.client.report(context, FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u00BC', "¼", "1/4"), null); } else if (top.equals("3") && bottom.equals("4")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.client.report(context, FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u00BE', "¾", "3/4"), null); } else if (top.equals("1") && bottom.equals("3")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.client.report(context, FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u2153', "⅓", "1/3"), null); } else if (top.equals("2") && bottom.equals("3")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.client.report(context, FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u2154', "⅔", "2/3"), null); } } @@ -361,7 +360,7 @@ public class TypographyDetector extends ResourceXmlDetector { if (text.indexOf('(') != -1 && (text.contains("(c)") || text.contains("(C)"))) { //$NON-NLS-1$ //$NON-NLS-2$ // Suggest replacing with copyright symbol? - context.client.report(context, OTHER, context.getLocation(element), + context.report(OTHER, context.getLocation(element), COPYRIGHT_MESSAGE, null); // Replace (R) and TM as well? There are unicode characters for these but they // are probably not very common within Android app strings. 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 6af5060..94ee772 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 @@ -18,6 +18,7 @@ package com.android.tools.lint.checks; import static com.android.tools.lint.detector.api.LintConstants.ATTR_NAME; import static com.android.tools.lint.detector.api.LintConstants.DOT_JAVA; +import static com.android.tools.lint.detector.api.LintConstants.DOT_PNG; import static com.android.tools.lint.detector.api.LintConstants.DOT_XML; import static com.android.tools.lint.detector.api.LintConstants.RESOURCE_CLR_STYLEABLE; import static com.android.tools.lint.detector.api.LintConstants.RESOURCE_CLZ_ARRAY; @@ -35,11 +36,12 @@ 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.Location.Handle; import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -69,6 +71,7 @@ import java.util.Set; public class UnusedResourceDetector extends ResourceXmlDetector implements Detector.JavaScanner { private static final String ATTR_REF_PREFIX = "?attr/"; //$NON-NLS-1$ private static final String R_PREFIX = "R."; //$NON-NLS-1$ + private static final String R_ID_PREFIX = "R.id."; //$NON-NLS-1$ /** Unused resources (other than ids). */ public static final Issue ISSUE = Issue.create("UnusedResources", //$NON-NLS-1$ @@ -105,9 +108,9 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec protected Set<String> mDeclarations; protected Set<String> mReferences; protected Map<String, Attr> mIdToAttr; - protected Map<Attr, File> mAttrToFile; - protected Map<Attr, Location> mAttrToLocation; + protected Map<Attr, Handle> mAttrToLocation; protected Map<String, File> mDeclarationToFile; + protected Map<String, Handle> mDeclarationToHandle; /** * Constructs a new {@link UnusedResourceDetector} @@ -121,17 +124,23 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec } @Override + public boolean appliesTo(Context context, File file) { + return LintUtils.isXmlFile(file) || LintUtils.endsWith(file.getName(), DOT_JAVA); + } + + @Override public void beforeCheckProject(Context context) { mIdToAttr = new HashMap<String, Attr>(300); - mAttrToFile = new HashMap<Attr, File>(300); - mAttrToLocation = new HashMap<Attr, Location>(300); + mAttrToLocation = new HashMap<Attr, Handle>(300); mDeclarations = new HashSet<String>(300); mReferences = new HashSet<String>(300); mDeclarationToFile = new HashMap<String, File>(300); + mDeclarationToHandle = new HashMap<String, Handle>(300); } // ---- Implements JavaScanner ---- + @Override public void checkJavaSources(Context context, List<File> sourceFolders) { // For right now, this is hacked via String scanning in .java files instead. for (File dir : sourceFolders) { @@ -164,7 +173,7 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec private void addJavaDeclarations(Context context, File file) { // mDeclarations - String s = context.client.readFile(file); + String s = context.getClient().readFile(file); String[] lines = s.split("\n"); //$NON-NLS-1$ String currentType = null; for (int i = 0; i < lines.length; i++) { @@ -201,7 +210,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.client.readFile(file); + String s = context.getClient().readFile(file); if (s == null || s.length() <= 2) { return; } @@ -335,11 +344,11 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec unused.removeAll(styles); // Remove id's if the user has disabled reporting issue ids - if (unused.size() > 0 && !context.configuration.isEnabled(ISSUE_IDS)) { + if (unused.size() > 0 && !context.isEnabled(ISSUE_IDS)) { // Remove all R.id references List<String> ids = new ArrayList<String>(); for (String resource : unused) { - if (resource.startsWith("R.id.")) { //$NON-NLS-1$ + if (resource.startsWith(R_ID_PREFIX)) { ids.add(resource); } } @@ -357,16 +366,9 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec Location location = null; Attr attr = mIdToAttr.get(resource); if (attr != null) { - location = mAttrToLocation.get(attr); - if (location == null) { - File f = mAttrToFile.get(attr); - IDomParser parser = context.client.getParser(); - Position start = parser.getStartPosition(context, attr); - Position end = null; - if (start != null) { - end = parser.getEndPosition(context, attr); - } - location = new Location(f, start, end); + Handle handle = mAttrToLocation.get(attr); + if (handle != null) { + location = handle.resolve(); } } else { // Try to figure out the file if it's a file based resource (such as R.layout) -- @@ -378,28 +380,35 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec ResourceType type = ResourceType.getEnum(typeName); if (type != null && LintUtils.isFileBasedResourceType(type)) { String name = resource.substring(secondDot + 1); - File file = new File(context.project.getDir(), + File file = new File(context.getProject().getDir(), "res" + File.separator + typeName + File.separator + //$NON-NLS-1$ - name + ".xml"); //$NON-NLS-1$ + name + DOT_XML); if (file.exists()) { - location = new Location(file, null, null); + location = Location.create(file); + } else if (type == ResourceType.DRAWABLE) { + file = new File(file.getParentFile(), file.getName().substring(0, + file.getName().length() - DOT_XML.length()) + DOT_PNG); + if (file.exists()) { + location = Location.create(file); + } } } } if (location == null) { + Handle handle = mDeclarationToHandle.get(resource); + if (handle != null) { + location = handle.resolve(); + } + } + if (location == null) { File file = mDeclarationToFile.get(resource); if (file != null) { - location = new Location(file, null, null); + location = Location.create(file); } } - context.client.report(context, ISSUE, location, message, resource); + Issue issue = resource.startsWith(R_ID_PREFIX) ? ISSUE_IDS : ISSUE; + context.report(issue, location, message, resource); } - - mReferences = null; - mAttrToFile = null; - mAttrToLocation = null; - mIdToAttr = null; - mDeclarations = null; } @Override @@ -416,7 +425,7 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { if (TAG_RESOURCES.equals(element.getTagName())) { for (Element item : LintUtils.getChildren(element)) { String name = item.getAttribute(ATTR_NAME); @@ -436,6 +445,8 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec String resource = R_PREFIX + type + '.' + name; mDeclarations.add(resource); mDeclarationToFile.put(resource, context.file); + Handle handle = context.parser.createLocationHandle(context, item); + mDeclarationToHandle.put(resource, handle); } } } else { @@ -469,7 +480,7 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec } @Override - public void visitAttribute(Context context, Attr attribute) { + public void visitAttribute(XmlContext context, Attr attribute) { String value = attribute.getValue(); if (value.startsWith("@+") && !value.startsWith("@+android")) { //$NON-NLS-1$ //$NON-NLS-2$ String r = R_PREFIX + value.substring(2).replace('/', '.'); @@ -477,11 +488,10 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec // these here to get attributes for position info mDeclarations.add(r); mIdToAttr.put(r, attribute); - mAttrToFile.put(attribute, context.file); // It's important for this to be lightweight since we're storing ALL attribute // locations even if we don't know that we're going to have any unused resources! - mAttrToLocation.put(attribute, context.parser.getLocation(context, attribute)); - mDeclarationToFile.put(r, context.file); + IDomParser parser = context.parser; + mAttrToLocation.put(attribute, parser.createLocationHandle(context, attribute)); } else if (value.startsWith("@") //$NON-NLS-1$ && !value.startsWith("@android:")) { //$NON-NLS-1$ // Compute R-string, e.g. @string/foo => R.string.foo 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 2a634e6..7636869 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 @@ -23,13 +23,13 @@ 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; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; @@ -72,7 +72,7 @@ public class UseCompoundDrawableDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { int childCount = LintUtils.getChildCount(element); if (childCount == 2) { List<Element> children = LintUtils.getChildren(element); @@ -84,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.client.report(context, ISSUE, context.getLocation(element), + context.report(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 6a5e3ab..5fe9119 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 @@ -27,7 +27,6 @@ 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; @@ -35,6 +34,7 @@ import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -120,7 +120,7 @@ public class UselessViewDetector extends LayoutDetector { } @Override - public void visitElement(Context context, Element element) { + public void visitElement(XmlContext context, Element element) { int childCount = LintUtils.getChildCount(element); if (childCount == 0) { // Check to see if this is a leaf layout that can be removed @@ -132,7 +132,7 @@ public class UselessViewDetector extends LayoutDetector { } // This is the old UselessLayoutCheck from layoutopt - private void checkUselessMiddleLayout(Context context, Element element) { + private void checkUselessMiddleLayout(XmlContext context, Element element) { // Conditions: // - The node has children // - The node does not have siblings @@ -191,11 +191,11 @@ public class UselessViewDetector extends LayoutDetector { format += "; transfer the background attribute to the other view"; } String message = String.format(format, tag, parentTag); - context.client.report(context, USELESS_PARENT, location, message, null); + context.report(USELESS_PARENT, location, message, null); } // This is the old UselessView check from layoutopt - private void checkUselessLeaf(Context context, Element element) { + private void checkUselessLeaf(XmlContext context, Element element) { assert LintUtils.getChildCount(element) == 0; // Conditions: @@ -216,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.client.report(context, USELESS_LEAF, location, message, null); + context.report(USELESS_LEAF, location, message, null); } } diff --git a/lint/libs/lint_checks/tests/.settings/org.moreunit.prefs b/lint/libs/lint_checks/tests/.settings/org.moreunit.prefs new file mode 100644 index 0000000..a3b57cb --- /dev/null +++ b/lint/libs/lint_checks/tests/.settings/org.moreunit.prefs @@ -0,0 +1,5 @@ +#Fri Dec 02 12:58:10 PST 2011 +eclipse.preferences.version=1 +org.moreunit.prefixes= +org.moreunit.unitsourcefolder=lint_check-tests\:src\:lint-api\:src\#lint_check-tests\:src\:lint-checks\:src\#lint_check-tests\:src\:lint-cli\:src +org.moreunit.useprojectsettings=true 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 af37f06..7c47ceb 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 @@ -16,10 +16,12 @@ package com.android.tools.lint; -import com.android.tools.lint.detector.api.Context; +import com.android.tools.lint.detector.api.Location; +import com.android.tools.lint.detector.api.Location.Handle; import com.android.tools.lint.detector.api.Position; import com.android.tools.lint.detector.api.Project; import com.android.tools.lint.detector.api.Scope; +import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import org.w3c.dom.Document; @@ -63,8 +65,9 @@ public class PositionXmlParserTest extends TestCase { fw.write(xml); fw.close(); Project project = new Project(null, file.getParentFile(), file.getParentFile()); - Context context = new Context(new Main(), project, file, EnumSet.of(Scope.RESOURCE_FILE)); - Document document = parser.parse(context); + XmlContext context = new XmlContext(new Main(), project, file, + EnumSet.of(Scope.RESOURCE_FILE)); + Document document = parser.parseXml(context); assertNotNull(document); // Basic parsing heart beat tests @@ -79,8 +82,9 @@ public class PositionXmlParserTest extends TestCase { // Check attribute positions Attr attr = linearLayout.getAttributeNodeNS(ANDROID_URI, "layout_width"); assertNotNull(attr); - Position start = parser.getStartPosition(context, attr); - Position end = parser.getEndPosition(context, attr); + Location location = parser.getLocation(context, attr); + Position start = location.getStart(); + Position end = location.getEnd(); assertEquals(2, start.getLine()); assertEquals(xml.indexOf("android:layout_width"), start.getOffset()); assertEquals(2, end.getLine()); @@ -89,24 +93,33 @@ public class PositionXmlParserTest extends TestCase { // Check element positions Element button = (Element) buttons.item(0); - start = parser.getStartPosition(context, button); - end = parser.getEndPosition(context, button); + location = parser.getLocation(context, button); + start = location.getStart(); + end = location.getEnd(); assertEquals(6, start.getLine()); assertEquals(xml.indexOf("<Button"), start.getOffset()); assertEquals(xml.indexOf("/>") + 2, end.getOffset()); assertEquals(10, end.getLine()); int button1End = end.getOffset(); + Handle handle = parser.createLocationHandle(context, button); + Location location2 = handle.resolve(); + assertSame(location.getFile(), location.getFile()); + assertNotNull(location2.getStart()); + assertNotNull(location2.getEnd()); + assertEquals(6, location2.getStart().getLine()); + assertEquals(10, location2.getEnd().getLine()); Element button2 = (Element) buttons.item(1); - start = parser.getStartPosition(context, button2); - end = parser.getEndPosition(context, button2); + location = parser.getLocation(context, button2); + start = location.getStart(); + end = location.getEnd(); assertEquals(12, start.getLine()); assertEquals(xml.indexOf("<Button", button1End), start.getOffset()); assertEquals(xml.indexOf("/>", start.getOffset()) + 2, end.getOffset()); assertEquals(16, end.getLine()); - parser.dispose(context); + parser.dispose(context, document); 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 3227f0d..58f1045 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 @@ -266,7 +266,17 @@ abstract class AbstractCheckTest extends TestCase { StringBuilder sb = new StringBuilder(); if (location != null && location.getFile() != null) { - sb.append(location.getFile().getName()); + // Include parent directory for locations that have alternates, since + // frequently the file name is the same across different resource folders + // and we want to make sure in the tests that we're indeed passing the + // right files in as secondary locations + if (location.getSecondary() != null) { + sb.append(location.getFile().getParentFile().getName() + "/" + + location.getFile().getName()); + } else { + sb.append(location.getFile().getName()); + } + sb.append(':'); Position startPosition = location.getStart(); @@ -282,11 +292,38 @@ abstract class AbstractCheckTest extends TestCase { sb.append(' '); } - Severity severity = context.configuration.getSeverity(issue); + Severity severity = context.getConfiguration().getSeverity(issue); sb.append(severity.getDescription()); sb.append(": "); sb.append(message); + + if (location != null && location.getSecondary() != null) { + location = location.getSecondary(); + while (location != null) { + if (location.getMessage() != null) { + sb.append('\n'); + sb.append("=> "); + sb.append(location.getFile().getParentFile().getName() + "/" + + location.getFile().getName()); + sb.append(':'); + Position startPosition = location.getStart(); + if (startPosition != null) { + int line = startPosition.getLine(); + if (line >= 0) { + // line is 0-based, should display 1-based + sb.append(Integer.toString(line + 1)); + sb.append(':'); + } + } + sb.append(' '); + if (location.getMessage() != null) { + sb.append(location.getMessage()); + } + } + location = location.getSecondary(); + } + } mErrors.add(sb.toString()); } @@ -306,7 +343,7 @@ abstract class AbstractCheckTest extends TestCase { } @Override - public IDomParser getParser() { + public IDomParser getDomParser() { return new PositionXmlParser(); } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java index 91b012c..09da20b 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java @@ -26,10 +26,10 @@ public class ArraySizeDetectorTest extends AbstractCheckTest { } public void testArraySizes() throws Exception { assertEquals( - "arrays.xml: Warning: Array security_questions has an inconsistent number of items " + - "(3 in values-nl-rNL/arrays.xml, 4 in values-cs/arrays.xml)\n" + - "arrays.xml: Warning: Array signal_strength has an inconsistent number of items " + - "(5 in values/arrays.xml, 6 in values-land/arrays.xml)", + "values-cs/arrays.xml: Warning: Array security_questions has an inconsistent number of items (3 in values-nl-rNL/arrays.xml, 4 in values-cs/arrays.xml)\n" + + "=> values-nl-rNL/arrays.xml: Declaration with conflicting size\n" + + "values-land/arrays.xml: Warning: Array signal_strength has an inconsistent number of items (5 in values/arrays.xml, 6 in values-land/arrays.xml)\n" + + "=> values/arrays.xml: Declaration with conflicting size", lintProject( "res/values/arrays.xml", diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java index 6f1051a..635b5d5 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/DuplicateIdDetectorTest.java @@ -27,19 +27,17 @@ public class DuplicateIdDetectorTest extends AbstractCheckTest { public void testDuplicate() throws Exception { assertEquals( - "duplicate.xml:5: Warning: Duplicate id @+id/android_logo, already defined " + - "earlier in this layout", + "layout/duplicate.xml:5: Warning: Duplicate id @+id/android_logo, already defined earlier in this layout\n" + + "=> layout/duplicate.xml:4: @+id/android_logo originally defined here", lintFiles("res/layout/duplicate.xml")); } public void testDuplicateChains() throws Exception { assertEquals( - "layout1.xml: Warning: Duplicate id @+id/button1, already defined in layout " + - "layout2 which is included in this layout (layout1 => layout3 => layout2)\n" + - "layout1.xml: Warning: Duplicate id @+id/button2, already defined in layout " + - "layout2 which is included in this layout (layout1 => layout4 => layout2)\n" + - "layout2.xml: Warning: Duplicate id @+id/button1, already defined in layout " + - "layout4.xml which is included in this layout", + "layout/layout2.xml: Warning: Duplicate id @+id/button1, already defined in layout layout4.xml which is included in this layout\n" + + "=> layout/layout4.xml: @+id/button1 originally defined here\n" + + "layout1.xml: Warning: Duplicate id @+id/button1, already defined in layout layout2 which is included in this layout (layout1 => layout3 => layout2)\n" + + "layout1.xml: Warning: Duplicate id @+id/button2, already defined in layout layout2 which is included in this layout (layout1 => layout4 => layout2)", lintProject("res/layout/layout1.xml", "res/layout/layout2.xml", "res/layout/layout3.xml", "res/layout/layout4.xml")); diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java index 9ac9013..7c105ad 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/IconDetectorTest.java @@ -29,8 +29,8 @@ public class IconDetectorTest extends AbstractCheckTest { assertEquals( "Warning: Missing density variation folders in res: drawable-xhdpi\n" + "drawable-hdpi: Warning: Missing the following drawables in drawable-hdpi: sample_icon.gif (found in drawable-mdpi)\n" + + "drawable/ic_launcher.png: Warning: The ic_launcher.png icon has identical contents in the following configuration folders: drawable-mdpi, drawable\n" + "ic_launcher.png: Warning: Found bitmap drawable res/drawable/ic_launcher.png in densityless folder\n" + - "ic_launcher.png: Warning: The ic_launcher.png icon has identical contents in the following configuration folders: drawable-mdpi, drawable\n" + "sample_icon.gif: Warning: Using the .gif format for bitmaps is discouraged", lintProject( "res/drawable/ic_launcher.png", @@ -45,8 +45,8 @@ public class IconDetectorTest extends AbstractCheckTest { public void test2() throws Exception { assertEquals( "Warning: Missing density variation folders in res: drawable-mdpi, drawable-xhdpi\n" + - "other.9.png: Warning: The following unrelated icon files have identical contents: appwidget_bg.9.png, other.9.png\n" + - "unrelated.png: Warning: The following unrelated icon files have identical contents: ic_launcher.png, unrelated.png", + "drawable-hdpi/other.9.png: Warning: The following unrelated icon files have identical contents: appwidget_bg.9.png, other.9.png\n" + + "drawable-hdpi/unrelated.png: Warning: The following unrelated icon files have identical contents: ic_launcher.png, unrelated.png", lintProject( "res/drawable-hdpi/unrelated.png", "res/drawable-hdpi/appwidget_bg.9.png", @@ -59,8 +59,8 @@ public class IconDetectorTest extends AbstractCheckTest { public void testNoDpi() throws Exception { assertEquals( "Warning: Missing density variation folders in res: drawable-hdpi, drawable-xhdpi\n" + - "frame.png: Warning: The following images appear in both -nodpi and in a density folder: frame.png\n" + - "frame.png: Warning: The frame.png icon has identical contents in the following configuration folders: drawable-mdpi, drawable-nodpi, drawable-xlarge-nodpi-v11", + "drawable-xlarge-nodpi-v11/frame.png: Warning: The frame.png icon has identical contents in the following configuration folders: drawable-mdpi, drawable-nodpi, drawable-xlarge-nodpi-v11\n" + + "frame.png: Warning: The following images appear in both -nodpi and in a density folder: frame.png", lintProject( "res/drawable-mdpi/frame.png", "res/drawable-nodpi/frame.png", @@ -70,8 +70,8 @@ public class IconDetectorTest extends AbstractCheckTest { public void testNoDpi2() throws Exception { // Having additional icon names in the no-dpi folder should not cause any complaints assertEquals( - "frame.png: Warning: The following unrelated icon files have identical contents: frame.png, frame.png, frame.png, file1.png, file2.png, frame.png\n" + - "frame.png: Warning: The image frame.png varies significantly in its density-independent (dip) size across the various density versions: drawable-ldpi/frame.png: 629x387 dp (472x290 px), drawable-mdpi/frame.png: 472x290 dp (472x290 px), drawable-hdpi/frame.png: 315x193 dp (472x290 px), drawable-xhdpi/frame.png: 236x145 dp (472x290 px)", + "drawable-xhdpi/frame.png: Warning: The following unrelated icon files have identical contents: frame.png, frame.png, frame.png, file1.png, file2.png, frame.png\n" + + "drawable-xhdpi/frame.png: Warning: The image frame.png varies significantly in its density-independent (dip) size across the various density versions: drawable-ldpi/frame.png: 629x387 dp (472x290 px), drawable-mdpi/frame.png: 472x290 dp (472x290 px), drawable-hdpi/frame.png: 315x193 dp (472x290 px), drawable-xhdpi/frame.png: 236x145 dp (472x290 px)", lintProject( "res/drawable-mdpi/frame.png=>res/drawable-mdpi/frame.png", diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OverdrawDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OverdrawDetectorTest.java index 5e6ffeb..d203dbb 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OverdrawDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OverdrawDetectorTest.java @@ -27,7 +27,7 @@ public class OverdrawDetectorTest extends AbstractCheckTest { public void test() throws Exception { assertEquals( - "main.xml:5: Warning: Possible overdraw: Root element paints background @drawable/ic_launcher with a theme that also paints a background (inferred theme is @style/MyTheme.First)\n" + + "main.xml:5: Warning: Possible overdraw: Root element paints background @drawable/ic_launcher with a theme that also paints a background (inferred theme is @style/MyTheme_First)\n" + "second.xml:5: Warning: Possible overdraw: Root element paints background @drawable/ic_launcher with a theme that also paints a background (inferred theme is @style/MyTheme)\n" + "sixth.xml:4: Warning: Possible overdraw: Root element paints background @drawable/custombg with a theme that also paints a background (inferred theme is @style/MyTheme)\n" + "third.xml:5: Warning: Possible overdraw: Root element paints background @drawable/ic_launcher with a theme that also paints a background (inferred theme is @style/MyTheme_Third)", diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ProguardDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ProguardDetectorTest.java index eb0aae4..019effd 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ProguardDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ProguardDetectorTest.java @@ -27,7 +27,7 @@ public class ProguardDetectorTest extends AbstractCheckTest { public void testProguard() throws Exception { assertEquals( - "proguard.cfg: Error: Obsolete proguard file; use -keepclasseswithmembers " + + "proguard.cfg:21: Error: Obsolete proguard file; use -keepclasseswithmembers " + "instead of -keepclasseswithmembernames", lintFiles("proguard.cfg")); } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SecurityDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SecurityDetectorTest.java index d50fe1b..3b55b6d 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SecurityDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SecurityDetectorTest.java @@ -68,6 +68,4 @@ public class SecurityDetectorTest extends AbstractCheckTest { "grantpermission.xml=>AndroidManifest.xml", "res/values/strings.xml")); } - - } 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 bbbb8ae..96590ba 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 @@ -40,12 +40,16 @@ public class UnusedResourceDetectorTest extends AbstractCheckTest { public void testUnused() throws Exception { mEnableIds = false; assertEquals( - "Warning: The resource R.layout.main appears to be unused\n" + - "Warning: The resource R.layout.other appears to be unused\n" + - "Warning: The resource R.string.hello appears to be unused\n" + - "accessibility.xml: Warning: The resource R.layout.accessibility appears to be unused", + "accessibility.xml: Warning: The resource R.layout.accessibility appears to be unused\n" + + "main.xml: Warning: The resource R.layout.main appears to be unused\n" + + "other.xml: Warning: The resource R.layout.other appears to be unused\n" + + "strings2.xml:3: Warning: The resource R.string.hello appears to be unused", lintProject( + "res/values/strings2.xml", + "res/layout/layout1.xml=>res/layout/main.xml", + "res/layout/layout1.xml=>res/layout/other.xml", + // Rename .txt files to .java "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java", "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java", diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/strings2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/strings2.xml new file mode 100644 index 0000000..bce9e32 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/strings2.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="hello">Hello</string> +</resources> + diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/DefaultSdkInfoTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/DefaultSdkInfoTest.java new file mode 100644 index 0000000..5b44c79 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/DefaultSdkInfoTest.java @@ -0,0 +1,54 @@ +/* + * 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.client.api; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class DefaultSdkInfoTest extends TestCase { + public void testGetParentClass() { + DefaultSdkInfo info = new DefaultSdkInfo(); + assertNull(info.getParentViewClass("android.view.View")); + assertEquals("android.view.View", info.getParentViewClass("android.view.ViewGroup")); + assertEquals("android.view.ViewGroup", + info.getParentViewClass("android.widget.LinearLayout")); + assertEquals("android.widget.LinearLayout", + info.getParentViewClass("android.widget.TableLayout")); + } + + public void testGetParentName() { + DefaultSdkInfo info = new DefaultSdkInfo(); + assertNull(info.getParentViewName("View")); + assertEquals("View", info.getParentViewName("ViewGroup")); + assertEquals("ViewGroup", info.getParentViewName("LinearLayout")); + assertEquals("LinearLayout", info.getParentViewName("TableLayout")); + } + + public void testIsSubViewOf() { + DefaultSdkInfo info = new DefaultSdkInfo(); + assertTrue(info.isSubViewOf("Button", "Button")); + assertTrue(info.isSubViewOf("TextView", "Button")); + assertFalse(info.isSubViewOf("Button", "TextView")); + assertFalse(info.isSubViewOf("CheckBox", "ToggleButton")); + assertFalse(info.isSubViewOf("ToggleButton", "CheckBox")); + assertTrue(info.isSubViewOf("LinearLayout", "LinearLayout")); + assertTrue(info.isSubViewOf("LinearLayout", "TableLayout")); + assertFalse(info.isSubViewOf("TableLayout", "LinearLayout")); + assertTrue(info.isSubViewOf("TextView", "EditText")); + assertFalse(info.isSubViewOf("EditText", "TextView")); + } +} |