public class VersionNameReferencedDetector extends Detector implements SourceCodeScanner {
private static final String VERSION_NAME = "VERSION_NAME";
public static final Issue VERSION_NAME_REFERENCED_ISSUE = Issue.create(
// ID: used in @SuppressLint warnings etc
"VersionNameReferenced",
// Title -- shown in the IDE's preference dialog, as category headers
// in the Analysis results window, etc
"Referencing BuildConfig.VERSION_NAME in source code",
// Full explanation of the issue; you can use some markdown markup
// such as `monospace`, *italic*, and **bold**.
"Version name is an entirely user-facing build descriptor and means " +
"nothing with relation to the app itself. For example, while unlikely, it is " +
"entirely possible for an app to be released with a version name of 2.0, only to " +
"then be released with a version name of 1.0. Therefore, developers should never " +
"reference version name for any logic, as it may lead to breaking changes. Instead, " +
"use version code which must always increment, or find another way to achieve the " +
"desired behavior.",
Category.CORRECTNESS,
// Range 1-10
8,
Severity.ERROR,
// Tell lint which files to look at, and which Detector to use (this one)
new Implementation(VersionNameReferencedDetector.class, Scope.JAVA_FILE_SCOPE));
/**
* Constructs a new {@link VersionNameReferencedDetector}
*/
public VersionNameReferencedDetector() {
}
public static Issue[] getIssues() {
return new Issue[]{
VERSION_NAME_REFERENCED_ISSUE
};
}
@Nullable
@Override
public List<String> getApplicableReferenceNames() {
return Collections.singletonList(VERSION_NAME);
}
@Override
public void visitReference(JavaContext context, UReferenceExpression reference, PsiElement referenced) {
// Not being called!
// ...
}
}
@Language("JAVA")
public static final String CODE_WITH_VERSION_NAME = "" +
"package com.example.lint;\n" +
"\n" +
"import com.android.ddmlib.Log;\n" +
"\n" +
"public class TestClass {\n" +
"\n" +
" public static void main(String[] args) {\n" +
" \n" +
" String name = BuildConfig.VERSION_NAME;\n" +
" Log.d(\"TAG\", name);\n" +
" \n" +
" }\n" +
"\n" +
"}";
public void testCodeWithVersionName() {
lint()
.sdkHome(sdkHome)
.files(java(TestStrings.CODE_WITH_VERSION_NAME))
.run()
.expect("Warning here");
}
if (mVisitReferences) {
val list = referenceDetectors[node.identifier]
if (list != null) {
val referenced = node.resolve() // <-- This returns null
if (referenced != null) { // So the check does not pass here
for (v in list) {
val uastScanner = v.uastScanner
uastScanner.visitReference(mContext, node, referenced)
}
}
}
}
/*
* Copyright (C) 2018 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
import com.android.tools.lint.detector.api.Category
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.PsiElement
import com.intellij.psi.PsiField
import org.jetbrains.uast.UReferenceExpression
class VersionNameDetector : SourceCodeScanner {
companion object Issues {
private val IMPLEMENTATION = Implementation(
VersionNameDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
@JvmField
val ISSUE = Issue.create(
id = "VersionNameReferenced",
briefDescription = "Referencing BuildConfig.VERSION_NAME in source code",
explanation = """
Version name is an entirely user-facing build descriptor and means nothing with \
relation to the app itself. For example, while unlikely, it is entirely possible \
for an app to be released with a version name of 2.0, only to then be released \
with a version name of 1.0. Therefore, developers should never reference version \
name for any logic, as it may lead to breaking changes. Instead, use version code \
which must always increment, or find another way to achieve the desired behavior.""",
category = Category.CORRECTNESS,
priority = 8,
severity = Severity.ERROR,
implementation = IMPLEMENTATION
)
}
override fun getApplicableReferenceNames(): List<String>? = listOf("VERSION_NAME")
override fun visitReference(
context: JavaContext,
reference: UReferenceExpression,
referenced: PsiElement
) {
if (referenced is PsiField &&
referenced.containingClass?.name == "BuildConfig"
) {
// TODO: Check surrounding code in AST to see if it looks like it's being used
// for something other than display purposes
context.report(
ISSUE,
reference,
context.getNameLocation(reference),
"Don't reference `BuildConfig.VERSION_NAME`; use `BuildConfig.VERSION_CODE` instead"
)
}
}
}
/*
* Copyright (C) 2018 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
import com.android.tools.lint.detector.api.Detector
class VersionNameDetectorTest : AbstractCheckTest() {
override fun getDetector(): Detector {
return VersionNameDetector()
}
fun testConnectivityChange() {
lint().files(
java(
"""
package com.example.lint;
import com.android.ddmlib.Log;
public class TestClass {
public static void main(String[] args) {
String name = BuildConfig.VERSION_NAME;
Log.d("TAG", name);
}
}"""
).indented(),
java(
"""
package com.example.lint;
public final class BuildConfig {
public static final String VERSION_NAME = "1.0";
}
"""
).indented()
).run().expect(
"""
src/com/example/lint/TestClass.java:8: Error: Don't reference BuildConfig.VERSION_NAME; use BuildConfig.VERSION_CODE instead [VersionNameReferenced]
String name = BuildConfig.VERSION_NAME;
~~~~~~~~~~~~
1 errors, 0 warnings
"""
)
}
}
--
You received this message because you are subscribed to the Google Groups "lint-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lint-dev+u...@googlegroups.com.
To post to this group, send email to lint...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/lint-dev/80ca29be-216f-44b9-8a80-1f10104686ce%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.