summaryrefslogtreecommitdiffstats
path: root/mobile/android/fenix/mozilla-detekt-rules
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/fenix/mozilla-detekt-rules')
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/.gitignore1
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/build.gradle16
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetConsoleReport.kt21
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetProvider.kt28
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/MozillaCorrectUnitTestRunner.kt76
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaBannedPropertyAccess.kt52
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaRunBlockingCheck.kt33
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaStrictModeSuppression.kt70
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitored.kt85
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.ConsoleReport1
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider1
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/main/resources/config.yml3
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaBannedPropertyAccessTest.kt94
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaStrictModeSuppressionTest.kt70
-rw-r--r--mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitoredTest.kt106
15 files changed, 657 insertions, 0 deletions
diff --git a/mobile/android/fenix/mozilla-detekt-rules/.gitignore b/mobile/android/fenix/mozilla-detekt-rules/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/mobile/android/fenix/mozilla-detekt-rules/build.gradle b/mobile/android/fenix/mozilla-detekt-rules/build.gradle
new file mode 100644
index 0000000000..8588c0504f
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'kotlin'
+
+dependencies {
+ compileOnly ComponentsDependencies.tools_detekt_api
+ implementation ComponentsDependencies.androidx_annotation
+
+ testImplementation ComponentsDependencies.tools_detekt_api
+ testImplementation ComponentsDependencies.tools_detekt_test
+ testImplementation FenixDependencies.junitApi
+ testImplementation FenixDependencies.junitParams
+ testRuntimeOnly FenixDependencies.junitEngine
+}
+
+test {
+ useJUnitPlatform {}
+}
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetConsoleReport.kt b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetConsoleReport.kt
new file mode 100644
index 0000000000..f7e8280617
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetConsoleReport.kt
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules
+
+import io.gitlab.arturbosch.detekt.api.ConsoleReport
+import io.gitlab.arturbosch.detekt.api.Detektion
+
+/**
+ * A reporter that custom formats violations of our custom lint rules.
+ */
+class CustomRulesetConsoleReport : ConsoleReport() {
+ @Suppress("DEPRECATION") // locationAsString
+ override fun render(detektion: Detektion): String? {
+ return detektion.findings["mozilla-detekt-rules"]?.fold("") { accumulator, finding ->
+ accumulator + "${finding.id}:\n ${finding.file}\n ${finding.messageOrDescription()}\n\n"
+ // This creates an extra newline at the very end but it's not worth fixing.
+ }
+ }
+}
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetProvider.kt b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetProvider.kt
new file mode 100644
index 0000000000..c2955fb9c0
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetProvider.kt
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules
+
+import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.api.RuleSet
+import io.gitlab.arturbosch.detekt.api.RuleSetProvider
+import org.mozilla.fenix.detektrules.perf.MozillaBannedPropertyAccess
+import org.mozilla.fenix.detektrules.perf.MozillaRunBlockingCheck
+import org.mozilla.fenix.detektrules.perf.MozillaStrictModeSuppression
+import org.mozilla.fenix.detektrules.perf.MozillaUseLazyMonitored
+
+class CustomRulesetProvider : RuleSetProvider {
+ override val ruleSetId: String = "mozilla-detekt-rules"
+
+ override fun instance(config: Config): RuleSet = RuleSet(
+ ruleSetId,
+ listOf(
+ MozillaBannedPropertyAccess(config),
+ MozillaStrictModeSuppression(config),
+ MozillaCorrectUnitTestRunner(config),
+ MozillaRunBlockingCheck(config),
+ MozillaUseLazyMonitored(config),
+ ),
+ )
+}
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/MozillaCorrectUnitTestRunner.kt b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/MozillaCorrectUnitTestRunner.kt
new file mode 100644
index 0000000000..89735ae877
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/MozillaCorrectUnitTestRunner.kt
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules
+
+import io.gitlab.arturbosch.detekt.api.CodeSmell
+import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.api.Debt
+import io.gitlab.arturbosch.detekt.api.Entity
+import io.gitlab.arturbosch.detekt.api.Issue
+import io.gitlab.arturbosch.detekt.api.Rule
+import io.gitlab.arturbosch.detekt.api.Severity
+import io.gitlab.arturbosch.detekt.api.internal.PathFilters
+import org.jetbrains.kotlin.psi.KtAnnotationEntry
+
+private val BANNED_TEST_RUNNERS = setOf(
+ // When updating this list, also update the violation message.
+ "AndroidJUnit4",
+ "RobolectricTestRunner",
+)
+
+// There is a change to how we output violations in a different PR so the formatting for message
+// might be weird but it should still be readable.
+private val VIOLATION_MSG = """The following code blocks:
+ |
+ | @RunWith(AndroidJUnit4::class)
+ | @Config(application = TestApplication::class)
+ | OR
+ | @RunWith(RobolectricTestRunner::class)
+ | @Config(application = TestApplication::class)
+ |
+ | should be replaced with:
+ |
+ | @RunWith(FenixRobolectricTestRunner::class)
+ |
+ | To reduce redundancy of setting @Config. No @Config specification is necessary because
+ | the FenixRobolectricTestRunner sets it automatically.
+ |
+ | Relatedly, adding robolectric to a test increases its runtime non-trivially so please
+ | ensure Robolectric is necessary before adding it.""".trimMargin()
+
+/**
+ * Ensures we're using the correct Robolectric unit test runner.
+ */
+class MozillaCorrectUnitTestRunner(config: Config) : Rule(config) {
+ override val issue = Issue(
+ MozillaCorrectUnitTestRunner::class.simpleName!!,
+ Severity.Defect,
+ "Verifies we're using the correct Robolectric unit test runner",
+ Debt.FIVE_MINS,
+ )
+
+ override val filters: PathFilters?
+ get() = PathFilters.of(
+ includes = listOf("**/test/**"), // unit tests only.
+ excludes = emptyList(),
+ )
+
+ override fun visitAnnotationEntry(annotationEntry: KtAnnotationEntry) {
+ super.visitAnnotationEntry(annotationEntry)
+
+ // Example of an annotation: @RunWith(FenixRobolectricUnitTestRunner::class)
+ val annotationClassName = annotationEntry.shortName?.identifier
+ if (annotationClassName == "RunWith") {
+ // Example arg in parens: "(FenixRobolectricUnitTestRunner::class)"
+ val argInParens = annotationEntry.lastChild.node.chars
+ val argClassName = argInParens.substring(1 until argInParens.indexOf(":"))
+
+ val isInViolation = BANNED_TEST_RUNNERS.any { it == argClassName }
+ if (isInViolation) {
+ report(CodeSmell(issue, Entity.from(annotationEntry), VIOLATION_MSG))
+ }
+ }
+ }
+}
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaBannedPropertyAccess.kt b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaBannedPropertyAccess.kt
new file mode 100644
index 0000000000..dcd467cf44
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaBannedPropertyAccess.kt
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules.perf
+
+import io.gitlab.arturbosch.detekt.api.CodeSmell
+import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.api.Debt
+import io.gitlab.arturbosch.detekt.api.Entity
+import io.gitlab.arturbosch.detekt.api.Issue
+import io.gitlab.arturbosch.detekt.api.Rule
+import io.gitlab.arturbosch.detekt.api.Severity
+import org.jetbrains.kotlin.psi.*
+
+class MozillaBannedPropertyAccess(config: Config = Config.empty) : Rule(config) {
+ override val issue = Issue(
+ "MozillaBannedPropertyAccess",
+ Severity.Defect,
+ DESCR,
+ Debt.FIVE_MINS,
+ )
+
+ private val banned by lazy {
+ val bannedPropertiesList = valueOrDefault("bannedProperties", "")
+ if (bannedPropertiesList.contentEquals("")) {
+ listOf()
+ } else {
+ bannedPropertiesList.split(",")
+ }
+ }
+
+ override fun visitDotQualifiedExpression(expression: KtDotQualifiedExpression) {
+ super.visitDotQualifiedExpression(expression)
+ val possiblyBannedPropertyAccess = expression.node.chars
+
+ // Does the current property access, a.b.c.d.possibly.Banned, end with
+ // it. Use endsWith() so that package qualification does not interfere
+ // with the check. In other words, if the configuration tells this rule
+ // to ban d.definitely.Banned, flag a use of the property via
+ // x.y.z.d.definitely.Banned.
+ banned.filter { possiblyBannedPropertyAccess.endsWith(it) }.map {
+ CodeSmell(
+ issue,
+ Entity.from(expression),
+ "Using $possiblyBannedPropertyAccess is not allowed because accessing property $it is against Mozilla policy. See 'mozilla-detekt-rules' stanza in 'config/detekt.yml' for more information.\n",
+ )
+ }.forEach { report(it) }
+ }
+}
+
+internal const val DESCR = "Using banned property"
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaRunBlockingCheck.kt b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaRunBlockingCheck.kt
new file mode 100644
index 0000000000..9064814bda
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaRunBlockingCheck.kt
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules.perf
+
+import io.gitlab.arturbosch.detekt.api.*
+import org.jetbrains.kotlin.psi.*
+
+private const val VIOLATION_MSG = "Please use `org.mozilla.fenix.perf.runBlockingIncrement` instead " +
+ "because it allows us to monitor the code for performance regressions."
+
+/**
+ * A check to prevent us from working around mechanisms we implemented in
+ * @see org.mozilla.fenix.perf.RunBlockingCounter.runBlockingIncrement to count how many runBlocking
+ * are used.
+ */
+class MozillaRunBlockingCheck(config: Config) : Rule(config) {
+
+ override val issue = Issue(
+ "MozillaRunBlockingCheck",
+ Severity.Performance,
+ "Prevents us from working around mechanisms we implemented to count how many " +
+ "runBlocking are used",
+ Debt.TWENTY_MINS,
+ )
+
+ override fun visitImportDirective(importDirective: KtImportDirective) {
+ if (importDirective.importPath?.toString() == "kotlinx.coroutines.runBlocking") {
+ report(CodeSmell(issue, Entity.from(importDirective), VIOLATION_MSG))
+ }
+ }
+}
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaStrictModeSuppression.kt b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaStrictModeSuppression.kt
new file mode 100644
index 0000000000..cc07bcde7e
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaStrictModeSuppression.kt
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules.perf
+
+import io.gitlab.arturbosch.detekt.api.CodeSmell
+import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.api.Debt
+import io.gitlab.arturbosch.detekt.api.Entity
+import io.gitlab.arturbosch.detekt.api.Issue
+import io.gitlab.arturbosch.detekt.api.Rule
+import io.gitlab.arturbosch.detekt.api.Severity
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtImportDirective
+
+private const val VIOLATION_MSG = "Please use `components.strictMode.resetAfter` instead because it has " +
+ "performance improvements and additional code to monitor for performance regressions."
+
+/**
+ * A check to prevent us from working around mechanisms we implemented to prevent suppressing StrictMode.
+ */
+class MozillaStrictModeSuppression(config: Config) : Rule(config) {
+ override val issue = Issue(
+ "MozillaStrictModeSuppression",
+ Severity.Performance,
+ "Prevents us from working around mechanisms we implemented to prevent suppressing StrictMode",
+ Debt.TEN_MINS,
+ )
+
+ override fun visitImportDirective(importDirective: KtImportDirective) {
+ super.visitImportDirective(importDirective)
+ reportIfImportAcResetAfter(importDirective)
+ }
+
+ override fun visitCallExpression(expression: KtCallExpression) {
+ super.visitCallExpression(expression)
+ reportIfCallStrictModeSetPolicy(expression)
+ }
+
+ private fun reportIfImportAcResetAfter(importDirective: KtImportDirective) {
+ if (importDirective.importPath?.toString() == "mozilla.components.support.ktx.android.os.resetAfter") {
+ report(CodeSmell(issue, Entity.from(importDirective), VIOLATION_MSG))
+ }
+ }
+
+ private fun reportIfCallStrictModeSetPolicy(expression: KtCallExpression) {
+ // There is probably a more correct way of doing this but this API is not well documented
+ // so it's not worth our time, I think.
+ val receiver = expression.parent?.firstChild?.node?.chars
+ val calledMethod = expression.calleeExpression?.firstChild?.node?.chars
+
+ // This won't catch if setVmPolicy is imported directly. However, this is unlikely so let's
+ // not handle it now. Maybe we can add it when we add tests for this file.
+ if (receiver == "StrictMode") {
+ val violationMsg = when (calledMethod) {
+ "setThreadPolicy" -> VIOLATION_MSG
+ "setVmPolicy" ->
+ "NOT YET IMPLEMENTED: please consult the perf team about implementing" +
+ "`StrictModeManager.resetAfter`: we want to understand the performance implications " +
+ "of suppressing setVmPolicy before allowing it."
+ else -> null
+ }
+
+ violationMsg?.let { msg ->
+ report(CodeSmell(issue, Entity.from(expression), msg))
+ }
+ }
+ }
+}
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitored.kt b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitored.kt
new file mode 100644
index 0000000000..9239ac2d27
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitored.kt
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules.perf
+
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.Companion.PRIVATE
+import io.gitlab.arturbosch.detekt.api.CodeSmell
+import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.api.Debt
+import io.gitlab.arturbosch.detekt.api.Entity
+import io.gitlab.arturbosch.detekt.api.Issue
+import io.gitlab.arturbosch.detekt.api.Rule
+import io.gitlab.arturbosch.detekt.api.Severity
+import io.gitlab.arturbosch.detekt.api.internal.PathFilters
+import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtPropertyDelegate
+
+private const val FUN_LAZY = "lazy"
+
+private const val ERROR_MESSAGE = "Please use `by lazyMonitored` instead of `by lazy` " +
+ "to declare application components (e.g. variables in Components.kt): it contains extra code to " +
+ "monitor for performance regressions. This change is not necessary for non-components."
+
+/**
+ * Prevents use of lazy in component groups files where lazyMonitored should be used instead.
+ */
+open class MozillaUseLazyMonitored(config: Config) : Rule(config) {
+ override val issue = Issue(
+ this::class.java.simpleName,
+ Severity.Performance,
+ "Prevents use of lazy in component groups files where lazyMonitored should be used instead",
+ Debt.FIVE_MINS,
+ )
+
+ override val filters = PathFilters.of(
+ includes = COMPONENT_GROUP_FILES.map { "**$it" },
+ excludes = emptyList(),
+ )
+
+ override fun visitPropertyDelegate(delegate: KtPropertyDelegate) {
+ super.visitPropertyDelegate(delegate)
+
+ val delegateFunction = delegate.expression?.node?.firstChildNode // the "lazy" in "by lazy { ..."
+ if (delegateFunction?.chars == FUN_LAZY) {
+ report(CodeSmell(issue, Entity.from(delegate), ERROR_MESSAGE))
+ }
+ }
+
+ override fun visitProperty(property: KtProperty) {
+ super.visitProperty(property)
+
+ // I only expect components to be declared as members, not at the top level or inside block scopes.
+ if (!property.isMember) return
+
+ val rightHandSideOfAssignment = property.initializer
+
+ // This is intended to catch `val example = lazy {` or `... lazy(`. It may be possible to
+ // make lazy not the first node to the right of the equal sign but it's probably uncommon
+ // enough that we don't handle that case.
+ val assignedFunction = rightHandSideOfAssignment?.firstChild
+ if (assignedFunction?.node?.chars == FUN_LAZY) {
+ report(CodeSmell(issue, Entity.from(property), ERROR_MESSAGE))
+ }
+ }
+
+ companion object {
+ /**
+ * All files that represent a "component group".
+ */
+ @VisibleForTesting(otherwise = PRIVATE)
+ val COMPONENT_GROUP_FILES = listOf(
+ "Analytics",
+ "BackgroundServices",
+ "Components",
+ "Core",
+ "IntentProcessors",
+ "PerformanceComponent",
+ "Push",
+ "Services",
+ "UseCases",
+ ).map { "app/src/main/java/org/mozilla/fenix/components/$it.kt" }
+ }
+}
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.ConsoleReport b/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.ConsoleReport
new file mode 100644
index 0000000000..fb899601e4
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.ConsoleReport
@@ -0,0 +1 @@
+org.mozilla.fenix.detektrules.CustomRulesetConsoleReport
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider b/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider
new file mode 100644
index 0000000000..81e4111972
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider
@@ -0,0 +1 @@
+org.mozilla.fenix.detektrules.CustomRulesetProvider
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/config.yml b/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/config.yml
new file mode 100644
index 0000000000..ce0237ab02
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/main/resources/config.yml
@@ -0,0 +1,3 @@
+ MozillaBannedPropertyAccess:
+ active: true
+ bannedProperties: "Sample.Banned"
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaBannedPropertyAccessTest.kt b/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaBannedPropertyAccessTest.kt
new file mode 100644
index 0000000000..fde95cb599
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaBannedPropertyAccessTest.kt
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@file:Suppress("Deprecation")
+
+package org.mozilla.fenix.detektrules.perf
+
+import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.test.lint
+import io.gitlab.arturbosch.detekt.test.yamlConfig
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.Arguments.arguments
+import org.junit.jupiter.params.provider.MethodSource
+import java.util.stream.Stream
+
+internal class MozillaBannedPropertyAccessTest {
+
+ private lateinit var config: Config
+
+ @BeforeEach
+ fun setup() {
+ config = yamlConfig("/config.yml")
+ }
+
+ @Test
+ internal fun `non compliant property access should warn`() {
+ val findings =
+ MozillaBannedPropertyAccess(config).lint(
+ NONCOMPLIANT_ACCESS.trimIndent(),
+ )
+ assertEquals(1, findings.size)
+ assertEquals(DESCR, findings[0].issue.description)
+ }
+
+ @DisplayName("compliant ")
+ @MethodSource("compliantProvider")
+ @ParameterizedTest(name = "{1} should not warn")
+ internal fun testCompliantWhen(source: String) {
+ val findings =
+ MozillaBannedPropertyAccess(config).lint(
+ source,
+ )
+ assertTrue(findings.isEmpty())
+ }
+
+ companion object {
+ @JvmStatic
+ fun compliantProvider(): Stream<Arguments> =
+ Stream.of(
+ arguments(COMPLIANT_ACCESS, "Safe property access"),
+ )
+ }
+}
+
+const val NONCOMPLIANT_ACCESS = """
+ class Test {
+ lateinit var x: String
+ class Sample {
+ companion object {
+ public var Banned = "true".toBoolean()
+ }
+ }
+ init {
+ if (Sample.Banned) {
+ x = "true"
+ }
+ }
+ }
+ """
+
+const val COMPLIANT_ACCESS = """
+ class Test {
+ lateinit var x: String
+ class Sample {
+ companion object {
+ public var Banned = "true".toBoolean()
+ public var Allowed = "false".toBoolean()
+
+ }
+ }
+ init {
+ if (Sample.Allowed) {
+ x = "true"
+ }
+ }
+ }
+ """
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaStrictModeSuppressionTest.kt b/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaStrictModeSuppressionTest.kt
new file mode 100644
index 0000000000..d78f8a7361
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaStrictModeSuppressionTest.kt
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules.perf
+
+import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.test.lint
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+internal class MozillaStrictModeSuppressionTest {
+
+ private lateinit var check: MozillaStrictModeSuppression
+
+ @BeforeEach
+ fun setUp() {
+ check = MozillaStrictModeSuppression(Config.empty)
+ }
+
+ @Test
+ fun `GIVEN a file with import not resetAfter WHEN linting THEN pass`() {
+ val file = """import android.os.StrictMode"""
+ assertNoFinding(file)
+ }
+
+ @Test
+ fun `GIVEN a file with import ac resetAfter WHEN linting THEN fail`() {
+ val file = """import mozilla.components.support.ktx.android.os.resetAfter"""
+ assertOneFinding(file)
+ }
+
+ @Test
+ fun `GIVEN a file with StrictMode getVmPolicy WHEN linting THEN pass`() {
+ val file = """fun main() {
+ | val policy = StrictMode.getVmPolicy()
+ | }""".trimMargin()
+ assertNoFinding(file)
+ }
+
+ @Test
+ fun `GIVEN a file with StrictMode setVmPolicy WHEN linting THEN fail`() {
+ val file = """fun main() {
+ | val policy = StrictMode.VmPolicy.Builder.build()
+ | StrictMode.setVmPolicy(policy)
+ | }""".trimMargin()
+ assertOneFinding(file)
+ }
+
+ @Test
+ fun `GIVEN a file with StrictMode setThreadPolicy WHEN linting THEN fail`() {
+ val file = """fun main() {
+ | val policy = StrictMode.ThreadPolicy.Builder.build()
+ | StrictMode.setThreadPolicy(policy)
+ | }""".trimMargin()
+ assertOneFinding(file)
+ }
+
+ private fun assertNoFinding(fileStr: String) {
+ val findings = check.lint(fileStr)
+ assertEquals(0, findings.size)
+ }
+
+ private fun assertOneFinding(fileStr: String) {
+ val findings = check.lint(fileStr)
+ assertEquals(1, findings.size)
+ assertEquals(check.issue, findings.first().issue)
+ }
+}
diff --git a/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitoredTest.kt b/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitoredTest.kt
new file mode 100644
index 0000000000..2ed6fb68a9
--- /dev/null
+++ b/mobile/android/fenix/mozilla-detekt-rules/src/test/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitoredTest.kt
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.detektrules.perf
+
+import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.api.internal.PathFilters
+import io.gitlab.arturbosch.detekt.test.lint
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+import java.io.File
+import java.nio.file.Paths
+
+internal class MozillaUseLazyMonitoredTest {
+
+ private lateinit var actualCheck: MozillaUseLazyMonitored
+ private lateinit var check: TestMozillaUseLazyMonitored
+
+ @BeforeEach
+ fun setUp() {
+ actualCheck = MozillaUseLazyMonitored(Config.empty)
+ check = TestMozillaUseLazyMonitored()
+ }
+
+ @Test
+ fun `GIVEN the hard-coded component group file paths THEN there are some to run the lint on`() {
+ assertFalse(MozillaUseLazyMonitored.COMPONENT_GROUP_FILES.isEmpty())
+ }
+
+ @Test
+ fun `GIVEN the hard-coded component group file paths THEN they all exist`() {
+ MozillaUseLazyMonitored.COMPONENT_GROUP_FILES.forEach { path ->
+ // The tests working directory is `<repo>/mozilla-detekt-rules` so we need to back out.
+ val modifiedPath = "../$path"
+
+ val errorMessage = "This lint rule hard-codes paths to files containing \"component groups\". " +
+ "One of these files - $path - no longer exists. Please update the check " +
+ "${MozillaUseLazyMonitored::class.java.simpleName} with the new path."
+ assertTrue(File(modifiedPath).exists(), errorMessage)
+ }
+ }
+
+ @Test
+ fun `GIVEN the hard-coded component group file paths THEN none of the files are ignored`() {
+ MozillaUseLazyMonitored.COMPONENT_GROUP_FILES.forEach {
+ assertTrue(actualCheck.filters?.isIgnored(Paths.get(it)) == false)
+ }
+ }
+
+ @Test
+ fun `GIVEN a snippet with lazyMonitored THEN there are no findings`() {
+ val text = getCodeInClass("val example by lazyMonitored { 4 }")
+ assertNoFindings(text)
+ }
+
+ @ParameterizedTest
+ @ValueSource(
+ strings = [
+ "val example by lazy { 4 }",
+ "val example by lazy { 4 }",
+ "val example\nby\nlazy\n{\n4\n}",
+ ],
+ )
+ fun `GIVEN a snippet with by lazy THEN there is a finding`(innerText: String) {
+ val text = getCodeInClass(innerText)
+ assertOneFinding(text)
+ }
+
+ @ParameterizedTest
+ @ValueSource(
+ strings = [
+ "val example = lazy(::exampleFun)",
+ "val example = lazy ( ::exampleFun)",
+ "val example\n=\nlazy\n(\n::exampleFun\n)",
+ ],
+ )
+ fun `GIVEN a snippet with = lazy THEN there is a finding`(innerText: String) {
+ val text = getCodeInClass(innerText)
+ assertOneFinding(text)
+ }
+
+ private fun assertNoFindings(text: String) {
+ val findings = check.lint(text)
+ assertEquals(0, findings.size)
+ }
+
+ private fun assertOneFinding(text: String) {
+ val findings = check.lint(text)
+ assertEquals(1, findings.size)
+ assertEquals(check.issue, findings[0].issue)
+ }
+}
+
+private fun getCodeInClass(text: String): String = """class Example() {
+ $text
+}"""
+
+class TestMozillaUseLazyMonitored : MozillaUseLazyMonitored(Config.empty) {
+ // If left on their own, the path filters will prevent us from running checks on raw strings.
+ // I'm not sure the best way to work around that so we just override the value.
+ override val filters: PathFilters? = null
+}