diff --git a/j2k/src/org/jetbrains/kotlin/j2k/AnnotationConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/AnnotationConverter.kt index e277391c6ef..4ec1243125f 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/AnnotationConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/AnnotationConverter.kt @@ -132,7 +132,8 @@ class AnnotationConverter(private val converter: Converter) { val name = it.name null to converter.deferredElement { QualifiedExpression(Identifier.withNoPrototype("AnnotationTarget", isNullable = false), - Identifier.withNoPrototype(name, isNullable = false)) + Identifier.withNoPrototype(name, isNullable = false), + null) } } return Identifier.withNoPrototype("Target") to deferredExpressionList diff --git a/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt b/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt index b289b047db9..4698e989245 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt @@ -359,7 +359,7 @@ class Converter private constructor( } else if (propertyInfo.modifiers.contains(Modifier.OVERRIDE) && !(propertyInfo.superInfo?.isAbstract() ?: false)) { val superExpression = SuperExpression(Identifier.Empty).assignNoPrototype() - val superAccess = QualifiedExpression(superExpression, propertyInfo.identifier).assignNoPrototype() + val superAccess = QualifiedExpression(superExpression, propertyInfo.identifier, null).assignNoPrototype() val returnStatement = ReturnStatement(superAccess).assignNoPrototype() val body = Block.of(returnStatement).assignNoPrototype() val parameterList = ParameterList.withNoPrototype(emptyList()) @@ -396,7 +396,7 @@ class Converter private constructor( } else if (propertyInfo.modifiers.contains(Modifier.OVERRIDE) && !(propertyInfo.superInfo?.isAbstract() ?: false)) { val superExpression = SuperExpression(Identifier.Empty).assignNoPrototype() - val superAccess = QualifiedExpression(superExpression, propertyInfo.identifier).assignNoPrototype() + val superAccess = QualifiedExpression(superExpression, propertyInfo.identifier, null).assignNoPrototype() val valueIdentifier = Identifier.withNoPrototype("value", isNullable = false) val assignment = AssignmentExpression(superAccess, valueIdentifier, Operator.EQ).assignNoPrototype() val body = Block.of(assignment).assignNoPrototype() diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt index 631fde382ae..e9918b51115 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt @@ -242,7 +242,7 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { } else { val type = converter.convertTypeElement(operand, Nullability.NotNull) - result = QualifiedExpression(ClassLiteralExpression(type).assignNoPrototype(), Identifier.withNoPrototype("java")) + result = QualifiedExpression(ClassLiteralExpression(type).assignNoPrototype(), Identifier.withNoPrototype("java"), null) return } @@ -253,7 +253,8 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { else wrapperTypeName.shortName().asString() result = QualifiedExpression(Identifier(classNameToUse, false).assignPrototype(operand), - Identifier.withNoPrototype("TYPE", isNullable = false)) + Identifier.withNoPrototype("TYPE", isNullable = false), + null) } override fun visitConditionalExpression(expression: PsiConditionalExpression) { @@ -322,6 +323,7 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { val methodExpr = expression.methodExpression val arguments = expression.argumentList.expressions + val qualifier = methodExpr.qualifierExpression val target = methodExpr.resolve() val isNullable = if (target is PsiMethod) typeConverter.methodNullability(target).isNullable(codeConverter.settings) else false val typeArguments = convertTypeArguments(expression) @@ -340,12 +342,15 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { val isExtension = property.isExtensionDeclaration() val propertyAccess = if (isTopLevel) { if (isExtension) - QualifiedExpression(codeConverter.convertExpression(arguments.firstOrNull(), true), propertyName).assignNoPrototype() + QualifiedExpression(codeConverter.convertExpression(arguments.firstOrNull(), true), propertyName, null).assignNoPrototype() else propertyName } + else if (qualifier != null) { + QualifiedExpression(codeConverter.convertExpression(qualifier), propertyName, methodExpr.dot()).assignNoPrototype() + } else { - QualifiedExpression(codeConverter.convertExpression(methodExpr.qualifierExpression), propertyName).assignNoPrototype() + propertyName } when(if (isExtension) parameterCount - 1 else parameterCount) { @@ -408,7 +413,7 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { if (target is PsiMethod) { val specialMethod = SpecialMethod.match(target, arguments.size, converter.services) if (specialMethod != null) { - val converted = specialMethod.convertCall(methodExpr.qualifierExpression, arguments, typeArguments, codeConverter) + val converted = specialMethod.convertCall(qualifier, arguments, typeArguments, codeConverter) if (converted != null) { result = converted return @@ -542,7 +547,7 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { expression.getContainingClass()?.getParentOfType(false)?.let { if (it == expression.qualifierExpression?.reference?.resolve()) { - result = QualifiedExpression(ThisExpression(Identifier.Empty).assignNoPrototype(), identifier) + result = QualifiedExpression(ThisExpression(Identifier.Empty).assignNoPrototype(), identifier, null) return } } @@ -583,7 +588,10 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { } } - result = if (qualifier != null) QualifiedExpression(codeConverter.convertExpression(qualifier), identifier) else identifier + result = if (qualifier != null) + QualifiedExpression(codeConverter.convertExpression(qualifier), identifier, expression.dot()) + else + identifier } private fun isNullable(target: PsiVariable): Boolean { diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ForConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/ForConverter.kt index 8bce87de44f..82ac0859669 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ForConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ForConverter.kt @@ -240,7 +240,7 @@ class ForConverter( val collectionType = PsiElementFactory.SERVICE.getInstance(project).createTypeByFQClassName(CommonClassNames.JAVA_UTIL_COLLECTION) val qualifierType = qualifier.type if (qualifierType != null && collectionType.isAssignableFrom(qualifierType)) { - indices = QualifiedExpression(codeConverter.convertExpression(qualifier), Identifier.withNoPrototype("indices", isNullable = false)) + indices = QualifiedExpression(codeConverter.convertExpression(qualifier), Identifier.withNoPrototype("indices", isNullable = false), null) } } } @@ -250,7 +250,7 @@ class ForConverter( && collectionSize.referenceName == "length") { val qualifier = collectionSize.qualifierExpression if (qualifier is PsiReferenceExpression && qualifier.type is PsiArrayType) { - indices = QualifiedExpression(codeConverter.convertExpression(qualifier), Identifier.withNoPrototype("indices", isNullable = false)) + indices = QualifiedExpression(codeConverter.convertExpression(qualifier), Identifier.withNoPrototype("indices", isNullable = false), null) } } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/SpecialMethod.kt b/j2k/src/org/jetbrains/kotlin/j2k/SpecialMethod.kt index c2c39c9ec5d..c459cf99e09 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/SpecialMethod.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/SpecialMethod.kt @@ -197,7 +197,10 @@ enum class SpecialMethod(val qualifiedClassName: String?, val methodName: String OBJECT_GET_CLASS(JAVA_LANG_OBJECT, "getClass", 0) { override fun convertCall(qualifier: PsiExpression?, arguments: Array, typeArgumentsConverted: List, codeConverter: CodeConverter): Expression { val identifier = Identifier.withNoPrototype("javaClass", isNullable = false) - return if (qualifier != null) QualifiedExpression(codeConverter.convertExpression(qualifier), identifier) else identifier + return if (qualifier != null) + QualifiedExpression(codeConverter.convertExpression(qualifier), identifier, null) + else + identifier } }, @@ -436,7 +439,10 @@ enum class SpecialMethod(val qualifiedClassName: String?, val methodName: String protected fun convertMethodCallToPropertyUse(codeConverter: CodeConverter, qualifier: PsiExpression?, propertyName: String = methodName): Expression { val identifier = Identifier.withNoPrototype(propertyName, isNullable = false) - return if (qualifier != null) QualifiedExpression(codeConverter.convertExpression(qualifier), identifier) else identifier + return if (qualifier != null) + QualifiedExpression(codeConverter.convertExpression(qualifier), identifier, null) + else + identifier } protected fun Array.notNull() = map { it to Nullability.NotNull } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/Utils.kt b/j2k/src/org/jetbrains/kotlin/j2k/Utils.kt index 0fc959ed3a8..1b073b910f0 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/Utils.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/Utils.kt @@ -21,6 +21,7 @@ import com.intellij.psi.util.PsiMethodUtil import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.j2k.ast.* +import org.jetbrains.kotlin.psi.psiUtil.allChildren import org.jetbrains.kotlin.types.expressions.OperatorConventions fun quoteKeywords(packageName: String): String = packageName.split('.').map { Identifier.toKotlin(it) }.joinToString(".") @@ -110,6 +111,8 @@ fun PsiModifierListOwner.accessModifier(): String = when { fun PsiMethod.isMainMethod(): Boolean = PsiMethodUtil.isMainMethod(this) +fun PsiReferenceExpression.dot(): PsiElement? = allChildren.firstOrNull { it.node.elementType == JavaTokenType.DOT } + fun PsiMember.isImported(file: PsiJavaFile): Boolean { if (this is PsiClass) { val fqName = qualifiedName diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/Expressions.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/Expressions.kt index 7599cae7238..cee0eda7e25 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/Expressions.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/Expressions.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.j2k.ast import com.intellij.psi.JavaTokenType +import com.intellij.psi.PsiElement import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.j2k.CodeBuilder import org.jetbrains.kotlin.j2k.append @@ -111,17 +112,26 @@ class SuperExpression(val identifier: Identifier) : Expression() { } } -class QualifiedExpression(val qualifier: Expression, val identifier: Expression) : Expression() { +class QualifiedExpression(val qualifier: Expression, val identifier: Expression, dotPrototype: PsiElement?) : Expression() { + private val dot = Dot().assignPrototype(dotPrototype, CommentsAndSpacesInheritance.LINE_BREAKS) + override val isNullable: Boolean get() = identifier.isNullable override fun generateCode(builder: CodeBuilder) { if (!qualifier.isEmpty) { - builder.appendOperand(this, qualifier).append(if (qualifier.isNullable) "!!." else ".") + builder.appendOperand(this, qualifier).append(if (qualifier.isNullable) "!!" else "") + builder.append(dot) } builder.append(identifier) } + + private class Dot : Element() { + override fun generateCode(builder: CodeBuilder) { + builder.append(".") + } + } } class PolyadicExpression(val expressions: List, val operators: List) : Expression() { diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/MethodCallExpression.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/MethodCallExpression.kt index 96ad576b622..e995d66da58 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/MethodCallExpression.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/MethodCallExpression.kt @@ -54,7 +54,7 @@ class MethodCallExpression( isNullable: Boolean ): MethodCallExpression { val identifier = Identifier.withNoPrototype(methodName, isNullable = false) - return MethodCallExpression(if (receiver != null) QualifiedExpression(receiver, identifier).assignNoPrototype() else identifier, + return MethodCallExpression(if (receiver != null) QualifiedExpression(receiver, identifier, null).assignNoPrototype() else identifier, argumentList, typeArguments, isNullable) diff --git a/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/AccessorToPropertyProcessing.kt b/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/AccessorToPropertyProcessing.kt index 6f5a8b8cdfc..d3613e2d474 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/AccessorToPropertyProcessing.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/AccessorToPropertyProcessing.kt @@ -20,6 +20,7 @@ import com.intellij.psi.* import org.jetbrains.kotlin.j2k.AccessorKind import org.jetbrains.kotlin.j2k.CodeConverter import org.jetbrains.kotlin.j2k.ast.* +import org.jetbrains.kotlin.j2k.dot import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.utils.addToStdlib.singletonList @@ -34,7 +35,11 @@ class AccessorToPropertyProcessing(val accessorMethod: PsiMethod, val accessorKi val arguments = methodCall.argumentList.expressions val propertyName = Identifier.withNoPrototype(propertyName, isNullable) - val propertyAccess = QualifiedExpression(codeConverter.convertExpression(methodExpr.qualifierExpression), propertyName).assignNoPrototype() + val qualifier = methodExpr.qualifierExpression + val propertyAccess = if (qualifier != null) + QualifiedExpression(codeConverter.convertExpression(qualifier), propertyName, methodExpr.dot()).assignNoPrototype() + else + propertyName if (accessorKind == AccessorKind.GETTER) { if (arguments.size != 0) return null // incorrect call diff --git a/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/FieldToPropertyProcessing.kt b/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/FieldToPropertyProcessing.kt index b1ea9b4213c..14517ee5a8d 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/FieldToPropertyProcessing.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/usageProcessing/FieldToPropertyProcessing.kt @@ -22,6 +22,7 @@ import com.intellij.util.IncorrectOperationException import org.jetbrains.kotlin.j2k.AccessorKind import org.jetbrains.kotlin.j2k.CodeConverter import org.jetbrains.kotlin.j2k.ast.* +import org.jetbrains.kotlin.j2k.dot import org.jetbrains.kotlin.utils.addToStdlib.singletonList class FieldToPropertyProcessing( @@ -60,7 +61,7 @@ class FieldToPropertyProcessing( val qualifier = expression.qualifierExpression if (qualifier != null && !useFieldReference) { - return QualifiedExpression(codeConverter.convertExpression(qualifier), identifier) + return QualifiedExpression(codeConverter.convertExpression(qualifier), identifier, expression.dot()) } else { // check if field name is shadowed @@ -74,7 +75,7 @@ class FieldToPropertyProcessing( return if (refExpr.resolve() == null) identifier else - QualifiedExpression(ThisExpression(Identifier.Empty).assignNoPrototype(), identifier) //TODO: this is not correct in case of nested/anonymous classes + QualifiedExpression(ThisExpression(Identifier.Empty).assignNoPrototype(), identifier, null) //TODO: this is not correct in case of nested/anonymous classes } } } diff --git a/j2k/testData/fileOrElement/comments/commentInsideCall.kt b/j2k/testData/fileOrElement/comments/commentInsideCall.kt index e48cd039745..7b55028920f 100644 --- a/j2k/testData/fileOrElement/comments/commentInsideCall.kt +++ b/j2k/testData/fileOrElement/comments/commentInsideCall.kt @@ -4,8 +4,11 @@ object Test { @JvmStatic fun main(args: Array) { println()// Comment - Test.foo()// Comment1 - .indexOf("s")// Comment2 + Test + // Comment1 + .foo() + // Comment2 + .indexOf("s") } fun foo(): String { diff --git a/j2k/testData/fileOrElement/constructors/customerBuilder.kt b/j2k/testData/fileOrElement/constructors/customerBuilder.kt index 1dfd809cd75..f95568d351e 100644 --- a/j2k/testData/fileOrElement/constructors/customerBuilder.kt +++ b/j2k/testData/fileOrElement/constructors/customerBuilder.kt @@ -35,7 +35,10 @@ internal class CustomerBuilder { object User { fun main() { - val customer = CustomerBuilder().WithFirstName("Homer").WithLastName("Simpson").Build() + val customer = CustomerBuilder() + .WithFirstName("Homer") + .WithLastName("Simpson") + .Build() println(customer.firstName) println(customer.lastName) } diff --git a/j2k/testData/fileOrElement/formatting/chainedCall.java b/j2k/testData/fileOrElement/formatting/chainedCall.java new file mode 100644 index 00000000000..27559d70cf6 --- /dev/null +++ b/j2k/testData/fileOrElement/formatting/chainedCall.java @@ -0,0 +1,8 @@ +public class C { + void foo() { + StringBuilder builder = new StringBuilder(); + builder.append(1) + .append(2).append(3) + .append(4); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/formatting/chainedCall.kt b/j2k/testData/fileOrElement/formatting/chainedCall.kt new file mode 100644 index 00000000000..8c13a41b75d --- /dev/null +++ b/j2k/testData/fileOrElement/formatting/chainedCall.kt @@ -0,0 +1,8 @@ +class C { + internal fun foo() { + val builder = StringBuilder() + builder.append(1) + .append(2).append(3) + .append(4) + } +} diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java index f5df543c5eb..cb1e193c78a 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java @@ -2317,6 +2317,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/formatting"), Pattern.compile("^(.+)\\.java$"), true); } + @TestMetadata("chainedCall.java") + public void testChainedCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/formatting/chainedCall.java"); + doTest(fileName); + } + @TestMetadata("lineBreaksBetweenArguments.java") public void testLineBreaksBetweenArguments() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/formatting/lineBreaksBetweenArguments.java"); diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java index f985d4bdad9..8061e1c4555 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java @@ -2317,6 +2317,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/formatting"), Pattern.compile("^(.+)\\.java$"), true); } + @TestMetadata("chainedCall.java") + public void testChainedCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/formatting/chainedCall.java"); + doTest(fileName); + } + @TestMetadata("lineBreaksBetweenArguments.java") public void testLineBreaksBetweenArguments() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/formatting/lineBreaksBetweenArguments.java");