From 4a8adacedd3d8a3dcc59e68e44771294ff8d7b44 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 17 Jul 2015 16:45:10 +0300 Subject: [PATCH] Changed policy for properties from methods like "getURL()"" --- .../synthetic/JavaSyntheticExtensionsScope.kt | 45 ++++++++++-------- .../kotlin/util/capitalizeDecapitalize.kt | 46 +++++++++++++++++++ .../syntheticExtensions/AbbreviationName.kt | 11 ++++- .../syntheticExtensions/AbbreviationName.txt | 2 + .../common/extensions/SyntheticExtensions2.kt | 2 +- .../parameterNameAndType/URLConnection.kt | 6 +++ .../test/JSBasicCompletionTestGenerated.java | 6 +++ .../test/JvmBasicCompletionTestGenerated.java | 6 +++ .../kotlin/idea/core/KotlinNameSuggester.kt | 26 ++--------- 9 files changed, 104 insertions(+), 46 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/util/capitalizeDecapitalize.kt create mode 100644 idea/idea-completion/testData/basic/common/parameterNameAndType/URLConnection.kt diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/synthetic/JavaSyntheticExtensionsScope.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/synthetic/JavaSyntheticExtensionsScope.kt index 695dcfca9af..ddf56b84a74 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/synthetic/JavaSyntheticExtensionsScope.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/synthetic/JavaSyntheticExtensionsScope.kt @@ -34,6 +34,8 @@ import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.typeUtil.isBoolean import org.jetbrains.kotlin.types.typeUtil.isUnit import org.jetbrains.kotlin.types.typeUtil.makeNotNullable +import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeFirstWord +import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart import org.jetbrains.kotlin.utils.addIfNotNull import java.beans.Introspector import java.util.* @@ -74,7 +76,7 @@ interface SyntheticJavaPropertyDescriptor : PropertyDescriptor { } if (!removePrefix) return methodName - val name = Introspector.decapitalize(identifier.removePrefix(prefix)) + val name = identifier.removePrefix(prefix).decapitalizeSmart() if (!Name.isValidIdentifier(name)) return null return Name.identifier(name) } @@ -94,16 +96,10 @@ class JavaSyntheticExtensionsScope(storageManager: StorageManager) : JetScope by private fun syntheticPropertyInClassNotCached(javaClass: JavaClassDescriptor, type: JetType, name: Name): PropertyDescriptor? { if (name.isSpecial()) return null - val identifier = name.getIdentifier() + val identifier = name.identifier if (identifier.isEmpty()) return null val firstChar = identifier[0] - if (!firstChar.isJavaIdentifierStart()) return null - if (identifier.length() > 1) { - if (firstChar.isUpperCase() != identifier[1].isUpperCase()) return null - } - else { - if (firstChar.isUpperCase()) return null - } + if (!firstChar.isJavaIdentifierStart() || firstChar.isUpperCase()) return null val memberScope = javaClass.getMemberScope(type.getArguments()) val getMethod = possibleGetMethodNames(name) @@ -111,6 +107,9 @@ class JavaSyntheticExtensionsScope(storageManager: StorageManager) : JetScope by .flatMap { memberScope.getFunctions(it).asSequence() } .singleOrNull { isGoodGetMethod(it) } ?: return null + // don't accept "uRL" for "getURL" etc + if (SyntheticJavaPropertyDescriptor.propertyNameByGetMethodName(getMethod.name) != name) return null + val propertyType = getMethod.getReturnType() ?: return null val setMethod = memberScope.getFunctions(setMethodName(getMethod.getName())).singleOrNull { isGoodSetMethod(it, propertyType) } @@ -139,13 +138,13 @@ class JavaSyntheticExtensionsScope(storageManager: StorageManager) : JetScope by override fun getSyntheticExtensionProperties(receiverTypes: Collection, name: Name): Collection { var result: SmartList? = null val processedTypes: MutableSet? = if (receiverTypes.size() > 1) HashSet() else null - receiverTypes.forEach { - result = collectSyntheticPropertiesByName(result, it.makeNotNullable(), name, processedTypes) + for (type in receiverTypes) { + result = collectSyntheticPropertiesByName(result, type.makeNotNullable(), name, processedTypes) } return when { result == null -> emptyList() - result!!.size() > 1 -> result!!.toSet() - else -> result!! + result.size() > 1 -> result.toSet() + else -> result } } @@ -202,24 +201,30 @@ class JavaSyntheticExtensionsScope(storageManager: StorageManager) : JetScope by //TODO: reuse code with generation? private fun possibleGetMethodNames(propertyName: Name): Collection { - val identifier = propertyName.getIdentifier() - val getPrefixName = Name.identifier("get" + identifier.capitalize()) + val result = ArrayList(3) + val identifier = propertyName.identifier + if (identifier.startsWith("is")) { - return listOf(propertyName, getPrefixName) + result.add(propertyName) } - else { - return listOf(getPrefixName) + + val capitalize1 = identifier.capitalize() + val capitalize2 = identifier.capitalizeFirstWord() + result.add(Name.identifier("get" + capitalize1)) + if (capitalize2 != capitalize1) { + result.add(Name.identifier("get" + capitalize2)) } + return result } private fun setMethodName(getMethodName: Name): Name { - val identifier = getMethodName.getIdentifier() + val identifier = getMethodName.identifier val prefix = when { identifier.startsWith("get") -> "get" identifier.startsWith("is") -> "is" else -> throw IllegalArgumentException() } - return Name.identifier("set" + identifier.removePrefix(prefix).capitalize()) + return Name.identifier("set" + identifier.removePrefix(prefix)) } private class MyPropertyDescriptor( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/capitalizeDecapitalize.kt b/compiler/frontend/src/org/jetbrains/kotlin/util/capitalizeDecapitalize.kt new file mode 100644 index 00000000000..76aafb5ba58 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/capitalizeDecapitalize.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * 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. + */ + +package org.jetbrains.kotlin.util.capitalizeDecapitalize + +/** + * "FooBar" -> "fooBar" + * "FOOBar" -> "fooBar" + * "FOO" -> "foo" + */ +public fun String.decapitalizeSmart(): String { + if (isEmpty() || !charAt(0).isUpperCase()) return this + + if (length() == 1 || !charAt(1).isUpperCase()) { + return decapitalize() + } + + val secondWordStart = (indices.firstOrNull { !charAt(it).isUpperCase() } + ?: return toLowerCase()) - 1 + return substring(0, secondWordStart).toLowerCase() + substring(secondWordStart) +} + +/** + * "fooBar" -> "FOOBar" + * "FooBar" -> "FOOBar" + * "foo" -> "FOO" + */ +public fun String.capitalizeFirstWord(): String { + val secondWordStart = indices.drop(1).firstOrNull { !charAt(it).isLowerCase() } + ?: return toUpperCase() + return substring(0, secondWordStart).toUpperCase() + substring(secondWordStart) +} + diff --git a/compiler/testData/diagnostics/tests/syntheticExtensions/AbbreviationName.kt b/compiler/testData/diagnostics/tests/syntheticExtensions/AbbreviationName.kt index b1b397fe5f9..2c812f9d52b 100644 --- a/compiler/testData/diagnostics/tests/syntheticExtensions/AbbreviationName.kt +++ b/compiler/testData/diagnostics/tests/syntheticExtensions/AbbreviationName.kt @@ -1,13 +1,20 @@ // FILE: KotlinFile.kt fun foo(javaClass: JavaClass) { - javaClass.URL = javaClass.URL + "/" + javaClass.url = javaClass.url + "/" + javaClass.htmlFile += "1" - javaClass.url + javaClass.URL javaClass.uRL + javaClass.HTMLFile + javaClass.hTMLFile + javaClass.htmlfile } // FILE: JavaClass.java public class JavaClass { public String getURL() { return true; } public void setURL(String value) { } + + public String getHTMLFile() { return true; } + public void setHTMLFile(String value) { } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/syntheticExtensions/AbbreviationName.txt b/compiler/testData/diagnostics/tests/syntheticExtensions/AbbreviationName.txt index c2731564e6d..6d50ee24342 100644 --- a/compiler/testData/diagnostics/tests/syntheticExtensions/AbbreviationName.txt +++ b/compiler/testData/diagnostics/tests/syntheticExtensions/AbbreviationName.txt @@ -5,8 +5,10 @@ internal fun foo(/*0*/ javaClass: JavaClass): kotlin.Unit public open class JavaClass { public constructor JavaClass() public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open fun getHTMLFile(): kotlin.String! public open fun getURL(): kotlin.String! public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open fun setHTMLFile(/*0*/ value: kotlin.String!): kotlin.Unit public open fun setURL(/*0*/ value: kotlin.String!): kotlin.Unit public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } diff --git a/idea/idea-completion/testData/basic/common/extensions/SyntheticExtensions2.kt b/idea/idea-completion/testData/basic/common/extensions/SyntheticExtensions2.kt index 98c55ba5476..5c1e4726cba 100644 --- a/idea/idea-completion/testData/basic/common/extensions/SyntheticExtensions2.kt +++ b/idea/idea-completion/testData/basic/common/extensions/SyntheticExtensions2.kt @@ -6,7 +6,7 @@ fun Thread.foo(urlConnection: java.net.URLConnection) { // EXIST_JAVA_ONLY: { lookupString: "priority", itemText: "priority", tailText: " (from getPriority()/setPriority())", typeText: "Int" } // EXIST_JAVA_ONLY: { lookupString: "isDaemon", itemText: "isDaemon", tailText: " (from isDaemon()/setDaemon())", typeText: "Boolean" } -// EXIST_JAVA_ONLY: { lookupString: "URL", itemText: "URL", tailText: " (from getURL())", typeText: "URL!" } +// EXIST_JAVA_ONLY: { lookupString: "url", itemText: "url", tailText: " (from getURL())", typeText: "URL!" } // ABSENT: getPriority // ABSENT: setPriority // ABSENT: { itemText: "isDaemon", tailText: "()" } diff --git a/idea/idea-completion/testData/basic/common/parameterNameAndType/URLConnection.kt b/idea/idea-completion/testData/basic/common/parameterNameAndType/URLConnection.kt new file mode 100644 index 00000000000..b746eca68f9 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/parameterNameAndType/URLConnection.kt @@ -0,0 +1,6 @@ +import java.net.URLConnection + +fun foo(url){} + +// EXIST_JAVA_ONLY: { lookupString: "urlConnection", itemText: "urlConnection: URLConnection", tailText: " (java.net)" } +// ABSENT: urlconnection diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index c87ef238e8c..41f8b46f755 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -1617,6 +1617,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes doTest(fileName); } + @TestMetadata("URLConnection.kt") + public void testURLConnection() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/URLConnection.kt"); + doTest(fileName); + } + @TestMetadata("UserPrefix1.kt") public void testUserPrefix1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/UserPrefix1.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index 747bcd68b61..56d3bd490c4 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -1617,6 +1617,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT doTest(fileName); } + @TestMetadata("URLConnection.kt") + public void testURLConnection() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/URLConnection.kt"); + doTest(fileName); + } + @TestMetadata("UserPrefix1.kt") public void testUserPrefix1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/UserPrefix1.kt"); diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinNameSuggester.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinNameSuggester.kt index f3e064b0f12..0c184a5fe8e 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinNameSuggester.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinNameSuggester.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.checker.JetTypeChecker +import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart import java.util.* import java.util.regex.Pattern @@ -240,12 +241,12 @@ public object KotlinNameSuggester { val upperCaseLetter = Character.isUpperCase(c) if (i == 0) { - addName(if (startLowerCase) decapitalize(s) else s, validator) + addName(if (startLowerCase) s.decapitalizeSmart() else s, validator) } else { if (upperCaseLetter && !upperCaseLetterBefore) { val substring = s.substring(i) - addName(if (startLowerCase) decapitalize(substring) else substring, validator) + addName(if (startLowerCase) substring.decapitalizeSmart() else substring, validator) } } @@ -253,27 +254,6 @@ public object KotlinNameSuggester { } } - private fun decapitalize(s: String): String { - var c = s.charAt(0) - if (!Character.isUpperCase(c)) return s - - val builder = StringBuilder(s.length()) - var decapitalize = true - for (i in 0..s.length() - 1) { - c = s.charAt(i) - if (decapitalize) { - if (Character.isUpperCase(c)) { - c = Character.toLowerCase(c) - } - else { - decapitalize = false - } - } - builder.append(c) - } - return builder.toString() - } - private fun deleteNonLetterFromString(s: String): String { val pattern = Pattern.compile("[^a-zA-Z]") val matcher = pattern.matcher(s)