blob: 631d7d662a2cee01cce72a2cdabb7a07a018b58b [file] [log] [blame]
/*
* 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.
*/
@file:JvmName("XmlScannerKt")
package com.android.tools.lint.detector.api
import com.android.resources.ResourceFolderType
import com.android.tools.lint.client.api.LintDriver
import com.android.tools.lint.client.api.XmlParser
import com.google.common.annotations.Beta
import org.w3c.dom.Attr
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.io.File
internal const val SUPPRESS_XML_COMMENT_PREFIX = "<!--suppress "
/** The prefix is usually //noinspection but it can also be @noinspection
* (such as / ** @noinspection ClassNameDiffersFromFileName * /
* in a javadoc, so just use the basename as the prefix */
internal const val SUPPRESS_JAVA_COMMENT_PREFIX = "noinspection "
/**
* A [Context] used when checking XML files.
*
* **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.**
*/
@Beta
open class XmlContext(
/** the driver running through the checks */
driver: LintDriver,
/** the project containing the file being checked */
project: Project,
/**
* The "main" project. For normal projects, this is the same as [.project],
* but for library projects, it's the root project that includes (possibly indirectly)
* the various library projects and their library projects.
*
* Note that this is a property on the [Context], not the
* [Project], since a library project can be included from multiple
* different top level projects, so there isn't **one** main project,
* just one per main project being analyzed with its library projects.
*/
main: Project?,
/** the file being checked */
file: File,
/** the [ResourceFolderType] of this file, if any */
folderType: ResourceFolderType?,
/** The XML parser */
val parser: XmlParser,
/** The XML contents of the file */
contents: String,
/** The XML document */
@JvmField // backwards compatibility
val document: Document
) : ResourceContext(driver, project, main, file, folderType, contents) {
/**
* 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
*/
fun getLocation(node: Node): Location = parser.getLocation(this, node)
/**
* Returns the location for name-portion of the given element or attribute.
*
* @param node the node to look up the location for
*
* @return the location for the node
*/
fun getNameLocation(node: Node): Location = parser.getNameLocation(this, node)
/**
* Returns the location for value-portion of the given attribute
*
* @param node the node to look up the location for
*
* @return the location for the node
*/
fun getValueLocation(node: Attr): Location = parser.getValueLocation(this, node)
/**
* Create a location suitable for highlighting an element.
* <p>
* In some cases, you want to point to an element (for example
* where it is missing an attribute, so you can't point to the
* attribute itself). However, some elements can span multiple
* lines. When running in the IDE, you don't want the entire
* element range to be highlighted. For an error on the root
* tag of a layout for example, it would make the entire editor
* light up in red.
* <p>
* In earlier versions, lint would special case [getLocation]
* for elements and deliberate treat it as [getNameLocation]
* instead. However, that's problematic since locations are not
* just used for error highlighting, but also for features such
* as quickfixes, where it's Very Very Badâ„¢ to have the range
* magically change to some subset.
* <p>
* This method instead creates error ranges intended for warning
* display purposes. If [node] is non null, the location for that
* node will be used. Otherwise, if [attribute] is provided it will
* highlight the given attribute range if the attribute is
* specified. A common example of this is the "name" attribute
* in resource values. If not passed in or not defined on the
* element, this method will use the element range if it fits
* on a single line; otherwise it will use just the tag name
* range.
*/
fun getElementLocation(
element: Element,
node: Node? = null,
namespace: String? = null,
attribute: String? = null
): Location = parser.getElementLocation(this, element, node, namespace, attribute)
/**
* Convenience wrapper for java so you don't have to specify default attributes
*/
fun getElementLocation(element: Element): Location = parser.getElementLocation(this, element)
/**
* Creates a new location within an XML text node
*
* @param textNode the text node
*
* @param begin the start offset within the text node (inclusive)
*
* @param end the end offset within the text node (exclusive)
*
* @return a new location
*/
fun getLocation(textNode: Node, begin: Int, end: Int): Location {
assert(textNode.nodeType == Node.TEXT_NODE || textNode.nodeType == Node.COMMENT_NODE)
return parser.getLocation(this, textNode, begin, end)
}
/**
* Reports an issue applicable to a given DOM node. The DOM node is used as the
* scope to check for suppress lint annotations.
*
* @param issue the issue to report
*
* @param scope the DOM node scope the error applies to. The lint infrastructure will
* check whether there are suppress directives on this node (or its
* enclosing nodes) and if so suppress the warning without involving the
* client.
*
* @param location the location of the issue, or null if not known
*
* @param message the message for this warning
*
* @param quickfixData optional data to pass to the IDE for use by a quickfix.
*/
@JvmOverloads
fun report(
issue: Issue,
scope: Node?,
location: Location,
message: String,
quickfixData: LintFix? = null
) {
if (scope != null && driver.isSuppressed(this, issue, scope)) {
return
}
super.doReport(issue, location, message, quickfixData)
}
@Deprecated(
"Here for temporary compatibility; the new typed quickfix data parameter " +
"should be used instead",
ReplaceWith("report(issue, scope, location, message)")
)
fun report(
issue: Issue,
scope: Node?,
location: Location,
message: String,
quickfixData: Any?
) = report(issue, scope, location, message)
override fun report(
issue: Issue,
location: Location,
message: String,
quickfixData: LintFix?
) {
// Warn if clients use the non-scoped form? No, there are cases where an
// XML detector's error isn't applicable to one particular location (or it's
// not feasible to compute it cheaply)
// driver.getClient().log(null, "Warning: Issue " + issue
// + " was reported without a scope node: Can't be suppressed.");
// For now just check the document root itself
if (driver.isSuppressed(this, issue, document)) {
return
}
super.report(issue, location, message, quickfixData)
}
override val suppressCommentPrefix: String?
get() = SUPPRESS_XML_COMMENT_PREFIX
fun isSuppressedWithComment(node: Node, issue: Issue): Boolean {
// Check whether there is a comment marker
getContents() ?: return false
val start = parser.getNodeStartOffset(this, node)
if (start != -1) {
return isSuppressedWithComment(start, issue)
}
return false
}
fun createLocationHandle(node: Node): Location.Handle =
parser.createLocationHandle(this, node)
override val resourceFolder: File?
// Like super, but for the parent folder instead of the context file
get() = if (resourceFolderType != null) file.parentFile else null
}