From 8e34e70295eb0dd63cdfff566aeb5804fc0b57ae Mon Sep 17 00:00:00 2001 From: Ilya Muromtsev Date: Sun, 31 May 2026 16:19:28 +0300 Subject: [PATCH] fix: avoid invalid TextRange in @Value reference provider (#236) When the @Value placeholder key is resolved from a constant or template, the evaluated string does not appear literally in the host text, so indexOf returns -1 and TextRange.from(-1, n) threw IllegalArgumentException. Skip references whose key cannot be located in the host text. Adds regression tests. --- ...eConfigurationPropertyReferenceProvider.kt | 7 +- ...figurationPropertyReferenceProviderTest.kt | 73 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 modules/spring-core/src/test/kotlin/com/explyt/spring/core/properties/kotlin/ValueConfigurationPropertyReferenceProviderTest.kt diff --git a/modules/spring-core/src/main/kotlin/com/explyt/spring/core/properties/providers/ValueConfigurationPropertyReferenceProvider.kt b/modules/spring-core/src/main/kotlin/com/explyt/spring/core/properties/providers/ValueConfigurationPropertyReferenceProvider.kt index a7dda0f21..80cc88378 100644 --- a/modules/spring-core/src/main/kotlin/com/explyt/spring/core/properties/providers/ValueConfigurationPropertyReferenceProvider.kt +++ b/modules/spring-core/src/main/kotlin/com/explyt/spring/core/properties/providers/ValueConfigurationPropertyReferenceProvider.kt @@ -58,9 +58,14 @@ class ValueConfigurationPropertyReferenceProvider : UastInjectionHostReferencePr } return referenceProperties - .map { referenceProperty -> + .mapNotNull { referenceProperty -> val text = host.text.removeDummyIdentifier() val startOffset = text.indexOf(referenceProperty.key) + // The key is extracted from the evaluated @Value string, which may not appear + // literally in the raw host text (e.g. a template referencing a constant). + // If it can't be located, skip it instead of building an invalid TextRange + // such as (-1, n), which throws IllegalArgumentException (issue #236). + if (startOffset < 0) return@mapNotNull null ExplytPropertyReference( host, referenceProperty.key, diff --git a/modules/spring-core/src/test/kotlin/com/explyt/spring/core/properties/kotlin/ValueConfigurationPropertyReferenceProviderTest.kt b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/properties/kotlin/ValueConfigurationPropertyReferenceProviderTest.kt new file mode 100644 index 000000000..a7ea1b358 --- /dev/null +++ b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/properties/kotlin/ValueConfigurationPropertyReferenceProviderTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright © 2024 Explyt Ltd + * + * All rights reserved. + * + * This code and software are the property of Explyt Ltd and are protected by copyright and other intellectual property laws. + * + * You may use this code under the terms of the Explyt Source License Version 1.0 ("License"), if you accept its terms and conditions. + * + * By installing, downloading, accessing, using, or distributing this code, you agree to the terms and conditions of the License. + * If you do not agree to such terms and conditions, you must cease using this code and immediately delete all copies of it. + * + * You may obtain a copy of the License at: https://github.com/explyt/spring-plugin/blob/main/EXPLYT-SOURCE-LICENSE.md + * + * Unauthorized use of this code constitutes a violation of intellectual property rights and may result in legal action. + */ + +package com.explyt.spring.core.properties.kotlin + +import com.explyt.spring.test.ExplytKotlinLightTestCase +import com.explyt.spring.test.TestLibrary + +class ValueConfigurationPropertyReferenceProviderTest : ExplytKotlinLightTestCase() { + + override val libraries: Array = arrayOf(TestLibrary.springContext_6_0_7) + + /** + * Regression for issue #236. + * + * The placeholder key is taken from a `const`, so the evaluated `@Value` string + * (`${'$'}{server.timing.minutes-to-next-claim}`) does not appear literally in the host + * text (`"${'$'}{${'$'}KEY}"`). The provider used to compute `indexOf(...) == -1` and then + * build `TextRange.from(-1, length)`, throwing + * `IllegalArgumentException: Invalid range specified: (-1, n)`. It must now skip the + * unlocatable key instead of crashing. + */ + fun testValueReferencingConstantKeyDoesNotThrow() { + myFixture.configureByText( + "TestComponent.kt", + """ + import org.springframework.beans.factory.annotation.Value + + const val KEY = "server.timing.minutes-to-next-claim" + + class TestComponent { + @Value("${'$'}{${'$'}KEY}") + private val injected: String = "" + } + """.trimIndent() + ) + + // Before the fix this threw IllegalArgumentException while computing references. + myFixture.doHighlighting() + } + + /** Happy path: a literal placeholder still yields a property reference. */ + fun testValueWithLiteralPlaceholderResolvesReference() { + myFixture.configureByText( + "TestComponent.kt", + """ + import org.springframework.beans.factory.annotation.Value + + class TestComponent { + @Value("${'$'}{my.property}") + private val injected: String = "" + } + """.trimIndent() + ) + + val ref = file.findReferenceAt(myFixture.caretOffset) + assertNotNull("Expected a property reference for the literal placeholder", ref) + } +}