PartiallyKnownString decoupled for UAST and moved to *lang-api*, *microservices* no more depends on UAST

GitOrigin-RevId: 4175fab68bed81acac8f84940450c15ff9a8a059
This commit is contained in:
Nicolay Mitropolsky
2019-09-12 14:53:57 +03:00
committed by intellij-monorepo-bot
parent 3b608e17af
commit 4aff9c7c9e
3 changed files with 184 additions and 161 deletions

View File

@@ -0,0 +1,179 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.util
import com.intellij.openapi.util.TextRange
import com.intellij.psi.ElementManipulators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiLanguageInjectionHost
import com.intellij.util.SmartList
import com.intellij.util.containers.toHeadAndTail
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
sealed class StringEntry {
abstract val sourcePsi: PsiElement? // maybe it should be PsiLanguageInjectionHost and only for `Known` values
abstract val range: TextRange
class Known(val value: String, override val sourcePsi: PsiElement?, override val range: TextRange) : StringEntry()
class Unknown(override val sourcePsi: PsiElement?, override val range: TextRange) : StringEntry()
val rangeAlignedToHost: TextRange?
get() {
val entry = this
val sourcePsi = entry.sourcePsi ?: return null
if (sourcePsi is PsiLanguageInjectionHost) return entry.range
if (sourcePsi.parent is PsiLanguageInjectionHost) { // Kotlin interpolated string, TODO: encapsulate this logic to range retrieval
return entry.range.shiftRight(sourcePsi.startOffsetInParent - ElementManipulators.getValueTextRange(sourcePsi.parent).startOffset)
}
return null
}
}
@ApiStatus.Experimental
class PartiallyKnownString(val segments: List<StringEntry>) {
val valueIfKnown: String?
get() {
(segments.singleOrNull() as? StringEntry.Known)?.let { return it.value }
val stringBuffer = StringBuffer()
for (segment in segments) {
when (segment) {
is StringEntry.Known -> stringBuffer.append(segment.value)
is StringEntry.Unknown -> return null
}
}
return stringBuffer.toString()
}
override fun toString(): String = segments.joinToString { segment ->
when (segment) {
is StringEntry.Known -> segment.value
is StringEntry.Unknown -> "<???>"
}
}
constructor(single: StringEntry) : this(listOf(single))
constructor(string: String, sourcePsi: PsiElement?, textRange: TextRange) : this(
StringEntry.Known(string, sourcePsi, textRange))
fun findIndexOfInKnown(pattern: String): Int {
var accumulated = 0
for (segment in segments) {
when (segment) {
is StringEntry.Known -> {
val i = segment.value.indexOf(pattern)
if (i >= 0) return accumulated + i
accumulated += segment.value.length
}
is StringEntry.Unknown -> {
}
}
}
return -1
}
fun splitAtInKnown(splitAt: Int): Pair<PartiallyKnownString, PartiallyKnownString> {
var accumulated = 0
val left = SmartList<StringEntry>()
for ((i, segment) in segments.withIndex()) {
when (segment) {
is StringEntry.Known -> {
if (accumulated + segment.value.length < splitAt) {
accumulated += segment.value.length
left.add(segment)
}
else {
val leftPart = segment.value.substring(0, splitAt - accumulated)
val rightPart = segment.value.substring(splitAt - accumulated)
left.add(StringEntry.Known(leftPart, segment.sourcePsi, /* TODO: should also be splitted */
segment.range))
return PartiallyKnownString(left) to PartiallyKnownString(
ArrayList<StringEntry>(segments.lastIndex - i + 1).apply {
if (rightPart.isNotEmpty())
add(StringEntry.Known(rightPart, segment.sourcePsi, /* TODO: should also be splitted */
segment.range))
addAll(segments.subList(i, segments.size))
}
)
}
}
is StringEntry.Unknown -> {
left.add(segment)
}
}
}
return this to empty
}
fun split(pattern: String): List<PartiallyKnownString> {
tailrec fun collectPaths(result: MutableList<PartiallyKnownString>,
pending: MutableList<StringEntry>,
segments: List<StringEntry>): MutableList<PartiallyKnownString> {
val (head, tail) = segments.toHeadAndTail() ?: return result.apply {
add(
PartiallyKnownString(pending))
}
when (head) {
is StringEntry.Unknown -> return collectPaths(result, pending.apply { add(head) }, tail)
is StringEntry.Known -> {
val value = head.value
val stringPaths = splitToTextRanges(value, pattern).toList()
if (stringPaths.size == 1) {
return collectPaths(result, pending.apply { add(head) }, tail)
}
else {
return collectPaths(
result.apply {
add(PartiallyKnownString(
pending.apply {
add(StringEntry.Known(stringPaths.first().substring(value), head.sourcePsi,
stringPaths.first()))
}))
addAll(stringPaths.subList(1, stringPaths.size - 1).map {
PartiallyKnownString(it.substring(value), head.sourcePsi, it)
})
},
mutableListOf(StringEntry.Known(stringPaths.last().substring(value), head.sourcePsi,
stringPaths.last())),
tail
)
}
}
}
}
return collectPaths(SmartList(), mutableListOf(), segments)
}
companion object {
val empty = PartiallyKnownString(emptyList())
}
}
@ApiStatus.Experimental
fun splitToTextRanges(charSequence: CharSequence, pattern: String): Sequence<TextRange> {
var lastMatch = 0
return sequence {
while (true) {
val start = charSequence.indexOf(pattern, lastMatch)
if (start == -1) {
yield(TextRange(lastMatch, charSequence.length))
return@sequence
}
yield(TextRange(lastMatch, start))
lastMatch = start + pattern.length
}
}
}

