blob: c7f8967e4597612e2a0cccc84eae1a9b6c01b522 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.lint.checks.studio
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.intellij.psi.CommonClassNames.JAVA_IO_FILE
import com.intellij.psi.CommonClassNames.JAVA_UTIL_OBJECTS
import org.jetbrains.uast.UBinaryExpression
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UastBinaryOperator
/**
* Makes sure we compare files properly to handle cross platform issues like
* case insensitive file systems
*
* TODO: Check for calling file.toURL or file.toURI.toURL: use our sdk utils instead
*/
class FileComparisonDetector : Detector(), SourceCodeScanner {
companion object Issues {
private val IMPLEMENTATION = Implementation(
FileComparisonDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
@JvmField
val ISSUE = Issue.create(
id = "FileComparisons",
briefDescription = "Invalid File Comparisons",
explanation = """
Never call `equals` (or worse, `==`) on a `java.io.File`:
this will not do the right thing on case insensitive file systems.
Always call `equals` instead of identity equals on VirtualFiles.
For more info, see `go/files-howto`.
""",
category = CROSS_PLATFORM,
priority = 3,
severity = Severity.ERROR,
platforms = STUDIO_PLATFORMS,
implementation = IMPLEMENTATION
)
}
override fun getApplicableUastTypes(): List<Class<out UElement>>? =
listOf(UBinaryExpression::class.java, UCallExpression::class.java)
override fun createUastHandler(context: JavaContext): UElementHandler? {
return object : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
if (node.methodName == "equals")
if (node.valueArgumentCount == 1) {
val lhs = node.receiver ?: return
check(context, node, lhs, node.valueArguments.first())
} else if (node.valueArgumentCount == 2) {
if (!context.evaluator.isMemberInClass(node.resolve(), JAVA_UTIL_OBJECTS)) {
return
}
check(context, node, node.valueArguments[0], node.valueArguments[1])
}
}
override fun visitBinaryExpression(node: UBinaryExpression) {
val operator = node.operator
if (operator == UastBinaryOperator.EQUALS ||
operator == UastBinaryOperator.IDENTITY_EQUALS ||
operator == UastBinaryOperator.NOT_EQUALS ||
operator == UastBinaryOperator.IDENTITY_NOT_EQUALS
) {
check(context, node, node.leftOperand, node.rightOperand)
}
}
}
}
private fun check(
context: JavaContext,
node: UExpression,
lhs: UExpression,
rhs: UExpression
) {
val evaluator = context.evaluator
if (!evaluator.typeMatches(lhs.getExpressionType(), JAVA_IO_FILE)) {
return
}
if (!evaluator.typeMatches(rhs.getExpressionType(), JAVA_IO_FILE)) {
return
}
context.report(
ISSUE, node, context.getLocation(node),
"Do not compare java.io.File with `equals` or `==`: will not work correctly on case insensitive file systems! See `go/files-howto`."
)
}
}