[http-client, microservices-jvm] IJPL-179625 The URL part of MicroservicesJvm was moved to Java

Because HttpClient will be in Android Studio. It needs access to this API to have implementations of HTTP clients (Ktor, Retrofit, OkHttp) in Android Studio.

GitOrigin-RevId: 30771b08ec0a1642ca010cde8513703abc0f7b0f
This commit is contained in:
Marat Dinmukhametov
2025-03-27 19:15:34 +02:00
committed by intellij-monorepo-bot
parent 6a2baf2c5a
commit f3df8ba421
12 changed files with 350 additions and 0 deletions

View File

@@ -33,5 +33,6 @@
<orderEntry type="module" module-name="intellij.platform.editor.ex" />
<orderEntry type="module" module-name="intellij.java.codeserver.highlighting" />
<orderEntry type="module" module-name="intellij.java.codeserver.core" />
<orderEntry type="module" module-name="intellij.platform.refactoring" />
</component>
</module>

View File

@@ -353,5 +353,17 @@
<optionController implementation="com.intellij.codeInsight.NullableNotNullManagerImpl$Provider"/>
<lang.jvm.annotations.marker.suppressor implementation="com.intellij.java.JavaNonCodeAnnotationsMarkerSuppressor"/>
<!--Microservices Url-->
<microservices.urlInlayLanguagesProvider implementation="com.intellij.microservices.jvm.url.UastUrlPathInlayLanguagesProvider"/>
<completion.contributor language="UAST"
implementationClass="com.intellij.microservices.jvm.url.RenameableSemElementCompletionContributor"/>
<nameSuggestionProvider implementation="com.intellij.microservices.jvm.url.RenameableSemElementNameSuggestionProvider"/>
<referenceInjector implementation="com.intellij.microservices.jvm.url.HttpUrlReferenceInjector"/>
<referenceInjector implementation="com.intellij.microservices.jvm.url.WSUrlReferenceInjector"/>
<referenceInjector implementation="com.intellij.microservices.jvm.url.HttpMethodReferenceInjector"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,3 @@
inject.http.url.reference=HTTP URL Reference
inject.http.ws.reference=WebSocket URL Reference
inject.http.method.reference=HTTP Method Reference

View File

@@ -0,0 +1,24 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.microservices.jvm.url
import com.intellij.icons.AllIcons
import com.intellij.microservices.http.HttpMethodReference
import com.intellij.openapi.util.TextRange
import com.intellij.psi.ElementManipulators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.injection.ReferenceInjector
import com.intellij.util.ProcessingContext
import javax.swing.Icon
internal class HttpMethodReferenceInjector : ReferenceInjector() {
override fun getId(): String = "http-method-reference"
override fun getDisplayName(): String = MicroservicesJvmUrlBundle.message("inject.http.method.reference")
override fun getIcon(): Icon = AllIcons.Nodes.PpWeb
override fun getReferences(element: PsiElement, context: ProcessingContext, range: TextRange): Array<PsiReference> {
return arrayOf(HttpMethodReference(element, ElementManipulators.getValueTextRange(element)))
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.microservices.jvm.url
import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.PropertyKey
import java.util.function.Supplier
@NonNls
private const val BUNDLE = "messages.MicroservicesJvmUrlBundle"
internal object MicroservicesJvmUrlBundle {
private val instance = DynamicBundle(MicroservicesJvmUrlBundle::class.java, BUNDLE)
fun message(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg params: Any ): @Nls String {
return instance.getMessage(key, *params)
}
fun lazyMessage(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): Supplier<@Nls String> {
return instance.getLazyMessage(key, *params)
}
}

View File

@@ -0,0 +1,34 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.microservices.jvm.url
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.microservices.url.parameters.RenameableSemElement
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.PsiElement
import com.intellij.psi.util.parents
import com.intellij.semantic.SemService
import com.intellij.util.Plow
import com.intellij.util.ProcessingContext
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
class RenameableSemElementCompletionContributor : CompletionContributor() {
init {
extend(CompletionType.BASIC, PlatformPatterns.psiElement(), object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
val position = parameters.position
getParameterNameVariants(position)
?.processWith { e -> result.addElement(e); !result.isStopped }
}
})
}
}
internal fun getParameterNameVariants(position: PsiElement): Plow<LookupElement>? {
val semService = SemService.getSemService(position.project)
return position.parents(true).take(2)
.mapNotNull { semService.getSemElement(RenameableSemElement.RENAMEABLE_SEM_KEY, it) }
.firstOrNull()
?.nameVariants
}

View File

@@ -0,0 +1,13 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.microservices.jvm.url
import com.intellij.psi.PsiElement
import com.intellij.psi.codeStyle.SuggestedNameInfo
import com.intellij.refactoring.rename.NameSuggestionProvider
internal class RenameableSemElementNameSuggestionProvider : NameSuggestionProvider {
override fun getSuggestedNames(element: PsiElement, nameSuggestionContext: PsiElement?, result: MutableSet<String>): SuggestedNameInfo? {
getParameterNameVariants(element)?.map { it.lookupString }?.collectTo(result)
return null
}
}

