blob: 5bcaf387aff7e63d1972a8e08cb761784031df62 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.lint.client.api
import com.android.SdkConstants
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Context
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.Location
import com.android.tools.lint.detector.api.Project
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.guessGradleLocationForFile
import java.io.File
/**
* Consult the lint.xml file, but override with the --enable and --disable flags supplied on the
* command line (as well as any other applicable flags)
*/
open class FlagConfiguration(configurations: ConfigurationHierarchy) : Configuration(configurations) {
var associatedLocation: Location? = null
open fun fatalOnly(): Boolean = false
open fun isWarningsAsErrors(): Boolean = false
open fun isIgnoreWarnings(): Boolean = false
open fun isCheckAllWarnings(): Boolean = false
open fun allowSuppress(): Boolean = true
open fun disabledIds(): Set<String> = emptySet()
open fun enabledIds(): Set<String> = emptySet()
open fun exactCheckedIds(): Set<String>? = null
open fun disabledCategories(): Set<Category>? = null
open fun enabledCategories(): Set<Category>? = null
open fun exactCategories(): Set<Category>? = null
open fun severityOverride(issue: Issue): Severity? = null
open fun severityOverrides(): Set<String> = emptySet()
override fun getDefinedSeverity(issue: Issue, source: Configuration): Severity? {
if (issue.suppressNames != null) {
return getDefaultSeverity(issue)
}
var severity = computeSeverity(issue, source)
if (fatalOnly()) {
if (severity == null) {
val configuredSeverity =
client.configurations.getDefinedSeverityWithoutOverride(source, issue)
if (configuredSeverity != null && configuredSeverity == Severity.FATAL) {
return configuredSeverity
} else if (configuredSeverity != null) {
return Severity.IGNORE
} else if (issue.defaultSeverity !== Severity.FATAL) {
return Severity.IGNORE
}
} else if (severity !== Severity.FATAL) {
return Severity.IGNORE
}
}
if (fatalOnly() && (
severity == null && issue.defaultSeverity !== Severity.FATAL ||
severity != null && severity !== Severity.FATAL
)
) {
return Severity.IGNORE
}
val impliedSeverity = severity ?: getDefaultSeverity(issue)
if (isWarningsAsErrors() && impliedSeverity === Severity.WARNING) {
if (issue === IssueRegistry.BASELINE) {
// Don't promote the baseline informational issue
// (number of issues promoted) to error
return severity
}
severity = Severity.ERROR
}
if (isIgnoreWarnings() && impliedSeverity === Severity.WARNING) {
severity = Severity.IGNORE
}
return severity
}
private fun unsupported(): Nothing {
error("This method should not be invoked on a synthetic (non XML) configuration")
}
override fun ignore(context: Context, issue: Issue, location: Location?, message: String) {
unsupported()
}
override fun ignore(issue: Issue, file: File) {
unsupported()
}
override fun ignore(issueId: String, file: File) {
unsupported()
}
override fun setSeverity(issue: Issue, severity: Severity?) {
unsupported()
}
override var baselineFile: File? = parent?.baselineFile
override fun getDefaultSeverity(issue: Issue): Severity {
return if (isCheckAllWarnings()) {
if (neverEnabledImplicitly(issue)) {
super.getDefaultSeverity(issue)
} else issue.defaultSeverity
} else super.getDefaultSeverity(issue)
}
private fun neverEnabledImplicitly(issue: Issue): Boolean {
// Exclude the inter-procedural check from the "enable all warnings" flag;
// it's much slower and still triggers various bugs in UAST that can affect
// other checks.
@Suppress("SpellCheckingInspection")
return issue.id == "WrongThreadInterprocedural"
}
private fun computeSeverity(issue: Issue, source: Configuration): Severity? {
if (issue.suppressNames != null && !allowSuppress()) {
return getDefaultSeverity(issue)
}
val severity = parent?.getDefinedSeverity(issue, source)
// Issue not allowed to be suppressed?
val id = issue.id
val suppress = disabledIds()
if (suppress.contains(id)) {
return Severity.IGNORE
}
val disabledCategories: Set<Category>? = disabledCategories()
if (disabledCategories != null) {
val category = issue.category
if (disabledCategories.contains(category) ||
category.parent != null && disabledCategories.contains(category.parent)
) {
return Severity.IGNORE
}
}
val manual = severityOverride(issue)
if (manual != null) {
return manual
}
val enabled = enabledIds()
val exact = exactCheckedIds()
val enabledCategories = enabledCategories()
val exactCategories = exactCategories()
val category = issue.category
if (exact != null) {
if (exact.contains(id)) {
return getVisibleSeverity(issue, severity, source)
} else if (category !== Category.LINT) {
return Severity.IGNORE
}
}
if (exactCategories != null) {
if (exactCategories.contains(category) ||
category.parent != null && exactCategories.contains(category.parent)
) {
return getVisibleSeverity(issue, severity, source)
} else if (category !== Category.LINT ||
disabledCategories()?.contains(Category.LINT) == true
) {
return Severity.IGNORE
}
}
if (enabled.contains(id) ||
enabledCategories != null && (
enabledCategories.contains(category) ||
category.parent != null && enabledCategories.contains(category.parent)
) ||
isCheckAllWarnings() && !neverEnabledImplicitly(issue)
) {
return getVisibleSeverity(issue, severity, source)
}
return severity
}
/**
* Returns the given severity, but if not visible, use the default. If an override
* [severity] is configured (from an inherited override configuration) use it, otherwise
* try to compute the severity from the [source] configuration (without applying overrides),
* and finally use default severity of the issue.
*/
private fun getVisibleSeverity(
issue: Issue,
severity: Severity?,
source: Configuration
): Severity {
val configuredSeverity = client.configurations.getDefinedSeverityWithoutOverride(source, issue)
if (configuredSeverity != null && configuredSeverity != Severity.IGNORE) {
if (configuredSeverity == Severity.WARNING && isWarningsAsErrors()) {
return Severity.ERROR
}
return configuredSeverity
}
// Overriding default
// Detectors shouldn't be returning ignore as a default severity,
// but in case they do, force it up to warning here to ensure that
// it's run
var visibleSeverity = severity
if (visibleSeverity == null || visibleSeverity === Severity.IGNORE) {
visibleSeverity = issue.defaultSeverity
if (visibleSeverity === Severity.IGNORE) {
if (isWarningsAsErrors()) {
visibleSeverity = Severity.ERROR
} else {
visibleSeverity = Severity.WARNING
}
}
}
return visibleSeverity
}
/**
* Already validated this issue? We can encounter the same configuration multiple times
* when searching up the parent tree. (We can't skip calling the parent because the
* parent references can change over time.)
*/
private var validated = false
override fun validateIssueIds(
client: LintClient,
driver: LintDriver,
project: Project?,
registry: IssueRegistry
) {
parent?.validateIssueIds(client, driver, project, registry)
if (validated) {
return
}
validated = true
validateIssueIds(client, driver, project, registry, disabledIds())
validateIssueIds(client, driver, project, registry, enabledIds())
validateIssueIds(client, driver, project, registry, severityOverrides())
exactCheckedIds()?.let { validateIssueIds(client, driver, project, registry, it) }
}
protected fun validateIssueIds(
client: LintClient,
driver: LintDriver,
project: Project?,
registry: IssueRegistry,
ids: Collection<String>
) {
for (id in ids) {
if (id == SdkConstants.SUPPRESS_ALL) {
// builtin special "id" which means all id's
continue
}
if (registry.getIssue(id) == null) {
// You can also configure issues by categories; don't flag these
if (registry.isCategoryName(id)) {
continue
}
reportNonExistingIssueId(client, driver, registry, project, id)
}
}
parent?.validateIssueIds(client, driver, project, registry)
}
override fun getIssueConfigLocation(
issue: String,
specificOnly: Boolean,
severityOnly: Boolean
): Location? {
overrides?.getIssueConfigLocation(issue, specificOnly, severityOnly)?.let {
return it
}
if (associatedLocation != null) {
val file = associatedLocation?.file
if (file != null) {
return guessGradleLocationForFile(configurations.client, file, issue)
}
}
return parent?.getIssueConfigLocation(issue, specificOnly, severityOnly)
?: associatedLocation
}
}