Make ReferenceSearcher a parameter for JavaToKotlinConverter
This commit is contained in:
@@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.j2k.Converter;
|
||||
import org.jetbrains.jet.j2k.ConverterSettings;
|
||||
import org.jetbrains.jet.j2k.FilesConversionScope;
|
||||
import org.jetbrains.jet.j2k.ReferenceSearcherImpl;
|
||||
import org.jetbrains.jet.plugin.j2k.J2kPostProcessor;
|
||||
|
||||
import java.util.List;
|
||||
@@ -49,6 +50,7 @@ public class JavaToKotlinAction extends AnAction {
|
||||
final Converter converter = Converter.OBJECT$.create(project,
|
||||
ConverterSettings.defaultSettings,
|
||||
new FilesConversionScope(selectedJavaFiles),
|
||||
ReferenceSearcherImpl.INSTANCE$,
|
||||
//TODO: (module refactoring) resulting files should be analyzed in context of respective java files
|
||||
new J2kPostProcessor(selectedJavaFiles.iterator().next()));
|
||||
CommandProcessor.getInstance().executeCommand(
|
||||
|
||||
@@ -89,6 +89,7 @@ public class ConvertJavaCopyPastePostProcessor() : CopyPastePostProcessor<TextBl
|
||||
val converter = Converter.create(fileCopiedFrom.getProject(),
|
||||
ConverterSettings.defaultSettings,
|
||||
FilesConversionScope(listOf(fileCopiedFrom)),
|
||||
ReferenceSearcherImpl,
|
||||
J2kPostProcessor(fileCopiedTo))
|
||||
val startOffsets = code.startOffsets
|
||||
val endOffsets = code.endOffsets
|
||||
|
||||
@@ -266,7 +266,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
|
||||
val (field, `type`) = parameterToField[parameter]!!
|
||||
Parameter(field.declarationIdentifier(),
|
||||
`type`,
|
||||
if (isVal(field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
|
||||
if (isVal(converter.referenceSearcher, field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
|
||||
converter.convertAnnotations(parameter) + converter.convertAnnotations(field),
|
||||
converter.convertModifiers(field).filter { it in ACCESS_MODIFIERS },
|
||||
defaultValue).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false))
|
||||
@@ -278,7 +278,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
|
||||
private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair<PsiField, PsiStatement>? {
|
||||
val body = constructor.getBody() ?: return null
|
||||
|
||||
val refs = findVariableUsages(parameter, body)
|
||||
val refs = converter.referenceSearcher.findVariableUsages(parameter, body)
|
||||
|
||||
if (refs.any { PsiUtil.isAccessedForWriting(it) }) return null
|
||||
|
||||
@@ -297,7 +297,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
|
||||
if (statement.getParent() != body) continue
|
||||
|
||||
// and no other assignments to field should exist in the constructor
|
||||
if (findVariableUsages(field, body).any { it != assignee && PsiUtil.isAccessedForWriting(it) && isQualifierEmptyOrThis(it) }) continue
|
||||
if (converter.referenceSearcher.findVariableUsages(field, body).any { it != assignee && PsiUtil.isAccessedForWriting(it) && isQualifierEmptyOrThis(it) }) continue
|
||||
//TODO: check access to field before assignment
|
||||
|
||||
return field to statement
|
||||
|
||||
@@ -45,6 +45,7 @@ public trait PostProcessor {
|
||||
public class Converter private(val project: Project,
|
||||
val settings: ConverterSettings,
|
||||
val conversionScope: ConversionScope,
|
||||
val referenceSearcher: ReferenceSearcher,
|
||||
private val postProcessor: PostProcessor?,
|
||||
private val state: Converter.State) {
|
||||
private class State(val methodReturnType: PsiType?,
|
||||
@@ -67,34 +68,34 @@ public class Converter private(val project: Project,
|
||||
val annotationConverter = AnnotationConverter(this)
|
||||
|
||||
class object {
|
||||
public fun create(project: Project, settings: ConverterSettings, conversionScope: ConversionScope, postProcessor: PostProcessor?): Converter {
|
||||
public fun create(project: Project, settings: ConverterSettings, conversionScope: ConversionScope, referenceSearcher: ReferenceSearcher, postProcessor: PostProcessor?): Converter {
|
||||
val state = State(null, { ExpressionVisitor(it) }, { StatementVisitor(it) }, null, null, null)
|
||||
return Converter(project, settings, conversionScope, postProcessor, state)
|
||||
return Converter(project, settings, conversionScope, referenceSearcher, postProcessor, state)
|
||||
}
|
||||
}
|
||||
|
||||
fun withMethodReturnType(methodReturnType: PsiType?): Converter
|
||||
= Converter(project, settings, conversionScope, postProcessor,
|
||||
= Converter(project, settings, conversionScope, referenceSearcher, postProcessor,
|
||||
State(methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, state.importList, state.importsToAdd))
|
||||
|
||||
fun withExpressionVisitor(factory: (Converter) -> ExpressionVisitor): Converter
|
||||
= Converter(project, settings, conversionScope, postProcessor,
|
||||
= Converter(project, settings, conversionScope, referenceSearcher, postProcessor,
|
||||
State(state.methodReturnType, factory, state.statementVisitorFactory, state.specialContext, state.importList, state.importsToAdd))
|
||||
|
||||
fun withStatementVisitor(factory: (Converter) -> StatementVisitor): Converter
|
||||
= Converter(project, settings, conversionScope, postProcessor,
|
||||
= Converter(project, settings, conversionScope, referenceSearcher, postProcessor,
|
||||
State(state.methodReturnType, state.expressionVisitorFactory, factory, state.specialContext, state.importList, state.importsToAdd))
|
||||
|
||||
fun withSpecialContext(context: PsiElement): Converter
|
||||
= Converter(project, settings, conversionScope, postProcessor,
|
||||
= Converter(project, settings, conversionScope, referenceSearcher, postProcessor,
|
||||
State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, context, state.importList, state.importsToAdd))
|
||||
|
||||
private fun withImportList(importList: ImportList): Converter
|
||||
= Converter(project, settings, conversionScope, postProcessor,
|
||||
= Converter(project, settings, conversionScope, referenceSearcher, postProcessor,
|
||||
State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, importList, state.importsToAdd))
|
||||
|
||||
private fun withImportsToAdd(importsToAdd: MutableCollection<String>): Converter
|
||||
= Converter(project, settings, conversionScope, postProcessor,
|
||||
= Converter(project, settings, conversionScope, referenceSearcher, postProcessor,
|
||||
State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, state.importList, importsToAdd))
|
||||
|
||||
public fun elementToKotlin(element: PsiElement): String {
|
||||
@@ -220,7 +221,7 @@ public class Converter private(val project: Project,
|
||||
val classObjectMembers = members.filter { it !is PsiClass && it.hasModifierProperty(PsiModifier.STATIC) }
|
||||
val nestedClasses = members.filterIsInstance(javaClass<PsiClass>()).filter { it.hasModifierProperty(PsiModifier.STATIC) }
|
||||
if (classObjectMembers.all { it is PsiMethod && it.hasModifierProperty(PsiModifier.PRIVATE) }) {
|
||||
return nestedClasses.any { nestedClass -> classObjectMembers.any { findMethodCalls(it as PsiMethod, nestedClass).isNotEmpty() } }
|
||||
return nestedClasses.any { nestedClass -> classObjectMembers.any { referenceSearcher.findMethodCalls(it as PsiMethod, nestedClass).isNotEmpty() } }
|
||||
}
|
||||
else {
|
||||
return true
|
||||
@@ -332,7 +333,7 @@ public class Converter private(val project: Project,
|
||||
ExpressionList(convertExpressions(argumentList?.getExpressions())).assignPrototype(argumentList))
|
||||
}
|
||||
else {
|
||||
val isVal = isVal(field)
|
||||
val isVal = isVal(referenceSearcher, field)
|
||||
val typeToDeclare = variableTypeToDeclare(field,
|
||||
settings.specifyFieldTypeByDefault || modifiers.isPublic || modifiers.isProtected,
|
||||
isVal && modifiers.isPrivate)
|
||||
@@ -344,7 +345,7 @@ public class Converter private(val project: Project,
|
||||
initializer,
|
||||
isVal,
|
||||
typeToDeclare != null,
|
||||
initializer.isEmpty && shouldGenerateDefaultInitializer(field))
|
||||
initializer.isEmpty && shouldGenerateDefaultInitializer(referenceSearcher, field))
|
||||
}
|
||||
return converted.assignPrototype(field)
|
||||
}
|
||||
@@ -378,7 +379,7 @@ public class Converter private(val project: Project,
|
||||
|
||||
val statementsToInsert = ArrayList<Statement>()
|
||||
for (parameter in method.getParameterList().getParameters()) {
|
||||
if (parameter.hasWriteAccesses(method)) {
|
||||
if (parameter.hasWriteAccesses(referenceSearcher, method)) {
|
||||
val variable = LocalVariable(parameter.declarationIdentifier(),
|
||||
Annotations.Empty,
|
||||
Modifiers.Empty,
|
||||
@@ -488,7 +489,7 @@ public class Converter private(val project: Project,
|
||||
fun convertLocalVariable(variable: PsiLocalVariable): LocalVariable {
|
||||
val isVal = variable.hasModifierProperty(PsiModifier.FINAL) ||
|
||||
variable.getInitializer() == null/* we do not know actually and prefer val until we have better analysis*/ ||
|
||||
!variable.hasWriteAccesses(variable.getContainingMethod())
|
||||
!variable.hasWriteAccesses(referenceSearcher, variable.getContainingMethod())
|
||||
return LocalVariable(variable.declarationIdentifier(),
|
||||
annotationConverter.convertAnnotations(variable),
|
||||
convertModifiers(variable),
|
||||
|
||||
@@ -83,7 +83,7 @@ public object JavaToKotlinTranslator {
|
||||
fun generateKotlinCode(javaCode: String): String {
|
||||
val file = createFile(javaCode)
|
||||
if (file is PsiJavaFile) {
|
||||
val converter = Converter.create(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)), null)
|
||||
val converter = Converter.create(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)), EmptyReferenceSearcher, null)
|
||||
return prettify(converter.elementToKotlin(file))
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.j2k
|
||||
|
||||
import com.intellij.psi.PsiVariable
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiReferenceExpression
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import java.util.Collections
|
||||
|
||||
public trait ReferenceSearcher {
|
||||
fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection<PsiReferenceExpression>
|
||||
fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression>
|
||||
}
|
||||
|
||||
public object EmptyReferenceSearcher: ReferenceSearcher {
|
||||
override fun findVariableUsages(variable: PsiVariable, scope: PsiElement) = Collections.emptyList<PsiReferenceExpression>()
|
||||
override fun findMethodCalls(method: PsiMethod, scope: PsiElement) = Collections.emptyList<PsiMethodCallExpression>()
|
||||
}
|
||||
|
||||
public object ReferenceSearcherImpl: ReferenceSearcher {
|
||||
override fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection<PsiReferenceExpression> {
|
||||
return ReferencesSearch.search(variable, LocalSearchScope(scope)).findAll().filterIsInstance(javaClass<PsiReferenceExpression>())
|
||||
}
|
||||
|
||||
override fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression> {
|
||||
return ReferencesSearch.search(method, LocalSearchScope(scope)).findAll().map {
|
||||
if (it is PsiReferenceExpression) {
|
||||
val methodCall = it.getParent() as? PsiMethodCallExpression
|
||||
if (methodCall?.getMethodExpression() == it) methodCall else null
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}.filterNotNull()
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ class TypeConverter(val converter: Converter) {
|
||||
}
|
||||
|
||||
if (nullability == Nullability.Default) {
|
||||
if (variable is PsiField && variable.hasModifierProperty(PsiModifier.PRIVATE) && shouldGenerateDefaultInitializer(variable)) {
|
||||
if (variable is PsiField && variable.hasModifierProperty(PsiModifier.PRIVATE) && shouldGenerateDefaultInitializer(converter.referenceSearcher, variable)) {
|
||||
return Nullability.Nullable
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ class TypeConverter(val converter: Converter) {
|
||||
if (nullability == Nullability.Default) {
|
||||
val scope = searchScope(variable)
|
||||
if (scope != null) {
|
||||
if (findVariableUsages(variable, scope).any { isNullableFromUsage(it) }) {
|
||||
if (converter.referenceSearcher.findVariableUsages(variable, scope).any { isNullableFromUsage(it) }) {
|
||||
nullability = Nullability.Nullable
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ class TypeConverter(val converter: Converter) {
|
||||
if (scope != null) {
|
||||
val parameters = method.getParameterList().getParameters()
|
||||
val parameterIndex = parameters.indexOf(variable)
|
||||
for (call in findMethodCalls(method, scope)) {
|
||||
for (call in converter.referenceSearcher.findMethodCalls(method, scope)) {
|
||||
val args = call.getArgumentList().getExpressions()
|
||||
if (args.size == parameters.size) {
|
||||
if (args[parameterIndex].nullability() == Nullability.Nullable) {
|
||||
@@ -211,7 +211,7 @@ class TypeConverter(val converter: Converter) {
|
||||
if (nullability == Nullability.Default) {
|
||||
val scope = searchScope(method)
|
||||
if (scope != null) {
|
||||
if (findMethodCalls(method, scope).any { isNullableFromUsage(it) }) {
|
||||
if (converter.referenceSearcher.findMethodCalls(method, scope).any { isNullableFromUsage(it) }) {
|
||||
nullability = Nullability.Nullable
|
||||
}
|
||||
}
|
||||
@@ -275,8 +275,8 @@ class TypeConverter(val converter: Converter) {
|
||||
private fun PsiVariable.isEffectivelyFinal(): Boolean {
|
||||
if (hasModifierProperty(PsiModifier.FINAL)) return true
|
||||
return when(this) {
|
||||
is PsiLocalVariable -> !hasWriteAccesses(getContainingMethod())
|
||||
is PsiField -> if (hasModifierProperty(PsiModifier.PRIVATE)) !hasWriteAccesses(getContainingClass()) else false
|
||||
is PsiLocalVariable -> !hasWriteAccesses(converter.referenceSearcher, getContainingMethod())
|
||||
is PsiField -> if (hasModifierProperty(PsiModifier.PRIVATE)) !hasWriteAccesses(converter.referenceSearcher, getContainingClass()) else false
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,34 +19,16 @@ package org.jetbrains.jet.j2k
|
||||
import org.jetbrains.jet.lang.types.expressions.OperatorConventions
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import org.jetbrains.jet.j2k.ast.*
|
||||
import com.intellij.psi.util.PsiMethodUtil
|
||||
|
||||
fun quoteKeywords(packageName: String): String = packageName.split("\\.").map { Identifier.toKotlin(it) }.joinToString(".")
|
||||
|
||||
fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection<PsiReferenceExpression> {
|
||||
return ReferencesSearch.search(variable, LocalSearchScope(scope)).findAll().filterIsInstance(javaClass<PsiReferenceExpression>())
|
||||
}
|
||||
fun PsiVariable.countWriteAccesses(searcher: ReferenceSearcher, scope: PsiElement?): Int
|
||||
= if (scope != null) searcher.findVariableUsages(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0
|
||||
|
||||
fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression> {
|
||||
return ReferencesSearch.search(method, LocalSearchScope(scope)).findAll().map {
|
||||
if (it is PsiReferenceExpression) {
|
||||
val methodCall = it.getParent() as? PsiMethodCallExpression
|
||||
if (methodCall?.getMethodExpression() == it) methodCall else null
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}.filterNotNull()
|
||||
}
|
||||
|
||||
fun PsiVariable.countWriteAccesses(scope: PsiElement?): Int
|
||||
= if (scope != null) findVariableUsages(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0
|
||||
|
||||
fun PsiVariable.hasWriteAccesses(scope: PsiElement?): Boolean
|
||||
= if (scope != null) findVariableUsages(this, scope).any { PsiUtil.isAccessedForWriting(it) } else false
|
||||
fun PsiVariable.hasWriteAccesses(searcher: ReferenceSearcher, scope: PsiElement?): Boolean
|
||||
= if (scope != null) searcher.findVariableUsages(this, scope).any { PsiUtil.isAccessedForWriting(it) } else false
|
||||
|
||||
fun getDefaultInitializer(field: Field): Expression? {
|
||||
val t = field.`type`
|
||||
@@ -68,11 +50,11 @@ fun getDefaultInitializer(field: Field): Expression? {
|
||||
return result?.assignNoPrototype()
|
||||
}
|
||||
|
||||
fun isVal(field: PsiField): Boolean {
|
||||
fun isVal(searcher: ReferenceSearcher, field: PsiField): Boolean {
|
||||
if (field.hasModifierProperty(PsiModifier.FINAL)) return true
|
||||
if (!field.hasModifierProperty(PsiModifier.PRIVATE)) return false
|
||||
val containingClass = field.getContainingClass() ?: return false
|
||||
val writes = findVariableUsages(field, containingClass).filter { PsiUtil.isAccessedForWriting(it) }
|
||||
val writes = searcher.findVariableUsages(field, containingClass).filter { PsiUtil.isAccessedForWriting(it) }
|
||||
if (writes.size == 0) return true
|
||||
if (writes.size > 1) return false
|
||||
val write = writes.single()
|
||||
@@ -89,8 +71,8 @@ fun isVal(field: PsiField): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun shouldGenerateDefaultInitializer(field: PsiField)
|
||||
= field.getInitializer() == null && !(isVal(field) && field.hasWriteAccesses(field.getContainingClass()))
|
||||
fun shouldGenerateDefaultInitializer(searcher: ReferenceSearcher, field: PsiField)
|
||||
= field.getInitializer() == null && !(isVal(searcher, field) && field.hasWriteAccesses(searcher, field.getContainingClass()))
|
||||
|
||||
fun isQualifierEmptyOrThis(ref: PsiReferenceExpression): Boolean {
|
||||
val qualifier = ref.getQualifierExpression()
|
||||
|
||||
@@ -118,8 +118,8 @@ class ForConverter(private val statement: PsiForStatement, private val converter
|
||||
if (initialization is PsiDeclarationStatement) {
|
||||
val loopVar = initialization.getDeclaredElements().singleOrNull2() as? PsiLocalVariable
|
||||
if (loopVar != null
|
||||
&& !loopVar.hasWriteAccesses(body)
|
||||
&& !loopVar.hasWriteAccesses(condition)
|
||||
&& !loopVar.hasWriteAccesses(converter.referenceSearcher, body)
|
||||
&& !loopVar.hasWriteAccesses(converter.referenceSearcher, condition)
|
||||
&& condition is PsiBinaryExpression) {
|
||||
val operationTokenType = condition.getOperationTokenType()
|
||||
val lowerBound = condition.getLOperand()
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.intellij.testFramework.LightProjectDescriptor
|
||||
import org.jetbrains.jet.plugin.JetWithJdkAndRuntimeLightProjectDescriptor
|
||||
import com.intellij.psi.PsiJavaFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.jet.j2k.ReferenceSearcherImpl
|
||||
|
||||
public abstract class AbstractJavaToKotlinConverterTest() : LightCodeInsightFixtureTestCase() {
|
||||
val testHeaderPattern = Pattern.compile("//(element|expression|statement|method|class|file|comp)\n")
|
||||
@@ -125,14 +126,14 @@ public abstract class AbstractJavaToKotlinConverterTest() : LightCodeInsightFixt
|
||||
|
||||
private fun elementToKotlin(text: String, settings: ConverterSettings, project: Project): String {
|
||||
val fileWithText = createJavaFile(text)
|
||||
val converter = Converter.create(project, settings, FilesConversionScope(listOf(fileWithText)), J2kPostProcessor(fileWithText))
|
||||
val converter = Converter.create(project, settings, FilesConversionScope(listOf(fileWithText)), ReferenceSearcherImpl, J2kPostProcessor(fileWithText))
|
||||
val element = fileWithText.getFirstChild()!!
|
||||
return converter.elementToKotlin(element)
|
||||
}
|
||||
|
||||
private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String {
|
||||
val file = createJavaFile(text)
|
||||
val converter = Converter.create(project, settings, FilesConversionScope(listOf(file)), J2kPostProcessor(file))
|
||||
val converter = Converter.create(project, settings, FilesConversionScope(listOf(file)), ReferenceSearcherImpl, J2kPostProcessor(file))
|
||||
return converter.elementToKotlin(file)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user