View File

@@ -0,0 +1,65 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:JvmName("UastReferenceInjectorUtils")
package com.intellij.microservices.jvm.url
import com.intellij.microservices.url.references.UrlPathReferenceInjector
import com.intellij.microservices.url.references.UrlPksParser
import com.intellij.microservices.url.references.UrlSegmentReferenceTarget
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.UastReferenceProvider
import com.intellij.psi.getRequestedPsiElement
import com.intellij.util.ProcessingContext
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.uast.*
import org.jetbrains.uast.expressions.UStringConcatenationsFacade
@JvmOverloads
fun uastUrlPathReferenceInjectorForScheme(schemes: List<String>,
parser: UrlPksParser = UrlPksParser()): UrlPathReferenceInjector<UExpression> =
UrlPathReferenceInjector.forPartialStringFrom<UExpression>(parser) { uElement ->
val context = getContextExpression(uElement) ?: return@forPartialStringFrom null
UStringConcatenationsFacade.createFromUExpression(context)?.asPartiallyKnownString()
}.withSchemesSupport(schemes)
@ApiStatus.Internal
fun getContextExpression(uExpression: UExpression): UExpression? =
uExpression.withContainingElements.take(2).firstOrNull { child ->
child.uastParent.let {
it is UCallExpression
|| it is UNamedExpression
|| it is UVariable
|| it is UBinaryExpression && it.operator == UastBinaryOperator.ASSIGN
|| (it is UQualifiedReferenceExpression && it.selector is UCallExpression)
}
} as? UExpression
fun uastUrlReferenceProvider(schemes: List<String>): UastReferenceProvider {
return uastUrlReferenceProvider(
uastUrlPathReferenceInjectorForScheme(schemes))
}
fun uastUrlReferenceProvider(injector: UrlPathReferenceInjector<UExpression>): UastReferenceProvider {
return UastUrlPathReferenceProvider { uExpression, psiElement ->
injector.buildReferences(uExpression).forPsiElement(psiElement)
}
}
/**
* This provider implements performance-optimisation in order to not contribute UrlPath references when the target element is known and
* it is not UrlSegmentReferenceTarget.
*/
class UastUrlPathReferenceProvider(val provider: (UExpression, PsiElement) -> Array<PsiReference>)
: UastReferenceProvider(UExpression::class.java) {
override fun getReferencesByElement(element: UElement, context: ProcessingContext): Array<PsiReference> {
return provider(UExpression::class.java.cast(element), getRequestedPsiElement(context))
}
override fun acceptsTarget(target: PsiElement): Boolean {
return target is UrlSegmentReferenceTarget
}
override fun toString(): String = "UastUrlPathReferenceProvider($provider)"
}

View File

@@ -0,0 +1,78 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.microservices.jvm.url
import com.intellij.lang.Language
import com.intellij.microservices.url.inlay.UrlPathInlayLanguagesProvider
import com.intellij.psi.PsiElement
import com.intellij.uast.UastMetaLanguage
import org.jetbrains.uast.*
import org.jetbrains.uast.UastFacade.getPossiblePsiSourceTypes
private const val SEARCH_LIMIT = 3
internal class UastUrlPathInlayLanguagesProvider : UrlPathInlayLanguagesProvider {
private val jamPsiSourceTypes = getPossiblePsiSourceTypes(UClass::class.java, UMethod::class.java)
private val blockedUExpressionTypes: Collection<Class<*>> = listOf(
UQualifiedReferenceExpression::class.java,
UClassLiteralExpression::class.java,
ULambdaExpression::class.java,
UJumpExpression::class.java,
UIfExpression::class.java,
ULoopExpression::class.java,
UTypeReferenceExpression::class.java
)
override val languages: Collection<Language>
get() = Language.findInstance(UastMetaLanguage::class.java).matchingLanguages
override fun getPotentialElementsWithHintsProviders(element: PsiElement): List<PsiElement> {
// for JAM
if (jamPsiSourceTypes.contains(element.javaClass)) { // do not try to convert every element in big files
val uDeclaration = element.toUElementOfExpectedTypes(UClass::class.java, UMethod::class.java)
if (uDeclaration != null) {
if (uDeclaration.sourcePsi != element) return emptyList()
val javaPsiElement = uDeclaration.javaPsi
if (element == javaPsiElement) {
return listOf(element)
}
return listOfNotNull(element, javaPsiElement)
}
}
val uExpression = element.toUElementOfType<UExpression>() ?: return emptyList()
if (blockedUExpressionTypes.any { it.isAssignableFrom(uExpression.javaClass) }) {
// performance optimization: do not query SEM for `receiver.selector()`, `() -> {}`, control flow expressions
return emptyList()
}
// currently arguments of call expressions and values of fields are supported
if (hasFieldOrExpressionParent(uExpression)) {
val uastParent = uExpression.uastParent
if (uastParent is UTypeReferenceExpression
|| uastParent is UCallableReferenceExpression
|| uExpression is ULiteralExpression && (uExpression.isBoolean || uExpression.isNull)) {
// no URLs inlay in types, booleans and nulls
return emptyList()
}
return listOf(element)
}
return emptyList()
}
private fun hasFieldOrExpressionParent(uExpression: UExpression): Boolean {
if (uExpression.getUCallExpression(searchLimit = SEARCH_LIMIT) != null) return true
var u: UElement = uExpression
repeat(SEARCH_LIMIT) {
if (u is UField) return true
u = u.uastParent ?: return false
}
return false
}
}