View File

@@ -11,5 +11,6 @@
<orderEntry type="module" module-name="intellij.java.psi" exported="" />
<orderEntry type="module" module-name="intellij.java.psi.impl" />
<orderEntry type="module" module-name="intellij.platform.util.ex" />
<orderEntry type="module" module-name="intellij.platform.lang" />
</component>
</module>

View File

@@ -5,8 +5,8 @@ import com.intellij.openapi.util.TextRange
import com.intellij.psi.ElementManipulators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiLanguageInjectionHost
import com.intellij.util.SmartList
import com.intellij.util.containers.toHeadAndTail
import com.intellij.psi.util.PartiallyKnownString
import com.intellij.psi.util.StringEntry
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.uast.*
@@ -82,8 +82,8 @@ class UStringConcatenationsFacade @ApiStatus.Experimental constructor(uContext:
@ApiStatus.Experimental
fun asPartiallyKnownString() = PartiallyKnownString(segments.map { segment ->
segment.value?.let { value ->
StringEntry.Known(value, segment.uExpression, getSegmentInnerTextRange(segment))
} ?: StringEntry.Unknown(segment.uExpression, getSegmentInnerTextRange(segment))
StringEntry.Known(value, segment.uExpression.sourcePsi, getSegmentInnerTextRange(segment))
} ?: StringEntry.Unknown(segment.uExpression.sourcePsi, getSegmentInnerTextRange(segment))
})
private fun getSegmentInnerTextRange(segment: Segment): TextRange {
@@ -108,160 +108,3 @@ class UStringConcatenationsFacade @ApiStatus.Experimental constructor(uContext:
}
@ApiStatus.Experimental
sealed class StringEntry {
abstract val uExpression: UExpression
abstract val range: TextRange
class Known(val value: String, override val uExpression: UExpression, override val range: TextRange) : StringEntry()
class Unknown(override val uExpression: UExpression, override val range: TextRange) : StringEntry()
val rangeAlignedToHost: TextRange?
get() {
val entry = this
val sourcePsi = entry.uExpression.sourcePsi ?: return null
if (sourcePsi is PsiLanguageInjectionHost) return entry.range
if (sourcePsi.parent is PsiLanguageInjectionHost) { // Kotlin interpolated string, TODO: encapsulate this logic to range retrieval
return entry.range.shiftRight(sourcePsi.startOffsetInParent - ElementManipulators.getValueTextRange(sourcePsi.parent).startOffset)
}
return null
}
}
@ApiStatus.Experimental
class PartiallyKnownString(val segments: List<StringEntry>) {
val valueIfKnown: String?
get() {
(segments.singleOrNull() as? StringEntry.Known)?.let { return it.value }
val stringBuffer = StringBuffer()
for (segment in segments) {
when (segment) {
is StringEntry.Known -> stringBuffer.append(segment.value)
is StringEntry.Unknown -> return null
}
}
return stringBuffer.toString()
}
override fun toString(): String = segments.joinToString { segment ->
when (segment) {
is StringEntry.Known -> segment.value
is StringEntry.Unknown -> "<???>"
}
}
constructor(single: StringEntry) : this(listOf(single))
constructor(string: String, uExpression: UExpression, textRange: TextRange) : this(StringEntry.Known(string, uExpression, textRange))
fun findIndexOfInKnown(pattern: String): Int {
var accumulated = 0
for (segment in segments) {
when (segment) {
is StringEntry.Known -> {
val i = segment.value.indexOf(pattern)
if (i >= 0) return accumulated + i
accumulated += segment.value.length
}
is StringEntry.Unknown -> {
}
}
}
return -1
}
fun splitAtInKnown(splitAt: Int): Pair<PartiallyKnownString, PartiallyKnownString> {
var accumulated = 0
val left = SmartList<StringEntry>()
for ((i, segment) in segments.withIndex()) {
when (segment) {
is StringEntry.Known -> {
if (accumulated + segment.value.length < splitAt) {
accumulated += segment.value.length
left.add(segment)
}
else {
val leftPart = segment.value.substring(0, splitAt - accumulated)
val rightPart = segment.value.substring(splitAt - accumulated)
left.add(StringEntry.Known(leftPart, segment.uExpression, /* TODO: should also be splitted */ segment.range))
return PartiallyKnownString(left) to PartiallyKnownString(
ArrayList<StringEntry>(segments.lastIndex - i + 1).apply {
if (rightPart.isNotEmpty())
add(StringEntry.Known(rightPart, segment.uExpression, /* TODO: should also be splitted */ segment.range))
addAll(segments.subList(i, segments.size))
}
)
}
}
is StringEntry.Unknown -> {
left.add(segment)
}
}
}
return this to PartiallyKnownString.empty
}
fun split(pattern: String): List<PartiallyKnownString> {
tailrec fun collectPaths(result: MutableList<PartiallyKnownString>,
pending: MutableList<StringEntry>,
segments: List<StringEntry>): MutableList<PartiallyKnownString> {
val (head, tail) = segments.toHeadAndTail() ?: return result.apply { add(PartiallyKnownString(pending)) }
when (head) {
is StringEntry.Unknown -> return collectPaths(result, pending.apply { add(head) }, tail)
is StringEntry.Known -> {
val value = head.value
val stringPaths = splitToTextRanges(value, pattern).toList()
if (stringPaths.size == 1) {
return collectPaths(result, pending.apply { add(head) }, tail)
}
else {
return collectPaths(
result.apply {
add(PartiallyKnownString(
pending.apply { add(StringEntry.Known(stringPaths.first().substring(value), head.uExpression, stringPaths.first())) }))
addAll(stringPaths.subList(1, stringPaths.size - 1).map { PartiallyKnownString(it.substring(value), head.uExpression, it) })
},
mutableListOf(StringEntry.Known(stringPaths.last().substring(value), head.uExpression, stringPaths.last())),
tail
)
}
}
}
}
return collectPaths(SmartList(), mutableListOf(), segments)
}
companion object {
val empty = PartiallyKnownString(emptyList())
}
}
@ApiStatus.Experimental
fun splitToTextRanges(charSequence: CharSequence, pattern: String): Sequence<TextRange> {
var lastMatch = 0
return sequence {
while (true) {
val start = charSequence.indexOf(pattern, lastMatch)
if (start == -1) {
yield(TextRange(lastMatch, charSequence.length))
return@sequence
}
yield(TextRange(lastMatch, start))
lastMatch = start + pattern.length
}
}
}