Removes ModifierInspectorInfoDetector
Modifier.Node should be used for new modifier feature development, and inspector info for that is covered by a separate lint check. ModifierInspectorInfoDetector is not aware of Modifier.Node, and so provides false positives for any migrated / new modifiers using those APIs, so we can just remove it.
Test: lint
Fixes: b/251831790
Change-Id: Ia32c1f4d53ed75fa3739f96fdd13143abc6f547a
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
index 742fcbe..ba5bb31 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
@@ -943,7 +943,6 @@
private val DefaultAlpha = mutableStateOf(1f)
private val DefaultAlphaAndScaleSpring = spring<Float>(stiffness = Spring.StiffnessMediumLow)
-@Suppress("ModifierInspectorInfo")
private fun Modifier.slideInOut(
transition: Transition<EnterExitState>,
slideIn: State<Slide?>,
@@ -1025,7 +1024,6 @@
}
}
-@Suppress("ModifierInspectorInfo")
private fun Modifier.shrinkExpand(
transition: Transition<EnterExitState>,
expand: State<ChangeSize?>,
diff --git a/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.android.kt b/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.android.kt
index 62833e4..9a9e20d 100644
--- a/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.android.kt
+++ b/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.android.kt
@@ -241,7 +241,7 @@
mandatorySystemGestures
}
-@Suppress("NOTHING_TO_INLINE", "ModifierInspectorInfo")
+@Suppress("NOTHING_TO_INLINE")
@Stable
private inline fun Modifier.windowInsetsPadding(
noinline inspectorInfo: InspectorInfo.() -> Unit,
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
index 71c9444..a088418 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
@@ -144,7 +144,6 @@
* @sample androidx.compose.foundation.layout.samples.PaddingFromBaselineSampleDp
*/
@Stable
-@Suppress("ModifierInspectorInfo")
fun Modifier.paddingFromBaseline(top: Dp = Dp.Unspecified, bottom: Dp = Dp.Unspecified) = this
.then(
if (top != Dp.Unspecified) {
@@ -178,7 +177,6 @@
* @sample androidx.compose.foundation.layout.samples.PaddingFromBaselineSampleTextUnit
*/
@Stable
-@Suppress("ModifierInspectorInfo")
fun Modifier.paddingFromBaseline(
top: TextUnit = TextUnit.Unspecified,
bottom: TextUnit = TextUnit.Unspecified
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
index c56f33c..3fd65398 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
@@ -456,7 +456,6 @@
* @sample androidx.compose.foundation.layout.samples.FillHalfWidthModifier
*/
@Stable
-@Suppress("ModifierInspectorInfo")
fun Modifier.fillMaxWidth(/*@FloatRange(from = 0.0, to = 1.0)*/ fraction: Float = 1f) =
this.then(if (fraction == 1f) FillWholeMaxWidth else FillElement.width(fraction))
@@ -477,7 +476,6 @@
* @sample androidx.compose.foundation.layout.samples.FillHalfHeightModifier
*/
@Stable
-@Suppress("ModifierInspectorInfo")
fun Modifier.fillMaxHeight(/*@FloatRange(from = 0.0, to = 1.0)*/ fraction: Float = 1f) =
this.then(if (fraction == 1f) FillWholeMaxHeight else FillElement.height(fraction))
@@ -502,7 +500,6 @@
* @sample androidx.compose.foundation.layout.samples.FillHalfSizeModifier
*/
@Stable
-@Suppress("ModifierInspectorInfo")
fun Modifier.fillMaxSize(/*@FloatRange(from = 0.0, to = 1.0)*/ fraction: Float = 1f) =
this.then(if (fraction == 1f) FillWholeMaxSize else FillElement.size(fraction))
@@ -521,7 +518,6 @@
* @sample androidx.compose.foundation.layout.samples.SimpleWrapContentHorizontallyAlignedModifier
*/
@Stable
-@Suppress("ModifierInspectorInfo")
fun Modifier.wrapContentWidth(
align: Alignment.Horizontal = Alignment.CenterHorizontally,
unbounded: Boolean = false
@@ -551,7 +547,6 @@
* @sample androidx.compose.foundation.layout.samples.SimpleWrapContentVerticallyAlignedModifier
*/
@Stable
-@Suppress("ModifierInspectorInfo")
fun Modifier.wrapContentHeight(
align: Alignment.Vertical = Alignment.CenterVertically,
unbounded: Boolean = false
@@ -581,7 +576,6 @@
* @sample androidx.compose.foundation.layout.samples.SimpleWrapContentAlignedModifier
*/
@Stable
-@Suppress("ModifierInspectorInfo")
fun Modifier.wrapContentSize(
align: Alignment = Alignment.Center,
unbounded: Boolean = false
@@ -608,7 +602,6 @@
* Example usage:
* @sample androidx.compose.foundation.layout.samples.DefaultMinSizeSample
*/
-@Suppress("ModifierInspectorInfo")
@Stable
fun Modifier.defaultMinSize(
minWidth: Dp = Dp.Unspecified,
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsSize.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsSize.kt
index abf9a58..76af273 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsSize.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsSize.kt
@@ -45,7 +45,6 @@
*
* @sample androidx.compose.foundation.layout.samples.insetsStartWidthSample
*/
-@Suppress("ModifierInspectorInfo")
@Stable
fun Modifier.windowInsetsStartWidth(insets: WindowInsets) = this.then(
DerivedWidthModifier(insets, debugInspectorInfo {
@@ -71,7 +70,6 @@
*
* @sample androidx.compose.foundation.layout.samples.insetsEndWidthSample
*/
-@Suppress("ModifierInspectorInfo")
@Stable
fun Modifier.windowInsetsEndWidth(insets: WindowInsets) = this.then(
DerivedWidthModifier(insets, debugInspectorInfo {
@@ -95,7 +93,6 @@
*
* @sample androidx.compose.foundation.layout.samples.insetsTopHeightSample
*/
-@Suppress("ModifierInspectorInfo")
@Stable
fun Modifier.windowInsetsTopHeight(insets: WindowInsets) = this.then(
DerivedHeightModifier(insets, debugInspectorInfo {
@@ -115,7 +112,6 @@
*
* @sample androidx.compose.foundation.layout.samples.insetsBottomHeightSample
*/
-@Suppress("ModifierInspectorInfo")
@Stable
fun Modifier.windowInsetsBottomHeight(insets: WindowInsets) = this.then(
DerivedHeightModifier(insets, debugInspectorInfo {
diff --git a/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/AndroidSelectionHandles.android.kt b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/AndroidSelectionHandles.android.kt
index ab50488..50a2e51 100644
--- a/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/AndroidSelectionHandles.android.kt
+++ b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/AndroidSelectionHandles.android.kt
@@ -102,7 +102,6 @@
)
}
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.drawSelectionHandle(
isStartHandle: Boolean,
direction: ResolvedTextDirection,
diff --git a/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.android.kt b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.android.kt
index 7a7e170..fa0d056 100644
--- a/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.android.kt
+++ b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.android.kt
@@ -16,7 +16,6 @@
package androidx.compose.foundation.newtext.text.copypasta.selection
-import android.annotation.SuppressLint
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.MagnifierStyle
import androidx.compose.foundation.magnifier
@@ -36,7 +35,6 @@
// We use composed{} to read a local, but don't provide inspector info because the underlying
// magnifier modifier provides more meaningful inspector info.
-@SuppressLint("ModifierInspectorInfo")
@OptIn(ExperimentalFoundationApi::class)
internal actual fun Modifier.selectionMagnifier(manager: SelectionManager): Modifier {
// Avoid tracking animation state on older Android versions that don't support magnifiers.
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMagnifier.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMagnifier.kt
index 2817f3e..1f7a00c 100644
--- a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMagnifier.kt
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMagnifier.kt
@@ -60,7 +60,6 @@
* The text magnifier follows horizontal dragging exactly, but is vertically clamped to the current
* line, so when it changes lines we animate it.
*/
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.animatedSelectionMagnifier(
magnifierCenter: () -> Offset,
platformMagnifier: (animatedCenter: () -> Offset) -> Modifier
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
index e1375ef..925b531 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
@@ -16,7 +16,6 @@
package androidx.compose.foundation
-import android.annotation.SuppressLint
import android.os.Build
import android.widget.Magnifier
import androidx.annotation.ChecksSdkIntAtLeast
@@ -261,7 +260,6 @@
@OptIn(ExperimentalFoundationApi::class)
// The InspectorInfo this modifier reports is for the above public overload, and intentionally
// doesn't include the platformMagnifierFactory parameter.
-@SuppressLint("ModifierInspectorInfo")
@RequiresApi(28)
internal fun Modifier.magnifier(
sourceCenter: Density.() -> Offset,
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AndroidCursorHandle.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AndroidCursorHandle.android.kt
index 5d3a11a..9cd3f60 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AndroidCursorHandle.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AndroidCursorHandle.android.kt
@@ -59,7 +59,6 @@
Spacer(modifier.size(CursorHandleWidth, CursorHandleHeight).drawCursorHandle())
}
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.drawCursorHandle() = composed {
val handleColor = LocalTextSelectionColors.current.handleColor
this.then(
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/AndroidSelectionHandles.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/AndroidSelectionHandles.android.kt
index 2e36f5e..70000af 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/AndroidSelectionHandles.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/AndroidSelectionHandles.android.kt
@@ -105,7 +105,6 @@
)
}
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.drawSelectionHandle(
isStartHandle: Boolean,
direction: ResolvedTextDirection,
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
index fb8c947..2227609 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
@@ -16,7 +16,6 @@
package androidx.compose.foundation.text.selection
-import android.annotation.SuppressLint
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.MagnifierStyle
import androidx.compose.foundation.magnifier
@@ -37,7 +36,6 @@
// We use composed{} to read a local, but don't provide inspector info because the underlying
// magnifier modifier provides more meaningful inspector info.
-@SuppressLint("ModifierInspectorInfo")
@OptIn(ExperimentalFoundationApi::class)
internal actual fun Modifier.selectionMagnifier(manager: SelectionManager): Modifier {
// Avoid tracking animation state on older Android versions that don't support magnifiers.
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.android.kt
index af9d356..e750b21 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.android.kt
@@ -16,7 +16,6 @@
package androidx.compose.foundation.text.selection
-import android.annotation.SuppressLint
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.MagnifierStyle
import androidx.compose.foundation.magnifier
@@ -35,7 +34,6 @@
// We use composed{} to read a local, but don't provide inspector info because the underlying
// magnifier modifier provides more meaningful inspector info.
-@SuppressLint("ModifierInspectorInfo")
@OptIn(ExperimentalFoundationApi::class)
internal actual fun Modifier.textFieldMagnifier(manager: TextFieldSelectionManager): Modifier {
// Avoid tracking animation state on older Android versions that don't support magnifiers.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
index 1e56270..9e43e1c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
@@ -35,7 +35,7 @@
import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
-@Suppress("ComposableModifierFactory", "ModifierInspectorInfo")
+@Suppress("ComposableModifierFactory")
@Composable
internal fun Modifier.lazyLayoutSemantics(
itemProvider: LazyLayoutItemProvider,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
index 69c0ab3..703aa24 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
@@ -36,7 +36,6 @@
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.withContext
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.cursor(
state: TextFieldState,
value: TextFieldValue,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.kt
index 10bdc6d..37db2fe 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.kt
@@ -232,7 +232,6 @@
}
}
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.textFieldKeyInput(
state: TextFieldState,
manager: TextFieldSelectionManager,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldPressGestureFilter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldPressGestureFilter.kt
index 24af865..dbb23d8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldPressGestureFilter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldPressGestureFilter.kt
@@ -33,7 +33,6 @@
/**
* Required for the press and tap [MutableInteractionSource] consistency for TextField.
*/
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.tapPressTextFieldModifier(
interactionSource: MutableInteractionSource?,
enabled: Boolean = true,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldSize.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldSize.kt
index 62f03bc..124fdb7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldSize.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldSize.kt
@@ -35,7 +35,6 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.textFieldMinSize(style: TextStyle) = composed {
val density = LocalDensity.current
val fontFamilyResolver = LocalFontFamilyResolver.current
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMagnifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMagnifier.kt
index 531f018..604a5c3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMagnifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMagnifier.kt
@@ -60,7 +60,6 @@
* The text magnifier follows horizontal dragging exactly, but is vertically clamped to the current
* line, so when it changes lines we animate it.
*/
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.animatedSelectionMagnifier(
magnifierCenter: () -> Offset,
platformMagnifier: (animatedCenter: () -> Offset) -> Modifier
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
index 29098a8..34632df 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
@@ -29,7 +29,6 @@
override val issues get(): List<Issue> {
return listOf(
ListIteratorDetector.ISSUE,
- ModifierInspectorInfoDetector.ISSUE,
UnnecessaryLambdaCreationDetector.ISSUE,
)
}
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
deleted file mode 100644
index 7cb8923..0000000
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
+++ /dev/null
@@ -1,695 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-@file:Suppress("UnstableApiUsage")
-
-package androidx.compose.lint
-
-import com.android.tools.lint.client.api.UElementHandler
-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.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.android.tools.lint.detector.api.TextFormat
-import com.intellij.psi.PsiField
-import com.intellij.psi.PsiJavaFile
-import com.intellij.psi.PsiMethod
-import com.intellij.psi.PsiType
-import com.intellij.psi.PsiWildcardType
-import com.intellij.psi.impl.source.PsiClassReferenceType
-import com.intellij.psi.util.InheritanceUtil
-import org.jetbrains.kotlin.asJava.classes.KtLightClass
-import org.jetbrains.kotlin.asJava.elements.KtLightField
-import org.jetbrains.kotlin.asJava.elements.KtLightParameter
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtParameter
-import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes.VALUE_PARAMETER
-import org.jetbrains.uast.UBinaryExpression
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UExpressionList
-import org.jetbrains.uast.UIfExpression
-import org.jetbrains.uast.ULambdaExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UObjectLiteralExpression
-import org.jetbrains.uast.UQualifiedReferenceExpression
-import org.jetbrains.uast.UReturnExpression
-import org.jetbrains.uast.USimpleNameReferenceExpression
-import org.jetbrains.uast.USwitchClauseExpression
-import org.jetbrains.uast.USwitchClauseExpressionWithBody
-import org.jetbrains.uast.USwitchExpression
-import org.jetbrains.uast.UYieldExpression
-import org.jetbrains.uast.kotlin.KotlinStringTemplateUPolyadicExpression
-import org.jetbrains.uast.kotlin.KotlinStringULiteralExpression
-import org.jetbrains.uast.kotlin.KotlinUArrayAccessExpression
-import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
-import org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression
-import org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression
-import org.jetbrains.uast.visitor.AbstractUastVisitor
-
-private const val ModifierClass = "androidx.compose.ui.Modifier"
-private const val ModifierCompanionClass = "androidx.compose.ui.Modifier.Companion"
-private const val ModifierFile = "Modifier.kt"
-private const val ComposedModifierFile = "ComposedModifier.kt"
-private const val InspectableValueFile = "InspectableValue.kt"
-private const val LambdaFunction = "kotlin.jvm.functions.Function1"
-private const val InspectorInfoClass = "androidx.compose.ui.platform.InspectorInfo"
-private const val InspectorValueInfoClass = "androidx.compose.ui.platform.InspectorValueInfo"
-private const val UnitClass = "kotlin.Unit"
-private const val DebugInspectorInfoFunction = "debugInspectorInfo"
-private const val ThenMethodName = "then"
-private const val ComposedMethodName = "composed"
-private const val RememberMethodName = "remember"
-private const val InspectableMethodName = "inspectable"
-private const val ComposedMethodPackage = "androidx.compose.ui"
-private const val RememberMethodPackage = "androidx.compose.runtime"
-private val DemosPackageRegEx = "androidx\\.compose\\..+\\.demos\\..+".toRegex()
-private val UiPackage = FqName("androidx.compose.ui")
-private val PlatformPackage = FqName("androidx.compose.ui.platform")
-
-/**
- * Lint [Detector] to ensure that we are creating debug information for the layout inspector on
- * all modifiers. For example in:
- * ```
- * fun Modifier.width(width: Dp) = this.then(SizeModifier(width))
- * ```
- *
- * The layout inspector will not know that the name is `width` and the `width` member
- * may not be a field in SizeModifier that we can see via reflection.
- *
- * To supply debug information to the layout inspector include an InspectorInfo lambda like this:
- * ```
- * fun Modifier.width(width: Dp) = this.then(
- * SizeModifier(
- * width = width,
- * inspectorInfo = debugInspectorInfo {
- * name = "width",
- * value = width,
- * )
- * )
- * )
- * ```
- *
- * The `debugInspectorInfo' lambda will be stripped from release builds.
- *
- * If the modifier has multiple parameters use the `properties` specifier instead:
- * ```
- * fun Modifier.size(width: Dp, height: Dp) = this.then(
- * SizeModifier(
- * width = width,
- * inspectorInfo = debugInspectorInfo {
- * name = "width",
- * properties["width"] = width
- * properties["height"] = height
- * )
- * )
- * )
- * ```
- *
- * This inspector only applies to Modifiers implemented prior to Modifier.Node. For the
- * corresponding Modifier.Node inspection, see `ModifierNodeInspectablePropertiesDetector` in
- * `:compose:ui:ui-lint`.
- */
-class ModifierInspectorInfoDetector : Detector(), SourceCodeScanner {
- override fun createUastHandler(context: JavaContext): UElementHandler = ModifierHandler(context)
-
- override fun getApplicableUastTypes() = listOf(UMethod::class.java)
-
- /**
- * This handler visits every method and determines if this is a Modifier definition with a
- * `then` construct, and reports an issue for any of the following problems:
- *
- * 1. The modifier implementation is not a function call
- * 2. The modifier implementation does not have an InspectorInfo lambda as last parameter
- * 3. The lambda is not surrounded by a call to `debugInspectorInfo`
- * 4. The modifier name is missing from the lambda or specified incorrectly
- * 5. A modifier value is specified that doesn't match an actual modifier argument
- * 6. A modifier argument is specified that doesn't match an actual modifier argument
- * 7. An actual modifier argument is missing
- */
- private class ModifierHandler(private val context: JavaContext) : UElementHandler() {
- private val returnVisitor = ReturnVisitor()
- private val builderVisitor = ModifierBuilderVisitor()
- private val modifierVisitor = ModifierVisitor()
- private val debugInspectorVisitor = DebugInspectorVisitor()
- private val lambdaVisitor = InspectorLambdaVisitor()
- private var methodInfo: MethodInfo? = null
-
- override fun visitMethod(node: UMethod) {
- if (node.isInFile(ModifierFile, UiPackage) ||
- node.isInFile(ComposedModifierFile, UiPackage) ||
- node.isInFile(InspectableValueFile, PlatformPackage) ||
- firstParameterType(node) != ModifierClass ||
- node.returnType?.canonicalText != ModifierClass ||
- DemosPackageRegEx.matches(node.containingClass?.qualifiedName ?: "")
- ) {
- // Ignore the method if it isn't a method on Modifier returning a Modifier,
- // or if the method is defined in Modifier.kt or ComposedModifier.kt
- return
- }
- methodInfo = MethodInfo(node)
- node.uastBody?.accept(returnVisitor)
- methodInfo = null
- }
-
- private fun UMethod.isInFile(fileName: String, packageName: FqName): Boolean {
- val file = containingFile as? KtFile ?: return false
- return file.name == fileName && file.packageFqName == packageName
- }
-
- private fun firstParameterType(method: UMethod): String? =
- (method.parameters.firstOrNull() as? KtLightParameter)?.type?.canonicalText
-
- private fun isModifierType(type: PsiType?): Boolean =
- InheritanceUtil.isInheritor(type, ModifierClass)
-
- // Disregard the mangled part of a method with inline class parameters.
- // TODO: change this to an API call when a demangle function is available
- private fun name(method: PsiMethod): String =
- method.name.substringBefore('-')
-
- private fun wildcardType(type: PsiType?): String? =
- (type as? PsiWildcardType)?.bound?.canonicalText
-
- private fun isThenFunctionCall(node: UQualifiedReferenceExpression): Boolean {
- if (!isModifierType(node.receiver.getExpressionType())) return false
- val then = node.selector as? KotlinUFunctionCallExpression ?: return false
- return then.methodName == ThenMethodName &&
- then.valueArguments.size == 1 &&
- isModifierType(then.valueArguments.first().getExpressionType())
- }
-
- private fun isComposeFunctionCall(node: UCallExpression): Boolean =
- node.methodName == ComposedMethodName &&
- node.receiverType?.canonicalText == ModifierClass &&
- node.returnType?.canonicalText == ModifierClass &&
- methodPackageName(node) == ComposedMethodPackage
-
- private fun isRememberFunctionCall(node: UCallExpression): Boolean =
- node.methodName == RememberMethodName &&
- node.receiver == null &&
- isModifierType(node.returnType) &&
- methodPackageName(node) == RememberMethodPackage
-
- private fun isInspectableModifier(node: UCallExpression): Boolean =
- node.methodName == InspectableMethodName &&
- node.receiverType?.canonicalText in listOf(ModifierClass, ModifierCompanionClass) &&
- node.returnType?.canonicalText == ModifierClass &&
- methodPackageName(node) == PlatformPackage.asString()
-
- // Return true if this is a lambda expression of the type: "InspectorInfo.() -> Unit"
- private fun isInspectorInfoLambdaType(type: PsiType?): Boolean {
- val referenceType = type as? PsiClassReferenceType ?: return false
- return referenceType.rawType().canonicalText == LambdaFunction &&
- referenceType.parameterCount == 2 &&
- wildcardType(referenceType.parameters[0]) == InspectorInfoClass &&
- wildcardType(referenceType.parameters[1]) !== UnitClass
- }
-
- private fun isDebugInspectorInfoCall(node: UCallExpression): Boolean =
- node.methodName == DebugInspectorInfoFunction &&
- node.valueArgumentCount == 1 &&
- isInspectorInfoLambdaType(node.valueArguments.first().getExpressionType()) &&
- isInspectorInfoLambdaType(node.returnType)
-
- private fun methodPackageName(node: UCallExpression): String? =
- (node.resolve()?.containingFile as? PsiJavaFile)?.packageName
-
- private fun wrongLambda(element: UElement) =
- report(element, ISSUE.getBriefDescription(TextFormat.TEXT))
-
- private fun missingDebugInfoCall(element: UElement) =
- report(element, message = "Expected debugInspectorInfo call")
-
- private fun reportMissing(missingVariables: Set<String>, element: UElement) = report(
- element = element,
- message = "These lambda arguments are missing in the InspectorInfo: " +
- "`${missingVariables.sorted().joinToString("`, `")}`"
- )
-
- private fun wrongName(expectedName: String, unexpected: UElement) = report(
- element = unexpected,
- message = "Expected name of the modifier: `\"name\" = \"${expectedName}\"`"
- )
-
- private fun wrongArgument(args: Collection<String>, unexpected: UElement) {
- val message = when (args.size) {
- 0 -> "Unexpected, modifier has no arguments"
- 1 -> "Expected the variable: \"${args.first()}\""
- else -> "Expected one of the variables: ${args.joinToString(", ", "\"", "\"")}"
- }
- report(unexpected, message = message)
- }
-
- private fun wrongArgumentForIndex(indexName: String, value: UElement) =
- report(value, message = "The value should match the index name: `$indexName`")
-
- private fun unexpected(element: UElement) =
- report(element, message = "Unexpected element found")
-
- private fun report(element: UElement, message: String) {
- context.report(
- ISSUE,
- element,
- context.getNameLocation(element),
- message
- )
- methodInfo?.foundError = true
- }
-
- /**
- * Method related data for checking the inspector lambda against the modifier method.
- *
- * The inspector lambda normally holds a `properties["name"] = name` expression for each
- * of the parameters of the modifier. However if the modifier has exactly one parameter,
- * and that parameter is an instance of a data class, then the arguments of the data class
- * can be used in the lambda instead of the modifier parameters.
- *
- * See the test existingInspectorInfoWithDataClassMemberValues as an example.
- */
- private inner class MethodInfo(method: UMethod) {
- val name = name(method)
- val methodArguments = Arguments(findMethodArguments(method))
- val dataClassArguments = Arguments(findDataClassMembers(method))
- var foundName = false
- var foundError = false
-
- fun checkName(value: UExpression) {
- if (name == literal(value)) {
- foundName = true
- } else if (!foundError) {
- wrongName(name, value)
- }
- }
-
- fun checkValue(value: UExpression, inConditional: Boolean) {
- val (arguments, variable) = variable(value)
- if (variable != null && arguments.expected.contains(variable)) {
- arguments.found.add(variable)
- } else if (!foundError && !(inConditional && isArgumentReceiver(value))) {
- wrongArgument(arguments.expected, value)
- }
- }
-
- /**
- * Check an array from an Inspector lambda.
- *
- * A Inspector lambda may contain expressions of the form:
- * ` properties["name"] = value
- *
- * Check the name and value against the known parameters of the modifier.
- */
- fun checkArray(
- keyExpr: KotlinUArrayAccessExpression,
- value: UExpression,
- inConditional: Boolean
- ) {
- if (foundError) {
- return
- }
- val index = keyExpr.indices.singleOrNull()
- val key = literal(index)
- val (arguments, variable) = variable(value)
- when {
- key != null && arguments.expected.contains(key) && key == variable ->
- arguments.found.add(variable)
- inConditional && isArgumentReceiver(value) ->
- {} // ignore extra information
- key == null || !arguments.expected.contains(key) ->
- wrongArgument(arguments.expected, index ?: keyExpr)
- else ->
- wrongArgumentForIndex(key, value)
- }
- }
-
- fun checkComplete(lambda: UExpression) {
- if (foundError) {
- return
- } else if (!foundName) {
- wrongName(name, lambda)
- } else {
- val arguments =
- if (dataClassArguments.found.isNotEmpty()) dataClassArguments
- else methodArguments
- val absentVariables = arguments.expected.minus(arguments.found)
- if (absentVariables.isNotEmpty()) {
- reportMissing(absentVariables, lambda)
- }
- }
- }
-
- private fun literal(expr: UExpression?): String? = when (expr) {
- is KotlinStringULiteralExpression,
- is KotlinStringTemplateUPolyadicExpression -> expr.evaluate() as? String
- else -> null
- }
-
- private fun isArgumentReceiver(value: UExpression): Boolean {
- val reference = value as? KotlinUQualifiedReferenceExpression ?: return false
- val (arguments, variable) = variable(reference.receiver)
- return arguments.expected.contains(variable)
- }
-
- /**
- * Return the name of the variable that the [expr] represents.
- *
- * This can be a simple parameter variable from the modifier.\
- * Example: `width` from `fun Modifier.width(width: Int)`
- *
- * Or it can be a selector from a data class parameter variable from the modifier.\
- * Example: `values.start` from `fun Modifier.border(values: Borders)`
- *
- * where `Borders` is a data class with `start` as one of the value parameters.
- */
- private fun variable(expr: UExpression?): Pair<Arguments, String?> {
- return when (expr) {
- is KotlinUSimpleReferenceExpression ->
- Pair(methodArguments, expr.identifier)
- is KotlinUQualifiedReferenceExpression -> {
- val receiver = identifier(expr.receiver)
- if (methodArguments.expected.contains(receiver)) {
- Pair(dataClassArguments, identifier(expr.selector))
- } else {
- Pair(methodArguments, null)
- }
- }
- else -> Pair(methodArguments, null)
- }
- }
-
- private fun identifier(expr: UExpression?): String? =
- (expr as? KotlinUSimpleReferenceExpression)?.identifier
-
- private fun findMethodArguments(method: UMethod): Set<String> =
- method.parameters.asSequence().drop(1).mapNotNull { it.name }.toSet()
-
- /**
- * Return all the names of the value parameters of a data class.
- *
- * We don't actually know if a class is a data class, but we can see if all the
- * parameters of the main constructor are value parameter fields.
- */
- private fun findDataClassMembers(method: UMethod): Set<String> {
- val singleParameter = method.parameters.asSequence().drop(1).singleOrNull()
- val type = (singleParameter?.type as? PsiClassReferenceType)?.reference?.resolve()
- val klass = type as? KtLightClass ?: return emptySet()
- val mainConstructor = klass.constructors.firstOrNull() ?: return emptySet()
- val valueParameters = klass.fields.asSequence()
- .filter { isValueParameter(it) }.map { it.name }.toSet()
- val constructorParameters = mainConstructor.parameters.asSequence()
- .map { it.name }.toSet()
- if (constructorParameters != valueParameters) {
- return emptySet()
- }
- return valueParameters
- }
-
- private fun isValueParameter(field: PsiField): Boolean {
- val kotlinField = field as? KtLightField
- val kotlinParameter =
- kotlinField?.lightMemberOrigin?.originalElement as? KtParameter
- return kotlinParameter?.elementType == VALUE_PARAMETER
- }
- }
-
- private class Arguments(val expected: Set<String>) {
- val found = mutableSetOf<String>()
- }
-
- /**
- * Finds all return expressions, ignores all other elements.
- */
- private inner class ReturnVisitor : AbstractUastVisitor() {
- override fun visitReturnExpression(node: UReturnExpression): Boolean {
- node.returnExpression?.accept(builderVisitor)
- return true
- }
-
- // Ignore returns from a lambda expression
- override fun visitLambdaExpression(node: ULambdaExpression): Boolean = true
- }
-
- /**
- * Find and check known Modifier builder expressions.
- *
- * The expression is known to be the return expression from a return statement.
- *
- * Currently the only check the following expressions:
- * - Modifier.then(Modifier)
- * - Modifier.composed(InspectorInfoLambda,factory)
- * - everything else is ignored
- */
- private inner class ModifierBuilderVisitor : AbstractUastVisitor() {
- override fun visitQualifiedReferenceExpression(
- node: UQualifiedReferenceExpression
- ): Boolean {
- if (isThenFunctionCall(node)) {
- node.receiver.accept(this)
- val then = node.selector as KotlinUFunctionCallExpression
- then.valueArguments.first().accept(modifierVisitor)
- return true
- }
- return super.visitQualifiedReferenceExpression(node)
- }
-
- override fun visitCallExpression(node: UCallExpression): Boolean {
- if (isComposeFunctionCall(node)) {
- val inspectorInfo = node.valueArguments
- .find { isInspectorInfoLambdaType(it.getExpressionType()) }
- if (inspectorInfo == null) {
- wrongLambda(node)
- } else {
- inspectorInfo.accept(debugInspectorVisitor)
- }
- return true
- }
- return super.visitCallExpression(node)
- }
- }
-
- /**
- * Find and check the Modifier constructor.
- *
- * The expression is known to be inside one of the Modifier builder expressions accepted
- * by [ModifierBuilderVisitor].
- *
- * Currently the only accepted expressions are of the form:
- * - SomeClassExtendingModifier(p1,p2,p3,p4,inspectorInfoLambda)
- * - SomeClass.InnerModifier(p1,p2,p3,p4,inspectorInfoLambda)
- * - object : Modifier, InspectorValueInfoClass(inspectorInfoLambda)
- * - remember { }
- * - Modifier
- * - if-then-else (with a modifier constructor in both then and else)
- * - when (with a modifier constructor in each of the when clauses)
- * All other expressions are considered errors.
- */
- private inner class ModifierVisitor : UnexpectedVisitor({ wrongLambda(it) }) {
- override fun visitCallExpression(node: UCallExpression): Boolean {
- val info: UExpression? = node.valueArguments.firstOrNull {
- isInspectorInfoLambdaType(it.getExpressionType())
- }
- if (info != null) {
- info.accept(debugInspectorVisitor)
- return true
- }
- if (isRememberFunctionCall(node)) {
- val lambda = node.valueArguments.singleOrNull() as? ULambdaExpression
- val body = lambda?.body as? UBlockExpression
- val ret = body?.expressions?.firstOrNull() as? UReturnExpression
- val definition = ret?.returnExpression ?: return super.visitCallExpression(node)
- definition.accept(this)
- return true
- }
- if (isModifierType(node.receiverType) && isModifierType(node.returnType)) {
- // For now accept all other calls. Assume that the method being called
- // will add inspector information.
- return true
- }
- return super.visitCallExpression(node)
- }
-
- override fun visitQualifiedReferenceExpression(
- node: UQualifiedReferenceExpression
- ): Boolean {
- node.selector.accept(this)
- return true
- }
-
- override fun visitObjectLiteralExpression(node: UObjectLiteralExpression): Boolean {
- if (node.valueArgumentCount == 1 &&
- node.declaration.uastSuperTypes.any {
- it.getQualifiedName() == InspectorValueInfoClass
- }
- ) {
- node.valueArguments.first().accept(debugInspectorVisitor)
- return true
- }
- return super.visitObjectLiteralExpression(node)
- }
-
- override fun visitSimpleNameReferenceExpression(
- node: USimpleNameReferenceExpression
- ): Boolean {
- // Accept any variable including Modifier
- return true
- }
-
- override fun visitIfExpression(node: UIfExpression): Boolean {
- node.thenExpression?.accept(this)
- node.elseExpression?.accept(this)
- return true
- }
-
- override fun visitSwitchExpression(node: USwitchExpression): Boolean {
- node.body.accept(this)
- return true
- }
-
- override fun visitSwitchClauseExpression(node: USwitchClauseExpression): Boolean {
- (node as? USwitchClauseExpressionWithBody)?.let {
- it.body.expressions.last().accept(this)
- return true
- }
- return super.visitSwitchClauseExpression(node)
- }
-
- override fun visitYieldExpression(node: UYieldExpression): Boolean {
- return false
- }
-
- override fun visitExpressionList(node: UExpressionList): Boolean {
- return false
- }
-
- override fun visitBlockExpression(node: UBlockExpression): Boolean {
- node.expressions.lastOrNull()?.accept(this)
- return true
- }
- }
-
- /**
- * Expect debugInspectorInfo factory method:
- * - debugInspectorInfo { inspectorInfoLambda }
- * For all other expressions: complain about the missing debugInspectorInfo call.
- */
- private inner class DebugInspectorVisitor :
- UnexpectedVisitor({ missingDebugInfoCall(it) }) {
-
- override fun visitCallExpression(node: UCallExpression): Boolean {
- if (isDebugInspectorInfoCall(node)) {
- node.valueArguments.single().accept(lambdaVisitor)
- return true
- }
- return super.visitCallExpression(node)
- }
- }
-
- /**
- * Find and check the InspectorInfo lambda.
- *
- * The expression is known to be a InspectorInfo lambda found inside a debugInspectorInfo
- * call from one of the modifier constructors accepted by [ModifierVisitor].
- *
- * Check the name, value, and properties against the original modifier definition expressed
- * by [methodInfo] found by [ModifierHandler] above. After the lambda expression check that
- * all the elements of [methodInfo] was found.
- */
- private inner class InspectorLambdaVisitor : UnexpectedVisitor({ unexpected(it) }) {
- // We allow alternate values inside conditionals.
- // Example see the test: existingInspectorInfoWffithConditionals.
- var inConditional = false
-
- override fun visitBinaryExpression(node: UBinaryExpression): Boolean {
- val left = node.leftOperand
- val right = node.rightOperand
- when {
- left is KotlinUSimpleReferenceExpression && left.identifier == "name" ->
- methodInfo?.checkName(right)
- left is KotlinUSimpleReferenceExpression && left.identifier == "value" ->
- methodInfo?.checkValue(right, inConditional)
- left is KotlinUArrayAccessExpression ->
- methodInfo?.checkArray(left, right, inConditional)
- else ->
- unexpected(left)
- }
- return true
- }
-
- override fun visitLambdaExpression(node: ULambdaExpression): Boolean {
- // accept, and recurse
- return false
- }
-
- override fun visitIfExpression(node: UIfExpression): Boolean {
- inConditional = true
- try {
- node.thenExpression?.accept(this)
- node.elseExpression?.accept(this)
- } finally {
- inConditional = false
- }
- return true
- }
-
- override fun visitBlockExpression(node: UBlockExpression): Boolean {
- // accept, and recurse
- return false
- }
-
- override fun afterVisitLambdaExpression(node: ULambdaExpression) {
- methodInfo?.checkComplete(node)
- }
- }
-
- private abstract inner class UnexpectedVisitor(
- private val error: (node: UElement) -> Unit
- ) : AbstractUastVisitor() {
-
- override fun visitElement(node: UElement): Boolean {
- error(node)
- return true
- }
- }
- }
-
- companion object {
- val ISSUE = Issue.create(
- id = "ModifierInspectorInfo",
- briefDescription = "Modifier missing inspectorInfo",
- explanation =
- """
- The Layout Inspector will see an instance of the usually private modifier class \
- where the modifier name is gone, and the fields may not reflect directly on what \
- was specified for the modifier. Instead specify the `inspectorInfo` directly on \
- the modifier. See example here:
- `androidx.compose.ui.samples.InspectorInfoInComposedModifierSample`.""",
- category = Category.PRODUCTIVITY,
- priority = 5,
- severity = Severity.ERROR,
- implementation = Implementation(
- ModifierInspectorInfoDetector::class.java,
- Scope.JAVA_FILE_SCOPE
- )
- )
- }
-}
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
deleted file mode 100644
index 25fa03f..0000000
--- a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
+++ /dev/null
@@ -1,1440 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-@file:Suppress("UnstableApiUsage")
-
-package androidx.compose.lint
-
-import androidx.compose.lint.test.Stubs
-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
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-/* ktlint-disable max-line-length */
-@RunWith(JUnit4::class)
-class ModifierInspectorInfoDetectorTest : LintDetectorTest() {
- override fun getDetector(): Detector = ModifierInspectorInfoDetector()
-
- override fun getIssues(): List<Issue> = listOf(ModifierInspectorInfoDetector.ISSUE)
-
- private val inspectableInfoStub = kotlin(
- """
- package androidx.compose.ui.platform
-
- import androidx.compose.ui.Modifier
-
- val NoInspectorInfo: InspectorInfo.() -> Unit = {}
- val DebugInspectorInfo = false
-
- interface InspectableValue {
- val inspectableElements: Sequence<ValueElement>
- get() = emptySequence()
-
- val nameFallback: String?
- get() = null
-
- val valueOverride: Any?
- get() = null
- }
-
- data class ValueElement(val name: String, val value: Any?)
-
- class InspectorInfo {
- var name: String? = null
- var value: Any? = null
- val properties = ValueElementSequence()
- }
-
- class ValueElementSequence : Sequence<ValueElement> {
- private val elements = mutableListOf<ValueElement>()
-
- override fun iterator(): Iterator<ValueElement> = elements.iterator()
-
- operator fun set(name: String, value: Any?) {
- elements.add(ValueElement(name, value))
- }
- }
-
- abstract class InspectorValueInfo(
- private val info: InspectorInfo.() -> Unit
- ) : InspectableValue {
- private var _values: InspectorInfo? = null
-
- private val values: InspectorInfo
- get() {
- val valueInfo = _values ?: InspectorInfo().apply { info() }
- _values = valueInfo
- return valueInfo
- }
-
- override val nameFallback: String?
- get() = values.name
-
- override val valueOverride: Any?
- get() = values.value
-
- override val inspectableElements: Sequence<ValueElement>
- get() = values.properties
- }
-
- inline fun debugInspectorInfo(
- crossinline definitions: InspectorInfo.() -> Unit
- ): InspectorInfo.() -> Unit =
- if (DebugInspectorInfo) ({ definitions() }) else NoInspectorInfo
-
- fun Modifier.inspectable(
- inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
- wrapped: Modifier
- ): Modifier = this.then(InspectableModifierImpl(inspectorInfo, wrapped))
-
- /**
- * Interface for a [Modifier] wrapped for inspector purposes.
- */
- interface InspectableModifier {
- val wrapped: Modifier
- }
-
- private class InspectableModifierImpl(
- inspectorInfo: InspectorInfo.() -> Unit,
- override val wrapped: Modifier
- ) : Modifier.Element, InspectableModifier, InspectorValueInfo(inspectorInfo) {
- override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
- wrapped.foldIn(operation(initial, this), operation)
-
- override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
- operation(this, wrapped.foldOut(initial, operation))
-
- override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =
- wrapped.any(predicate)
-
- override fun all(predicate: (Modifier.Element) -> Boolean): Boolean =
- wrapped.all(predicate)
- }
- """
- ).indented()
-
- private val composedStub = kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
-
- fun Modifier.composed(
- inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
- factory: Modifier.() -> Modifier
- ): Modifier = this.then(ComposedModifier(inspectorInfo, factory))
-
- private class ComposedModifier(
- inspectorInfo: InspectorInfo.() -> Unit,
- val factory: Modifier.() -> Modifier
- ) : Modifier.Element, InspectorValueInfo(inspectorInfo)
- """
- ).indented()
-
- @Test
- fun existingInspectorInfo() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- inline class Dp(val value: Float)
-
- fun Modifier.width1(width: Dp) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "width1"
- properties["width"] = width
- }))
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo)
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun existingInspectorInfoWithStatementsBeforeDefinition() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- inline class Dp(val value: Float)
-
- inline fun require(value: Boolean, lazyMessage: () -> String) {
- if (!value) {
- val message = lazyMessage()
- throw IllegalArgumentException(message)
- }
- }
-
- fun Modifier.width1(width: Dp): Modifier {
- require(width.value > 0.0f) { return "sds" }
-
- val x = width.value.toInt() * 2
- for (i in 0..4) {
- println("x = " + x)
- }
-
- return this.then(SizeModifier1(x, inspectorInfo = debugInspectorInfo {
- name = "width1"
- properties["width"] = width
- }))
- }
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo)
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun existingInspectorInfoWithValue() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width2(width: Int) =
- this.then(SizeModifier2(width, inspectorInfo = debugInspectorInfo {
- name = "width2"
- value = width
- }))
-
- private class SizeModifier2(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun existingInspectorInfoViaSynonym() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- inline class Dp(val value: Float)
-
- fun Modifier.width1(width: Dp) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "width1"
- properties["width"] = width
- }))
-
- fun Modifier.width2(width: Dp) = width1(width)
-
- fun Modifier.width20() = width1(Dp(20.0f))
-
- fun Modifier.preferredIconWidth(x: Int) = this.then(
- if (x == 7) DefaultIconSizeModifier else Modifier
- )
-
- private val DefaultIconSizeModifier = Modifier.width1(Dp(24.0f))
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo)
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun existingInspectorInfoWithAnonymousClass() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.drawBehind() = this.then(
- object : Modifier,
- InspectorValueInfo(debugInspectorInfo { name = "drawBehind" }) {}
- )
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun existingInspectorInfoWithDataClassMemberValues() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.border(values: Borders) =
- this.then(BorderModifier(values, inspectorInfo = debugInspectorInfo {
- name = "border"
- properties["start"] = values.start
- properties["top"] = values.top
- properties["end"] = values.end
- properties["bottom"] = values.bottom
- }))
-
- fun Modifier.border2(start: Int, top: Int, end: Int, bottom: Int) =
- this.then(
- BorderModifier2(
- start, top, end, bottom, inspectorInfo = debugInspectorInfo {
- name = "border2"
- properties["start"] = start
- properties["top"] = top
- properties["end"] = end
- properties["bottom"] = bottom
- }))
-
- fun Modifier.border2(values: Borders) =
- border2(values.start, values.top, values.end, values.bottom)
-
- fun Modifier.border3(corner1: Location, corner2: Location) =
- border2(corner1.x, corner1.y, corner2.x, corner2.y)
-
- private class BorderModifier(
- val values: Borders,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- private class BorderModifier2(
- val start: Int,
- val top: Int,
- val end: Int,
- val bottom: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- data class Borders(val start: Int, val top: Int, val end: Int, val bottom: Int) {
- constructor(all: Int) : this(all, all, all, all)
- }
-
- data class Location(val x: Int, val y: Int)
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun existingInspectorInfoWithConditional() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.padding(size: Int) =
- this.then(
- if (size >= 10) {
- PaddingModifier(
- paddingSize = size,
- inspectorInfo = debugInspectorInfo {
- name = "padding"
- properties["size"] = size
- }
- )
- } else {
- Modifier
- }
- )
-
- fun Modifier.paddingFromBaseline(top: Int, bottom: Int) = this
- .then(if (bottom > 0) padding(bottom) else Modifier)
- .then(if (top > 0) padding(top) else Modifier)
-
- fun Modifier.paddingFromBaseline2(top: Int, bottom: Int) =
- this.padding(bottom).padding(top)
-
- private class PaddingModifier(
- paddingSize: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun existingInspectorInfoWithWhen() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.border(painter: Painter) =
- this.then(
- when (painter.size) {
- 0 -> Modifier
- 1 -> BorderModifier(inspectorInfo = debugInspectorInfo {
- name = "border"
- properties["painter"] = painter
- })
- else -> Modifier
- }
- )
-
- private class BorderModifier(
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- class Painter(val size: Int)
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun existingInspectorInfoWithConditionals() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- class Brush
-
- class SolidColor(val color: Int): Brush()
-
- fun Modifier.border(width: Int, brush: Brush, shape: Shape): Modifier = composed(
- factory = { BorderModifier(shape, width, brush) },
- inspectorInfo = debugInspectorInfo {
- name = "border"
- properties["width"] = width
- if (brush is SolidColor) {
- properties["color"] = brush.value
- value = brush.value
- } else {
- properties["brush"] = brush
- }
- properties["shape"] = shape
- }
- )
-
- fun Modifier.border2(width: Int, color: Int, shape: Shape): Modifier =
- if (width > 0) {
- composed(
- inspectorInfo = debugInspectorInfo {
- name = "border2"
- properties["width"] = width
- properties["color"] = color
- properties["shape"] = shape
- }
- ) {
- border(width, SolidColor(color), shape)
- }
- } else {
- this
- }
-
- private class BorderModifier(shape: Shape, width: Int, brush: Brush)
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun composedModifierWithInspectorInfo() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.border(width: Int): Modifier = composed(
- inspectorInfo = debugInspectorInfo {
- name = "border"
- properties["width"] = width
- },
- factory = { this.then(BorderModifier(width)) }
- )
-
- fun Modifier.border2(width: Int): Modifier = composed(
- factory = { this.then(BorderModifier(width)) },
- inspectorInfo = debugInspectorInfo {
- name = "border2"
- properties["width"] = width
- }
- )
-
- private class BorderModifier(private val width: Int): Modifier.Element {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun rememberModifierInfo() {
- lint().files(
- Stubs.Composable,
- Stubs.Modifier,
- composedStub,
- Stubs.Remember,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.runtime.*
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width1(width: Int) = this.then(
- remember {
- SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "width1"
- properties["width"] = width
- })
- }
- )
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo)
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun emptyModifier() {
- lint().files(
- Stubs.Composable,
- Stubs.Modifier,
- Stubs.Remember,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.runtime.*
- import androidx.compose.ui.Modifier
-
- internal actual fun Modifier.width1(width: Int): Modifier = this
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun acceptMissingInspectorInfoInSamples() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui.demos.whatever
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width2(width: Int) = this.then(SizeModifier2(width))
-
- private data class SizeModifier2(
- val width: Int,
- ): Modifier.Element
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun missingInspectorInfo() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.preferredWidth2(width: Int) = this.then(SizeModifier2(width))
-
- private data class SizeModifier2(
- val width: Int,
- ): Modifier.Element
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier2.kt:8: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
- fun Modifier.preferredWidth2(width: Int) = this.then(SizeModifier2(width))
- ~~~~~~~~~~~~~
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun composedModifierWithMissingInspectorInfo() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.border(width: Int): Modifier =
- composed { this.then(BorderModifier(width)) }
-
- private class BorderModifier(private val width: Int): Modifier.Element {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/BorderModifier.kt:9: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
- composed { this.then(BorderModifier(width)) }
- ~~~~~~~~
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun missingInspectorInfoFromInnerClassImplementation() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- /**
- * Documentation
- */
- fun Modifier.size(width: Int) =
- this.then(SizeModifier.WithOption(width))
-
- internal sealed class SizeModifier : Modifier.Element {
- internal data class WithOption(val width: Int) : SizeModifier() {
- }
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier.kt:12: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
- this.then(SizeModifier.WithOption(width))
- ~~~~~~~~~~
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithWrongName() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width1(width: Int) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "otherName"
- value = width
- }))
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier1.kt:10: Error: Expected name of the modifier: "name" = "width1" [ModifierInspectorInfo]
- name = "otherName"
- ~~~~~~~~~
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithWrongValue() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width1(width: Int) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "width1"
- value = 3.4
- }))
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier1.kt:11: Error: Expected the variable: "width" [ModifierInspectorInfo]
- value = 3.4
- ~~~
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithWrongValueWhenMultipleAreAvailable() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width1(width: Int, height: Int) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "width1"
- value = "oldWidth"
- }))
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier1.kt:11: Error: Expected one of the variables: "width, height" [ModifierInspectorInfo]
- value = "oldWidth"
- ~~~~~~~~
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithWrongParameterNameInProperties() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width1(width: Int, height: Int) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "width1"
- properties["width"] = width
- properties["other"] = height
- }))
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier1.kt:12: Error: Expected one of the variables: "width, height" [ModifierInspectorInfo]
- properties["other"] = height
- ~~~~~
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithMismatchInProperties() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width1(width: Int, height: Int) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "width1"
- properties["height"] = width
- properties["width"] = height
- }))
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier1.kt:11: Error: The value should match the index name: height [ModifierInspectorInfo]
- properties["height"] = width
- ~~~~~
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithMissingDebugSelector() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
-
- fun Modifier.width1(width: Int, height: Int) =
- this.then(SizeModifier1(width, height, inspectorInfo = {
- name = "width1"
- properties["width"] = width
- properties["height"] = height
- }))
-
- private class SizeModifier1(
- val width: Int,
- val height: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier1.kt:8: Error: Expected debugInspectorInfo call [ModifierInspectorInfo]
- this.then(SizeModifier1(width, height, inspectorInfo = {
- ^
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithMissingName() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.width1(width: Int) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- value = width
- }))
-
- private class SizeModifier1(
- val width: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/SizeModifier1.kt:9: Error: Expected name of the modifier: "name" = "width1" [ModifierInspectorInfo]
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- ^
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithMissingVariables() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.border(start: Int, top: Int, end: Int, bottom: Int) =
- this.then(
- BorderModifier(
- start, top, end, bottom, debugInspectorInfo {
- name = "border"
- properties["start"] = start
- }
- ))
-
- private class BorderModifier(
- val start: Int,
- val top: Int,
- val end: Int,
- bottom: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/BorderModifier.kt:11: Error: These lambda arguments are missing in the InspectorInfo: bottom, end, top [ModifierInspectorInfo]
- start, top, end, bottom, debugInspectorInfo {
- ^
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun inspectorInfoWithMissingDataClassMemberValues() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.border(values: Borders) =
- this.then(BorderModifier(values, inspectorInfo = debugInspectorInfo {
- name = "border"
- value = values.start
- properties["top"] = values.top
- }))
-
- private class BorderModifier(
- val values: Borders,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier.Element, InspectorValueInfo(inspectorInfo) {
- }
-
- data class Borders(val start: Int, val top: Int, val end: Int, val bottom: Int) {
- constructor(all: Int) : this(all, all, all, all)
- }
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/BorderModifier.kt:9: Error: These lambda arguments are missing in the InspectorInfo: bottom, end [ModifierInspectorInfo]
- this.then(BorderModifier(values, inspectorInfo = debugInspectorInfo {
- ^
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun missingInfoInConditionals() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- class Brush
-
- class SolidColor(val color: Int): Brush()
-
- fun Modifier.border(width: Int): Modifier = composed(
- inspectorInfo = debugInspectorInfo {
- name = "border"
- value = width
- }
- ) {
- BorderModifier(shape, width, brush)
- }
-
- fun Modifier.border2(width: Int): Modifier =
- if (width > 0) {
- border(width)
- } else {
- composed { BorderModifier(shape, width, brush) }
- }
-
- fun Modifier.border3(width: Int): Modifier =
- when {
- width < 0 -> this
- width < 2 -> border(width)
- width < 3 -> composed { BorderModifier(shape, width, brush) }
- else -> this
- }
-
- fun Modifier.border4(width: Int): Modifier =
- when {
- width < 0 -> this
- width < 2 -> border(width)
- else -> this.then(BorderModifier(shape, width, brush))
- }
-
- private class BorderModifier(shape: Shape, width: Int, brush: Brush): Modifier
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/androidx/compose/ui/Brush.kt:25: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
- composed { BorderModifier(shape, width, brush) }
- ~~~~~~~~
- src/androidx/compose/ui/Brush.kt:32: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
- width < 3 -> composed { BorderModifier(shape, width, brush) }
- ~~~~~~~~
- src/androidx/compose/ui/Brush.kt:40: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
- else -> this.then(BorderModifier(shape, width, brush))
- ~~~~~~~~~~~~~~
- 3 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun testInspectable() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package mypackage
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.inspectable
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.background(color: Int): Modifier = this.then(
- Background(color, inspectorInfo = debugInspectorInfo {
- name = "background"
- value = color
- })
- )
-
- fun Modifier.border(width: Int, color: Int): Modifier = this.then(
- BorderModifier(width, color, inspectorInfo = debugInspectorInfo {
- name = "border"
- properties["width"] = width
- properties["color"] = color
- })
- )
-
- fun Modifier.frame(color: Int) = this.then(
- Modifier.inspectable(
- inspectorInfo = debugInspectorInfo {
- name = "frame"
- value = color
- },
- wrapped = Modifier.background(color).border(width = 5, color = color)
- )
- )
-
- private class BackgroundModifier(
- color: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier
-
- private class BorderModifier(
- width: Int,
- color: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-
- @Test
- fun testInspectableWithMissingParameter() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package mypackage
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.inspectable
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- fun Modifier.background(color: Int): Modifier = this.then(
- Background(color, inspectorInfo = debugInspectorInfo {
- name = "background"
- value = color
- })
- )
-
- fun Modifier.border(width: Int, color: Int): Modifier = this.then(
- BorderModifier(width, color, inspectorInfo = debugInspectorInfo {
- name = "border"
- properties["width"] = width
- properties["color"] = color
- })
- )
-
- fun Modifier.frame(color: Int) = this.then(
- Modifier.inspectable(
- inspectorInfo = debugInspectorInfo {
- name = "frame"
- },
- wrapped = Modifier.background(color).border(width = 5, color = color)
- )
- )
-
- private class BackgroundModifier(
- color: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier
-
- private class BorderModifier(
- width: Int,
- color: Int,
- inspectorInfo: InspectorInfo.() -> Unit
- ): Modifier
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expect(
- """
- src/mypackage/BackgroundModifier.kt:26: Error: These lambda arguments are missing in the InspectorInfo: color [ModifierInspectorInfo]
- inspectorInfo = debugInspectorInfo {
- ^
- 1 errors, 0 warnings
- """
- )
- }
-
- @Test
- fun passInspectorInfoAtSecondLastParameter() {
- lint().files(
- Stubs.Modifier,
- composedStub,
- inspectableInfoStub,
- kotlin(
- """
- package androidx.compose.ui
-
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.InspectorInfo
- import androidx.compose.ui.platform.InspectorValueInfo
- import androidx.compose.ui.platform.debugInspectorInfo
-
- inline class Dp(val value: Float)
-
- fun Modifier.width1(width: Dp, height: Dp) =
- this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
- name = "width1"
- properties["width"] = width
- properties["height"] = height
- }, height))
-
- private class SizeModifier1(
- val width: Dp,
- inspectorInfo: InspectorInfo.() -> Unit,
- val height: Dp
- ): Modifier.Element, InspectorValueInfo(inspectorInfo)
-
- """
- ).indented()
- )
- .testModes(TestMode.DEFAULT)
- .run()
- .expectClean()
- }
-}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InteractiveComponentSize.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InteractiveComponentSize.kt
index 220666b..87ad9ab 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InteractiveComponentSize.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InteractiveComponentSize.kt
@@ -44,7 +44,6 @@
* sure there is adequate space for touch target expansion.
*/
@OptIn(ExperimentalMaterialApi::class)
-@Suppress("ModifierInspectorInfo")
fun Modifier.minimumInteractiveComponentSize(): Modifier = composed(
inspectorInfo = debugInspectorInfo {
name = "minimumInteractiveComponentSize"
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractiveComponentSize.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractiveComponentSize.kt
index 4597f577..31e307b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractiveComponentSize.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractiveComponentSize.kt
@@ -44,7 +44,6 @@
* sure there is adequate space for touch target expansion.
*/
@OptIn(ExperimentalMaterial3Api::class)
-@Suppress("ModifierInspectorInfo")
fun Modifier.minimumInteractiveComponentSize(): Modifier = composed(
inspectorInfo = debugInspectorInfo {
name = "minimumInteractiveComponentSize"
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CancelFocusDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CancelFocusDemo.kt
index ec9a575..3829477 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CancelFocusDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CancelFocusDemo.kt
@@ -16,7 +16,6 @@
package androidx.compose.ui.demos.focus
-import android.annotation.SuppressLint
import androidx.compose.foundation.border
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement.SpaceEvenly
@@ -76,7 +75,6 @@
}
}
-@SuppressLint("ModifierInspectorInfo")
private fun Modifier.focusableWithBorder() = composed {
var color by remember { mutableStateOf(Black) }
Modifier
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/layout/LazyLayoutSemantics.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/layout/LazyLayoutSemantics.kt
index 2d6def9..5058a26 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/layout/LazyLayoutSemantics.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/layout/LazyLayoutSemantics.kt
@@ -35,7 +35,7 @@
import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
-@Suppress("ComposableModifierFactory", "ModifierInspectorInfo")
+@Suppress("ComposableModifierFactory")
@Composable
internal fun Modifier.lazyLayoutSemantics(
itemProvider: LazyLayoutItemProvider,
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazySemantics.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazySemantics.kt
index 067b94a..fb8cb87 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazySemantics.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazySemantics.kt
@@ -22,7 +22,7 @@
import androidx.tv.foundation.lazy.layout.LazyLayoutSemanticState
// TODO (b/233188423): Address IllegalExperimentalApiUsage before moving to beta
-@Suppress("ComposableModifierFactory", "ModifierInspectorInfo", "IllegalExperimentalApiUsage")
+@Suppress("ComposableModifierFactory", "IllegalExperimentalApiUsage")
@ExperimentalFoundationApi
@Composable
internal fun rememberLazyListSemanticState(
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PickerGroup.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PickerGroup.kt
index 114c40e..a9be263 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PickerGroup.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PickerGroup.kt
@@ -320,7 +320,6 @@
return maxChildrenHeight.coerceIn(constraints.minHeight, constraints.maxHeight)
}
-@Suppress("ModifierInspectorInfo")
internal fun Modifier.autoCenteringTarget() = this.then(
object : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?) = AutoCenteringRowParentData()
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PositionIndicator.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PositionIndicator.kt
index 184e824..424101c 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PositionIndicator.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PositionIndicator.kt
@@ -1097,7 +1097,6 @@
// Sets the size of this element, but lets the child measure using the constraints
// of the element containing this.
private fun Modifier.transparentSizeModifier(size: Density.() -> IntSize): Modifier = this.then(
- @Suppress("ModifierInspectorInfo")
object : LayoutModifier {
override fun MeasureScope.measure(
measurable: Measurable,
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScrollAway.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScrollAway.kt
index b725fb2..d577f3e 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScrollAway.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScrollAway.kt
@@ -133,7 +133,6 @@
private fun Modifier.scrollAway(scrollFn: Density.() -> ScrollParams): Modifier =
this.then(
- @Suppress("ModifierInspectorInfo")
object : LayoutModifier {
override fun MeasureScope.measure(
measurable: Measurable,
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
index c6be1c3..e0b1320 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
@@ -16,7 +16,6 @@
package androidx.wear.compose.integration.demos
-import android.annotation.SuppressLint
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.FlingBehavior
@@ -185,7 +184,6 @@
internal data class TimestampedDelta(val time: Long, val delta: Float)
-@SuppressLint("ModifierInspectorInfo")
@OptIn(ExperimentalComposeUiApi::class)
@Suppress("ComposableModifierFactory")
@Composable