refactor: Prefer System.currentTimeMillis() to Date().getTime() (#645)

Avoids unnecessary object creation.

Add a lint rule to catch this.
This commit is contained in:
Nik Clayton 2024-04-24 12:07:05 +02:00 committed by GitHub
parent 3e1d94ded2
commit 12eccb63ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 145 additions and 8 deletions

View File

@ -34,7 +34,6 @@ import app.pachli.util.StatusDisplayOptions
import app.pachli.util.getRelativeTimeSpanString
import app.pachli.viewdata.NotificationViewData
import at.connyduck.sparkbutton.helpers.Utils
import java.util.Date
class ReportNotificationViewHolder(
private val binding: ItemReportNotificationBinding,
@ -90,7 +89,7 @@ class ReportNotificationViewHolder(
)
binding.notificationSummary.text = itemView.context.getString(
R.string.notification_summary_report_format,
getRelativeTimeSpanString(itemView.context, report.createdAt.time, Date().time),
getRelativeTimeSpanString(itemView.context, report.createdAt.time, System.currentTimeMillis()),
report.statusIds?.size ?: 0,
)
binding.notificationCategory.text = getTranslatedCategory(itemView.context, report.category)

View File

@ -42,7 +42,6 @@ import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.Date
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope
@ -271,7 +270,7 @@ class MediaUploader @Inject constructor(
val fileExtension = map.getExtensionFromMimeType(mimeType)
val filename = "%s_%s_%s.%s".format(
context.getString(R.string.app_name),
Date().time.toString(),
System.currentTimeMillis().toString(),
randomAlphanumericString(10),
fileExtension,
)

View File

@ -26,7 +26,6 @@ import app.pachli.databinding.ItemSeveredRelationshipsBinding
import app.pachli.util.StatusDisplayOptions
import app.pachli.util.getRelativeTimeSpanString
import app.pachli.viewdata.NotificationViewData
import java.util.Date
class SeveredRelationshipsViewHolder(
private val binding: ItemSeveredRelationshipsBinding,
@ -42,7 +41,7 @@ class SeveredRelationshipsViewHolder(
HtmlCompat.FROM_HTML_MODE_LEGACY,
)
binding.datetime.text = getRelativeTimeSpanString(itemView.context, event.createdAt.time, Date().time)
binding.datetime.text = getRelativeTimeSpanString(itemView.context, event.createdAt.time, System.currentTimeMillis())
binding.notificationSummary.text = itemView.context.resources.getQuantityString(
R.plurals.notification_severed_relationships_summary_report_fmt,
@ -59,7 +58,7 @@ class SeveredRelationshipsViewHolder(
binding.notificationCategory.text = itemView.context.getString(resourceId)
} else {
if (payloads.any { it == StatusBaseViewHolder.Key.KEY_CREATED }) {
binding.datetime.text = getRelativeTimeSpanString(itemView.context, event.createdAt.time, Date().time)
binding.datetime.text = getRelativeTimeSpanString(itemView.context, event.createdAt.time, System.currentTimeMillis())
}
}
}

View File

@ -173,7 +173,7 @@ internal class StatusNotificationViewHolder(
val readoutAloud: CharSequence
if (createdAt != null) {
val then = createdAt.time
val now = Date().time
val now = System.currentTimeMillis()
readout = getRelativeTimeSpanString(binding.statusMetaInfo.context, then, now)
readoutAloud = DateUtils.getRelativeTimeSpanString(
then,

View File

@ -0,0 +1,89 @@
/*
* Copyright 2024 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.lint.checks
import com.android.tools.lint.checks.DataFlowAnalyzer
import com.android.tools.lint.detector.api.Category
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.LintFix
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.PsiMethod
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.getParentOfType
class DateDotTimeDetector : Detector(), SourceCodeScanner {
override fun getApplicableConstructorTypes(): List<String> = listOf(CLASS_DATE)
val fix = LintFix.create()
.name("Replace with `System.currentTimeMillis()`")
.replace()
.text("Date().time")
.with("System.currentTimeMillis()")
.independent(true)
.build()
// Look for a Date() constructor call followed by a getTime() method call
override fun visitConstructor(context: JavaContext, node: UCallExpression, constructor: PsiMethod) {
val method = node.getParentOfType(UMethod::class.java) ?: return
val analyzer = object : DataFlowAnalyzer(listOf(node)) {
var reported = false
override fun receiver(call: UCallExpression) {
if (reported) return
if (call.methodName != METHOD_GET_TIME) return
reported = true
context.report(
issue = ISSUE,
scope = node,
location = context.getCallLocation(call, includeReceiver = true, includeArguments = true),
message = "Use `System.currentTimeMillis()`",
quickfixData = fix,
)
}
}
method.accept(analyzer)
}
companion object {
private const val CLASS_DATE = "java.util.Date"
private const val METHOD_GET_TIME = "getTime"
val ISSUE = Issue.create(
id = "DateDotTimeDetector",
briefDescription = "Don't use `Date().time`, use `System.currentTimeMillis()`",
explanation = """
Calling `Date().time` / `Date().getTime()` is unnecessary object creation. Use
`System.currentTimeMillis()` to avoid this.
""",
category = Category.CORRECTNESS,
priority = 6,
severity = Severity.WARNING,
implementation = Implementation(
DateDotTimeDetector::class.java,
Scope.JAVA_FILE_SCOPE,
),
)
}
}

View File

@ -10,6 +10,7 @@ class LintRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(
AndroidxToolbarDetector.ISSUE,
DateDotTimeDetector.ISSUE,
IntentDetector.ISSUE,
)

View File

@ -0,0 +1,50 @@
/*
* Copyright 2024 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.lint.checks
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
@Suppress("ktlint:standard:function-naming")
class DateDotTimeDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = DateDotTimeDetector()
override fun getIssues(): List<Issue> = listOf(DateDotTimeDetector.ISSUE)
fun `test Intent component constructor emits warning`() {
lint().files(
kotlin(
"""
package test.pkg
import java.util.Date
fun getTimeMills() = Date().time
""",
).indented(),
).allowMissingSdk().testModes(TestMode.DEFAULT).run().expect(
"""src/test/pkg/test.kt:5: Warning: Use System.currentTimeMillis() [DateDotTimeDetector]
fun getTimeMills() = Date().time
~~~~~~~~~~~
0 errors, 1 warnings""",
)
}
}