FIR: extract common logic from KotlinNameSuggester and share
This commit is contained in:
committed by
Ilya Kirillov
parent
00ffa69cb3
commit
0eaab6d8a2
@@ -19,12 +19,9 @@ package org.jetbrains.kotlin.idea.core
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||
import org.jetbrains.kotlin.lexer.KotlinLexer
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtValueArgument
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getOutermostParenthesizerOrThis
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
|
||||
@@ -33,12 +30,8 @@ import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeAsciiOnly
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart
|
||||
import java.util.*
|
||||
|
||||
object KotlinNameSuggester {
|
||||
object KotlinNameSuggester : AbstractNameSuggester() {
|
||||
fun suggestNamesByExpressionAndType(
|
||||
expression: KtExpression,
|
||||
type: KotlinType?,
|
||||
@@ -110,99 +103,6 @@ object KotlinNameSuggester {
|
||||
return result
|
||||
}
|
||||
|
||||
fun suggestNamesByFqName(
|
||||
fqName: FqName,
|
||||
ignoreCompanion: Boolean = true,
|
||||
validator: (String) -> Boolean = { true },
|
||||
defaultName: () -> String? = { null }
|
||||
): Collection<String> {
|
||||
val result = LinkedHashSet<String>()
|
||||
|
||||
var name = ""
|
||||
fqName.asString().split('.').asReversed().forEach {
|
||||
if (ignoreCompanion && it == "Companion") return@forEach
|
||||
name = name.withPrefix(it)
|
||||
result.addName(name, validator)
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
result.addName(defaultName(), validator)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun String.withPrefix(prefix: String): String {
|
||||
if (isEmpty()) return prefix
|
||||
val c = this[0]
|
||||
return (if (c in 'a'..'z') prefix.decapitalizeAsciiOnly()
|
||||
else prefix.capitalizeAsciiOnly()) + capitalizeAsciiOnly()
|
||||
}
|
||||
|
||||
private val COMMON_TYPE_PARAMETER_NAMES = listOf("T", "U", "V", "W", "X", "Y", "Z")
|
||||
private const val MAX_NUMBER_OF_SUGGESTED_NAME_CHECKS = 1000
|
||||
|
||||
fun suggestNamesForTypeParameters(count: Int, validator: (String) -> Boolean): List<String> {
|
||||
val result = ArrayList<String>()
|
||||
for (i in 0 until count) {
|
||||
result.add(suggestNameByMultipleNames(COMMON_TYPE_PARAMETER_NAMES, validator))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun suggestTypeAliasNameByPsi(typeElement: KtTypeElement, validator: (String) -> Boolean): String {
|
||||
fun KtTypeElement.render(): String {
|
||||
return when (this) {
|
||||
is KtNullableType -> "Nullable${innerType?.render() ?: ""}"
|
||||
is KtFunctionType -> {
|
||||
val arguments = listOfNotNull(receiverTypeReference) + parameters.mapNotNull { it.typeReference }
|
||||
val argText = arguments.joinToString(separator = "") { it.typeElement?.render() ?: "" }
|
||||
val returnText = returnTypeReference?.typeElement?.render() ?: "Unit"
|
||||
"${argText}To$returnText"
|
||||
}
|
||||
is KtUserType -> {
|
||||
val argText = typeArguments.joinToString(separator = "") { it.typeReference?.typeElement?.render() ?: "" }
|
||||
"$argText${referenceExpression?.text ?: ""}"
|
||||
}
|
||||
else -> text.capitalizeAsciiOnly()
|
||||
}
|
||||
}
|
||||
|
||||
return suggestNameByName(typeElement.render(), validator)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates name, and slightly improves it by adding number to name in case of conflicts
|
||||
* @param name to check it in scope
|
||||
* @return name or nameI, where I is number
|
||||
*/
|
||||
fun suggestNameByName(name: String, validator: (String) -> Boolean): String {
|
||||
if (validator(name)) return name
|
||||
var i = 1
|
||||
while (i <= MAX_NUMBER_OF_SUGGESTED_NAME_CHECKS && !validator(name + i)) {
|
||||
++i
|
||||
}
|
||||
|
||||
return name + i
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates name using set of variants which are tried in succession (and extended with suffixes if necessary)
|
||||
* For example, when given sequence of a, b, c possible names are tried out in the following order: a, b, c, a1, b1, c1, a2, b2, c2, ...
|
||||
* @param names to check it in scope
|
||||
* @return name or nameI, where name is one of variants and I is a number
|
||||
*/
|
||||
fun suggestNameByMultipleNames(names: Collection<String>, validator: (String) -> Boolean): String {
|
||||
var i = 0
|
||||
while (true) {
|
||||
for (name in names) {
|
||||
val candidate = if (i > 0) name + i else name
|
||||
if (validator(candidate)) return candidate
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableCollection<String>.addNamesByType(type: KotlinType, validator: (String) -> Boolean) {
|
||||
val myType = TypeUtils.makeNotNullable(type) // wipe out '?'
|
||||
val builtIns = myType.builtIns
|
||||
@@ -253,69 +153,6 @@ object KotlinNameSuggester {
|
||||
}
|
||||
}
|
||||
|
||||
private val ACCESSOR_PREFIXES = arrayOf("get", "is", "set")
|
||||
|
||||
fun getCamelNames(name: String, validator: (String) -> Boolean, startLowerCase: Boolean): List<String> {
|
||||
val result = ArrayList<String>()
|
||||
result.addCamelNames(name, validator, startLowerCase)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun MutableCollection<String>.addCamelNames(name: String, validator: (String) -> Boolean, startLowerCase: Boolean = true) {
|
||||
if (name === "" || !name.unquote().isIdentifier()) return
|
||||
var s = extractIdentifiers(name)
|
||||
|
||||
for (prefix in ACCESSOR_PREFIXES) {
|
||||
if (!s.startsWith(prefix)) continue
|
||||
|
||||
val len = prefix.length
|
||||
if (len < s.length && Character.isUpperCase(s[len])) {
|
||||
s = s.substring(len)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var upperCaseLetterBefore = false
|
||||
for (i in 0 until s.length) {
|
||||
val c = s[i]
|
||||
val upperCaseLetter = Character.isUpperCase(c)
|
||||
|
||||
if (i == 0) {
|
||||
addName(if (startLowerCase) s.decapitalizeSmart() else s, validator)
|
||||
} else {
|
||||
if (upperCaseLetter && !upperCaseLetterBefore) {
|
||||
val substring = s.substring(i)
|
||||
addName(if (startLowerCase) substring.decapitalizeSmart() else substring, validator)
|
||||
}
|
||||
}
|
||||
|
||||
upperCaseLetterBefore = upperCaseLetter
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractIdentifiers(s: String): String {
|
||||
return buildString {
|
||||
val lexer = KotlinLexer()
|
||||
lexer.start(s)
|
||||
while (lexer.tokenType != null) {
|
||||
if (lexer.tokenType == KtTokens.IDENTIFIER) {
|
||||
append(lexer.tokenText)
|
||||
}
|
||||
lexer.advance()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableCollection<String>.addNamesByExpressionPSI(expression: KtExpression?, validator: (String) -> Boolean) {
|
||||
if (expression == null) return
|
||||
when (val deparenthesized = KtPsiUtil.safeDeparenthesize(expression)) {
|
||||
is KtSimpleNameExpression -> addCamelNames(deparenthesized.getReferencedName(), validator)
|
||||
is KtQualifiedExpression -> addNamesByExpressionPSI(deparenthesized.selectorExpression, validator)
|
||||
is KtCallExpression -> addNamesByExpressionPSI(deparenthesized.calleeExpression, validator)
|
||||
is KtPostfixExpression -> addNamesByExpressionPSI(deparenthesized.baseExpression, validator)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableCollection<String>.addNamesByExpression(
|
||||
expression: KtExpression?,
|
||||
bindingContext: BindingContext?,
|
||||
@@ -343,13 +180,4 @@ object KotlinNameSuggester {
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableCollection<String>.addName(name: String?, validator: (String) -> Boolean) {
|
||||
if (name == null) return
|
||||
val correctedName = when {
|
||||
name.isIdentifier() -> name
|
||||
name == "class" -> "clazz"
|
||||
else -> return
|
||||
}
|
||||
add(suggestNameByName(correctedName, validator))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.core
|
||||
|
||||
object FirKotlinNameSuggester : AbstractNameSuggester()
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.core
|
||||
|
||||
import org.jetbrains.kotlin.lexer.KotlinLexer
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeAsciiOnly
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart
|
||||
|
||||
abstract class AbstractNameSuggester {
|
||||
fun suggestNamesByFqName(
|
||||
fqName: FqName,
|
||||
ignoreCompanion: Boolean = true,
|
||||
validator: (String) -> Boolean = { true },
|
||||
defaultName: () -> String? = { null }
|
||||
): Collection<String> {
|
||||
val result = LinkedHashSet<String>()
|
||||
|
||||
var name = ""
|
||||
fqName.asString().split('.').asReversed().forEach {
|
||||
if (ignoreCompanion && it == "Companion") return@forEach
|
||||
name = name.withPrefix(it)
|
||||
result.addName(name, validator)
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
result.addName(defaultName(), validator)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates name, and slightly improves it by adding number to name in case of conflicts
|
||||
* @param name to check it in scope
|
||||
* @return name or nameI, where I is number
|
||||
*/
|
||||
fun suggestNameByName(name: String, validator: (String) -> Boolean): String {
|
||||
if (validator(name)) return name
|
||||
var i = 1
|
||||
while (i <= MAX_NUMBER_OF_SUGGESTED_NAME_CHECKS && !validator(name + i)) {
|
||||
++i
|
||||
}
|
||||
|
||||
return name + i
|
||||
}
|
||||
|
||||
|
||||
fun suggestNamesForTypeParameters(count: Int, validator: (String) -> Boolean): List<String> {
|
||||
val result = ArrayList<String>()
|
||||
for (i in 0 until count) {
|
||||
result.add(suggestNameByMultipleNames(COMMON_TYPE_PARAMETER_NAMES, validator))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun suggestTypeAliasNameByPsi(typeElement: KtTypeElement, validator: (String) -> Boolean): String {
|
||||
fun KtTypeElement.render(): String {
|
||||
return when (this) {
|
||||
is KtNullableType -> "Nullable${innerType?.render() ?: ""}"
|
||||
is KtFunctionType -> {
|
||||
val arguments = listOfNotNull(receiverTypeReference) + parameters.mapNotNull { it.typeReference }
|
||||
val argText = arguments.joinToString(separator = "") { it.typeElement?.render() ?: "" }
|
||||
val returnText = returnTypeReference?.typeElement?.render() ?: "Unit"
|
||||
"${argText}To$returnText"
|
||||
}
|
||||
is KtUserType -> {
|
||||
val argText = typeArguments.joinToString(separator = "") { it.typeReference?.typeElement?.render() ?: "" }
|
||||
"$argText${referenceExpression?.text ?: ""}"
|
||||
}
|
||||
else -> text.capitalizeAsciiOnly()
|
||||
}
|
||||
}
|
||||
|
||||
return suggestNameByName(typeElement.render(), validator)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates name using set of variants which are tried in succession (and extended with suffixes if necessary)
|
||||
* For example, when given sequence of a, b, c possible names are tried out in the following order: a, b, c, a1, b1, c1, a2, b2, c2, ...
|
||||
* @param names to check it in scope
|
||||
* @return name or nameI, where name is one of variants and I is a number
|
||||
*/
|
||||
fun suggestNameByMultipleNames(names: Collection<String>, validator: (String) -> Boolean): String {
|
||||
var i = 0
|
||||
while (true) {
|
||||
for (name in names) {
|
||||
val candidate = if (i > 0) name + i else name
|
||||
if (validator(candidate)) return candidate
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
fun getCamelNames(name: String, validator: (String) -> Boolean, startLowerCase: Boolean): List<String> {
|
||||
val result = ArrayList<String>()
|
||||
result.addCamelNames(name, validator, startLowerCase)
|
||||
return result
|
||||
}
|
||||
|
||||
protected fun MutableCollection<String>.addCamelNames(name: String, validator: (String) -> Boolean, startLowerCase: Boolean = true) {
|
||||
if (name === "" || !name.unquote().isIdentifier()) return
|
||||
var s = extractIdentifiers(name)
|
||||
|
||||
for (prefix in ACCESSOR_PREFIXES) {
|
||||
if (!s.startsWith(prefix)) continue
|
||||
|
||||
val len = prefix.length
|
||||
if (len < s.length && Character.isUpperCase(s[len])) {
|
||||
s = s.substring(len)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var upperCaseLetterBefore = false
|
||||
for (i in s.indices) {
|
||||
val c = s[i]
|
||||
val upperCaseLetter = Character.isUpperCase(c)
|
||||
|
||||
if (i == 0) {
|
||||
addName(if (startLowerCase) s.decapitalizeSmart() else s, validator)
|
||||
} else {
|
||||
if (upperCaseLetter && !upperCaseLetterBefore) {
|
||||
val substring = s.substring(i)
|
||||
addName(if (startLowerCase) substring.decapitalizeSmart() else substring, validator)
|
||||
}
|
||||
}
|
||||
|
||||
upperCaseLetterBefore = upperCaseLetter
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractIdentifiers(s: String): String {
|
||||
return buildString {
|
||||
val lexer = KotlinLexer()
|
||||
lexer.start(s)
|
||||
while (lexer.tokenType != null) {
|
||||
if (lexer.tokenType == KtTokens.IDENTIFIER) {
|
||||
append(lexer.tokenText)
|
||||
}
|
||||
lexer.advance()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun MutableCollection<String>.addNamesByExpressionPSI(expression: KtExpression?, validator: (String) -> Boolean) {
|
||||
if (expression == null) return
|
||||
when (val deparenthesized = KtPsiUtil.safeDeparenthesize(expression)) {
|
||||
is KtSimpleNameExpression -> addCamelNames(deparenthesized.getReferencedName(), validator)
|
||||
is KtQualifiedExpression -> addNamesByExpressionPSI(deparenthesized.selectorExpression, validator)
|
||||
is KtCallExpression -> addNamesByExpressionPSI(deparenthesized.calleeExpression, validator)
|
||||
is KtPostfixExpression -> addNamesByExpressionPSI(deparenthesized.baseExpression, validator)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun MutableCollection<String>.addName(name: String?, validator: (String) -> Boolean) {
|
||||
if (name == null) return
|
||||
val correctedName = when {
|
||||
name.isIdentifier() -> name
|
||||
name == "class" -> "clazz"
|
||||
else -> return
|
||||
}
|
||||
add(suggestNameByName(correctedName, validator))
|
||||
}
|
||||
|
||||
|
||||
private fun String.withPrefix(prefix: String): String {
|
||||
if (isEmpty()) return prefix
|
||||
val c = this[0]
|
||||
return (if (c in 'a'..'z') prefix.decapitalizeAsciiOnly()
|
||||
else prefix.capitalizeAsciiOnly()) + capitalizeAsciiOnly()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_NUMBER_OF_SUGGESTED_NAME_CHECKS = 1000
|
||||
private val COMMON_TYPE_PARAMETER_NAMES = listOf("T", "U", "V", "W", "X", "Y", "Z")
|
||||
private val ACCESSOR_PREFIXES = arrayOf("get", "is", "set")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user