View File

@@ -0,0 +1,60 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.microservices.jvm.url
import com.intellij.icons.AllIcons
import com.intellij.microservices.url.HTTP_SCHEMES
import com.intellij.microservices.url.WS_SCHEMES
import com.intellij.microservices.url.references.UrlPathReferenceInjector
import com.intellij.openapi.util.TextRange
import com.intellij.psi.ElementManipulators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.injection.ReferenceInjector
import com.intellij.psi.util.PartiallyKnownString
import com.intellij.util.ProcessingContext
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.expressions.UStringConcatenationsFacade
import org.jetbrains.uast.toUElementOfType
import javax.swing.Icon
internal abstract class UrlPathReferenceContextInjector : ReferenceInjector() {
abstract val schemes: List<String>
protected val injector by lazy {
UrlPathReferenceInjector.forPartialStringFrom<PsiElement> { host ->
host.toUElementOfType<UExpression>()?.let { uExpression ->
return@forPartialStringFrom buildUastPartialString(uExpression)
}
PartiallyKnownString(ElementManipulators.getValueText(host), host, ElementManipulators.getValueTextRange(host))
}.withSchemesSupport(schemes)
}
private fun buildUastPartialString(uExpression: UExpression): PartiallyKnownString? {
val uContext = getContextExpression(uExpression) ?: uExpression
return UStringConcatenationsFacade.createFromUExpression(uContext)?.asPartiallyKnownString()
}
override fun getReferences(host: PsiElement, context: ProcessingContext, range: TextRange): Array<PsiReference> =
injector.buildAbsoluteOrRelativeReferences(host, host)
}
internal class HttpUrlReferenceInjector : UrlPathReferenceContextInjector() {
override fun getId(): String = "http-url-reference"
override fun getDisplayName(): String = MicroservicesJvmUrlBundle.message("inject.http.url.reference")
override fun getIcon(): Icon = AllIcons.Nodes.PpWeb
override val schemes: List<String> get() = HTTP_SCHEMES
}
internal class WSUrlReferenceInjector : UrlPathReferenceContextInjector() {
override fun getId(): String = "ws-reference"
override fun getDisplayName(): String = MicroservicesJvmUrlBundle.message("inject.http.ws.reference")
override val schemes: List<String> get() = WS_SCHEMES
override fun getIcon(): Icon = AllIcons.Webreferences.WebSocket
}

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Experimental
package com.intellij.microservices.jvm.url;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -0,0 +1,32 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:JvmName("UastUrlInlayHintsUtils")
@file:ApiStatus.Experimental
package com.intellij.microservices.jvm.url
import com.intellij.microservices.url.inlay.PsiElementUrlPathInlayHint
import com.intellij.microservices.url.inlay.UrlPathInlayHint
import com.intellij.microservices.url.inlay.UrlPathInlayHintsProviderSemElement
import com.intellij.microservices.url.references.UrlPathReferenceInjector
import com.intellij.microservices.url.references.forbidExpensiveUrlContext
import com.intellij.psi.UastSemProvider
import com.intellij.psi.uastSemElementProvider
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UReferenceExpression
import org.jetbrains.uast.expressions.UInjectionHost
fun urlInlayHintProvider(injector: UrlPathReferenceInjector<UExpression>): UastSemProvider<UrlPathInlayHintsProviderSemElement> {
return uastSemElementProvider(listOf(UInjectionHost::class.java, UReferenceExpression::class.java)) { uExpression, _ ->
val context = forbidExpensiveUrlContext {
val rootContext = injector.defaultRootContextProvider(uExpression)
if (!injector.ignoreSubPathContext) rootContext.subContext(injector.toUrlPath(uExpression))
else rootContext
}
object : UrlPathInlayHintsProviderSemElement {
override val inlayHints: List<UrlPathInlayHint>
get() = listOf(PsiElementUrlPathInlayHint(uExpression.sourcePsi!!, context))
}
}
}