blob: da94379cbd8f4a2982a0d3f4ae2d15ccfa99b769 [file] [log] [blame]
/*
* Copyright (C) 2017 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.testutils.TestUtils
import com.android.tools.lint.checks.infrastructure.TestFiles.java
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
import com.android.tools.lint.detector.api.LintFix.ReplaceString
import com.android.tools.lint.detector.api.LintFix.SetAttribute
import com.google.common.collect.Maps
import com.google.common.truth.Truth.assertThat
import com.intellij.psi.PsiMethod
import junit.framework.TestCase
import org.intellij.lang.annotations.Language
import org.jetbrains.uast.UCallExpression
import java.math.BigDecimal
import java.util.ArrayList
import java.util.HashMap
import java.util.regex.Pattern
class LintFixTest : TestCase() {
fun testBasic() {
val builder = LintFix.create().map("foo", 3, BigDecimal(50))
builder.put("name1", "Name1")
builder.put("name2", "Name2")
builder.put(Maps.newHashMap<Any, Any>())
builder.put(ArrayList<Any>())
builder.put<Any>(null) // no-op
val quickfixData = builder.build() as LintFix.DataMap
assertThat(quickfixData.get(Float::class.java)).isNull()
assertThat(quickfixData.get(String::class.java)).isEqualTo("foo")
assertThat(quickfixData.get(Int::class.java)).isEqualTo(3)
assertThat(quickfixData.get(BigDecimal::class.java)).isEqualTo(BigDecimal(50))
assertThat(quickfixData.get("name1")).isEqualTo("Name1")
assertThat(quickfixData.get("name2")).isEqualTo("Name2")
var foundString = false
var foundInteger = false
var foundBigDecimal = false
for (data in quickfixData) { // no order guarantee
when (data) {
"foo" -> foundString = true
3 -> foundInteger = true
is BigDecimal -> foundBigDecimal = true
}
}
assertThat(foundString).isTrue()
assertThat(foundInteger).isTrue()
assertThat(foundBigDecimal).isTrue()
TestCase.assertNotNull(LintFix.getData(quickfixData, BigDecimal::class.java))
// Check key conversion to general interface
TestCase.assertNull(LintFix.getData(quickfixData, ArrayList::class.java))
TestCase.assertNotNull(LintFix.getData(quickfixData, List::class.java))
TestCase.assertNull(LintFix.getData(quickfixData, HashMap::class.java))
TestCase.assertNotNull(LintFix.getData(quickfixData, Map::class.java))
}
fun testClassInheritance() {
val builder = LintFix.create().map(Integer.valueOf(5))
val quickfixData = builder.build() as LintFix.DataMap
assertThat(quickfixData.get(Int::class.java)).isEqualTo(5)
// Looking up with a more general class:
assertThat(quickfixData.get(Number::class.java)).isEqualTo(5)
}
fun testSetAttribute() {
val fixData = LintFix.create().set().namespace("namespace")
.attribute("attribute").value("value").build() as SetAttribute
assertThat(fixData.namespace).isEqualTo("namespace")
assertThat(fixData.attribute).isEqualTo("attribute")
assertThat(fixData.value).isEqualTo("value")
}
fun testReplaceString() {
var fixData = LintFix.create().replace().text("old")
.with("new").build() as ReplaceString
assertThat(fixData.oldString).isEqualTo("old")
assertThat(fixData.replacement).isEqualTo("new")
assertThat(fixData.oldPattern).isNull()
fixData = LintFix.create().replace().pattern("(oldPattern)")
.with("new").build() as ReplaceString
assertThat(fixData.oldPattern).isEqualTo("(oldPattern)")
assertThat(fixData.replacement).isEqualTo("new")
assertThat(fixData.oldString).isNull()
fixData = LintFix.create().replace().pattern("oldPattern").with("new")
.build() as ReplaceString
assertThat(fixData.oldPattern).isEqualTo("(oldPattern)")
assertThat(fixData.replacement).isEqualTo("new")
assertThat(fixData.oldString).isNull()
}
fun testGroupMatching() {
val fix = LintFix.create().replace().pattern("abc\\((\\d+)\\)def")
.with("Number was \\k<1>! I said \\k<1>!").build() as ReplaceString
TestCase.assertTrue(fix.oldPattern != null)
val matcher = Pattern.compile(fix.oldPattern).matcher("abc(42)def")
TestCase.assertTrue(matcher.matches())
val expanded = fix.expandBackReferences(matcher)
TestCase.assertEquals("Number was 42! I said 42!", expanded)
}
fun testMatching() {
lint().files(
java(
"" +
"package test.pkg;\n" +
"import android.util.Log;\n" +
"public class Test {\n" +
" void test() {\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" }\n" +
"}"
)
)
.detector(SampleTestDetector())
.sdkHome(TestUtils.getSdk())
.run()
.expect(
"src/test/pkg/Test.java:5: Warning: Sample test message [TestIssueId]\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" ~~~\n" +
"0 errors, 1 warnings\n"
)
.verifyFixes().window(1).expectFixDiffs(
"" +
"Fix for src/test/pkg/Test.java line 4: Fix Description:\n" +
"@@ -5 +5\n" +
" void test() {\n" +
"- Log.d(\"TAG\", \"msg\");\n" +
"+ MyLogger.d(\"msg\"); // Was: Log.d(\"TAG\", \"msg\");\n" +
" }\n"
)
}
/**
* Detector which makes use of a couple of lint fix string replacement features:
* (1) ranges (larger than error range, and (2) back references
*/
class SampleTestDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> {
return listOf("d")
}
override fun visitMethodCall(
context: JavaContext,
node: UCallExpression,
method: PsiMethod
) {
val evaluator = context.evaluator
if (evaluator.isMemberInClass(method, "android.util.Log")) {
val source = node.asSourceString()
@Language("RegExp")
val oldPattern = "(${Pattern.quote(source)})"
val receiver = node.receiver!!
var replacement = source.replace(
receiver.asSourceString(),
"MyLogger"
) + "; // Was: \\k<1>"
replacement = replacement.replace("\"TAG\", ", "")
val fix = LintFix.create()
.name("Fix Description")
.replace().pattern(oldPattern).with(replacement)
.range(context.getLocation(node))
.shortenNames()
.build()
context.report(
SAMPLE_ISSUE, context.getLocation(receiver),
"Sample test message", fix
)
}
}
companion object {
val SAMPLE_ISSUE = Issue.create(
"TestIssueId", "Not applicable", "Not applicable",
Category.MESSAGES, 5, Severity.WARNING,
Implementation(
SampleTestDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
}
}
}