All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.org.openrewrite.kotlin.internal.KotlinTreeParserVisitor Maven / Gradle / Ivy

/*
 * Copyright 2023 the original author or authors.
 * 

* 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 *

* https://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.openrewrite.kotlin.internal; import kotlin.Pair; import org.jetbrains.kotlin.KtNodeTypes; import org.jetbrains.kotlin.com.intellij.lang.ASTNode; import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange; import org.jetbrains.kotlin.com.intellij.psi.*; import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement; import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiErrorElementImpl; import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType; import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.kotlin.fir.FirElement; import org.jetbrains.kotlin.fir.declarations.FirResolvedImport; import org.jetbrains.kotlin.fir.expressions.FirCallableReferenceAccess; import org.jetbrains.kotlin.fir.references.FirResolvedCallableReference; import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol; import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol; import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol; import org.jetbrains.kotlin.kdoc.psi.api.KDoc; import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection; import org.jetbrains.kotlin.lexer.KtModifierKeywordToken; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.parsing.ParseUtilsKt; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; import org.openrewrite.ParseExceptionResult; import org.openrewrite.Tree; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.NonNull; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.marker.ImplicitReturn; import org.openrewrite.java.marker.OmitParentheses; import org.openrewrite.java.marker.Quoted; import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; import org.openrewrite.kotlin.KotlinParser; import org.openrewrite.kotlin.marker.*; import org.openrewrite.kotlin.tree.K; import org.openrewrite.marker.Markers; import org.openrewrite.style.NamedStyles; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; import static org.openrewrite.Tree.randomId; /** * PSI based parser */ @SuppressWarnings("ConstantValue") public class KotlinTreeParserVisitor extends KtVisitor { private final KotlinSource kotlinSource; private final PsiElementAssociations psiElementAssociations; private final List styles; private final Path sourcePath; @Nullable private final FileAttributes fileAttributes; private final Charset charset; private final Boolean charsetBomMarked; private final Stack ownerStack = new Stack<>(); private final ExecutionContext executionContext; private final List cRLFLocations; public KotlinTreeParserVisitor(KotlinSource kotlinSource, PsiElementAssociations psiElementAssociations, List styles, @Nullable Path relativeTo, ExecutionContext ctx) { this.kotlinSource = kotlinSource; this.psiElementAssociations = psiElementAssociations; this.styles = styles; sourcePath = kotlinSource.getInput().getRelativePath(relativeTo); fileAttributes = kotlinSource.getInput().getFileAttributes(); EncodingDetectingInputStream stream = kotlinSource.getInput().getSource(ctx); charset = stream.getCharset(); charsetBomMarked = stream.isCharsetBomMarked(); ownerStack.push(kotlinSource.getKtFile()); executionContext = ctx; cRLFLocations = kotlinSource.getCRLFLocations(); } public K.CompilationUnit parse() { return (K.CompilationUnit) visitKtFile(kotlinSource.getKtFile(), executionContext); } @Override public J visitParenthesizedExpression(KtParenthesizedExpression expression, ExecutionContext data) { assert expression.getExpression() != null; PsiElement rPar = expression.getLastChild(); if (rPar == null || !(")".equals(rPar.getText()))) { throw new UnsupportedOperationException("TODO"); } return new J.Parentheses<>( randomId(), deepPrefix(expression), Markers.EMPTY, padRight(expression.getExpression().accept(this, data), prefix(rPar)) ); } @Override public J visitForExpression(KtForExpression expression, ExecutionContext data) { return new J.ForEachLoop( randomId(), deepPrefix(expression), Markers.EMPTY, new J.ForEachLoop.Control( randomId(), prefix(expression.getLeftParenthesis()), Markers.EMPTY, padRight((J.VariableDeclarations) requireNonNull(expression.getLoopParameter()).accept(this, data), suffix(expression.getLoopParameter())), padRight(requireNonNull(expression.getLoopRange()).accept(this, data) .withPrefix(prefix(expression.getLoopRange().getParent())), suffix(expression.getLoopRange().getParent())) ), padRight(requireNonNull(expression.getBody()).accept(this, data) .withPrefix(prefix(expression.getBody().getParent())), suffix(expression.getBody())) ); } @Override public J visitAnnotatedExpression(KtAnnotatedExpression expression, ExecutionContext data) { List ktAnnotations = expression.getAnnotationEntries(); List annotations = new ArrayList<>(ktAnnotations.size()); for (int i = 0; i < ktAnnotations.size(); i++) { KtAnnotationEntry ktAnnotation = ktAnnotations.get(i); J.Annotation anno = (J.Annotation) ktAnnotation.accept(this, data); if (i == 0) { anno = anno.withPrefix(merge(prefix(expression), anno.getPrefix())); } annotations.add(anno); } return new K.AnnotatedExpression( randomId(), Markers.EMPTY, annotations, convertToExpression(requireNonNull(expression.getBaseExpression()).accept(this, data)) ); } @Override public J visitAnnotationUseSiteTarget(KtAnnotationUseSiteTarget annotationTarget, ExecutionContext data) { return createIdentifier(annotationTarget, type(annotationTarget)); } @Override public J visitAnonymousInitializer(KtAnonymousInitializer initializer, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitArrayAccessExpression(KtArrayAccessExpression expression, ExecutionContext data) { Markers markers = Markers.EMPTY; Expression selectExpr = convertToExpression(requireNonNull(expression.getArrayExpression()).accept(this, data)); JRightPadded select = padRight(selectExpr, suffix(expression.getArrayExpression())); JavaType.Method type = methodInvocationType(expression); J.Identifier name = createIdentifier("", Space.EMPTY, type); markers = markers.addIfAbsent(new IndexedAccess(randomId())); List indexExpressions = expression.getIndexExpressions(); List> expressions = new ArrayList<>(); for (int i = 0; i < indexExpressions.size(); i++) { KtExpression indexExp = indexExpressions.get(i); JRightPadded rp = padRight(convertToExpression(indexExp.accept(this, data)), suffix(indexExp)); expressions.add(maybeTrailingComma(indexExp, rp, i == indexExpressions.size() - 1)); } JContainer args = JContainer.build(Space.EMPTY, expressions, markers); return mapType(new J.MethodInvocation( randomId(), prefix(expression), markers, select, null, name, args, type )); } @Override public J visitBackingField(KtBackingField accessor, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitBinaryWithTypeRHSExpression(KtBinaryExpressionWithTypeRHS expression, ExecutionContext data) { IElementType type = expression.getOperationReference().getReferencedNameElementType(); if (type == KtTokens.AS_KEYWORD || type == KtTokens.AS_SAFE) { TypeTree clazz = (TypeTree) (requireNonNull(expression.getRight()).accept(this, data)); Markers markers = Markers.EMPTY; if (type == KtTokens.AS_SAFE) { markers = markers.addIfAbsent(new IsNullSafe(randomId())); } return new J.TypeCast( randomId(), deepPrefix(expression), markers, new J.ControlParentheses<>( randomId(), suffix(expression.getLeft()), Markers.EMPTY, JRightPadded.build(clazz) ), convertToExpression(expression.getLeft().accept(this, data)) ); } throw new UnsupportedOperationException("TODO"); } @Override public J visitBlockStringTemplateEntry(KtBlockStringTemplateEntry entry, ExecutionContext data) { J tree = requireNonNull(entry.getExpression()).accept(this, data); boolean inBraces = true; return new K.StringTemplate.Expression( randomId(), Space.EMPTY, Markers.EMPTY, tree, suffix(entry.getExpression()), inBraces ); } @Override public J visitBreakExpression(KtBreakExpression expression, ExecutionContext data) { return new J.Break( randomId(), deepPrefix(expression), Markers.EMPTY, expression.getTargetLabel() != null ? createIdentifier(requireNonNull(expression.getTargetLabel().getIdentifier()), null) : null ); } @Override public J visitCallableReferenceExpression(KtCallableReferenceExpression expression, ExecutionContext data) { FirElement firElement = psiElementAssociations.primary(expression.getCallableReference()); if (!(firElement instanceof FirResolvedCallableReference || firElement instanceof FirCallableReferenceAccess)) { throw new UnsupportedOperationException(java.lang.String.format("Unsupported callable reference: fir class: %s, fir: %s, psi class: %s.", firElement == null ? "null" : firElement.getClass().getName(), PsiTreePrinter.print(psiElementAssociations.primary(expression)), expression.getClass().getName())); } JavaType.Method methodReferenceType = null; JavaType.Variable fieldReferenceType = null; if (firElement instanceof FirResolvedCallableReference) { FirResolvedCallableReference reference = (FirResolvedCallableReference) psiElementAssociations.primary(expression.getCallableReference()); if (reference != null && reference.getResolvedSymbol() instanceof FirNamedFunctionSymbol) { methodReferenceType = psiElementAssociations.getTypeMapping().methodDeclarationType( ((FirNamedFunctionSymbol) reference.getResolvedSymbol()).getFir(), expression.getReceiverExpression() ); } if (reference != null && reference.getResolvedSymbol() instanceof FirConstructorSymbol) { methodReferenceType = psiElementAssociations.getTypeMapping().methodDeclarationType( ((FirConstructorSymbol) reference.getResolvedSymbol()).getFir(), expression.getReceiverExpression() ); } if (reference != null && reference.getResolvedSymbol() instanceof FirPropertySymbol) { fieldReferenceType = psiElementAssociations.getTypeMapping().variableType( ((FirPropertySymbol) reference.getResolvedSymbol()).getFir(), expression.getReceiverExpression() ); } } JRightPadded receiver; if (expression.getReceiverExpression() == null) { receiver = padRight(new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY), prefix(expression.findColonColon())); } else { Expression receiverExp = convertToExpression(expression.getReceiverExpression().accept(this, data)); if (expression.getHasQuestionMarks()) { PsiElement questionMark = PsiTreeUtil.findSiblingForward(expression.getFirstChild(), KtTokens.QUEST, null); receiverExp = new J.NullableType(randomId(), receiverExp.getPrefix(), Markers.EMPTY, Collections.emptyList(), padRight(receiverExp.withPrefix(Space.EMPTY), prefix(questionMark)) ); } receiver = padRight(receiverExp, prefix(expression.findColonColon())); } return new J.MemberReference( randomId(), deepPrefix(expression), Markers.EMPTY, receiver, null, padLeft(prefix(expression.getLastChild()), expression.getCallableReference().accept(this, data).withPrefix(Space.EMPTY)), type(expression), methodReferenceType, fieldReferenceType ); } @Override public J visitCatchSection(KtCatchClause catchClause, ExecutionContext data) { J.VariableDeclarations paramDecl = (J.VariableDeclarations) requireNonNull(catchClause.getCatchParameter()).accept(this, data); J.Block body = (J.Block) requireNonNull(catchClause.getCatchBody()).accept(this, data); return new J.Try.Catch( randomId(), deepPrefix(catchClause), Markers.EMPTY, new J.ControlParentheses<>( randomId(), prefix(catchClause.getParameterList()), Markers.EMPTY, padRight(paramDecl, endFixAndSuffix(catchClause.getCatchParameter())) ), body ); } @Override public J visitClassInitializer(KtClassInitializer initializer, ExecutionContext data) { J.Block staticInit = requireNonNull(initializer.getBody()).accept(this, data).withPrefix(deepPrefix(initializer)); staticInit = staticInit.getPadding().withStatic(padRight(true, prefix(initializer.getBody()))); return staticInit; } @Override public J visitClassLiteralExpression(KtClassLiteralExpression expression, ExecutionContext data) { return new J.MemberReference( randomId(), deepPrefix(expression), Markers.EMPTY, padRight(convertToExpression(requireNonNull(expression.getReceiverExpression()).accept(this, data)), prefix(expression.findColonColon())), null, padLeft(prefix(expression.getLastChild()), createIdentifier("class", Space.EMPTY, null)), type(expression), null, null ); } @Override public J visitClassOrObject(KtClassOrObject classOrObject, ExecutionContext data) { // Should never happen, as both `visitClass()` and `visitObjectDeclaration()` are implemented throw new IllegalArgumentException("Unsupported declaration: " + classOrObject.getText()); } @Override public J visitCollectionLiteralExpression(KtCollectionLiteralExpression expression, ExecutionContext data) { JContainer elements; if (expression.getInnerExpressions().isEmpty()) { elements = JContainer.build(singletonList( padRight(new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY), prefix(expression.getRightBracket())))); } else { List> rps = new ArrayList<>(expression.getInnerExpressions().size()); for (KtExpression ktExpression : expression.getInnerExpressions()) { rps.add(padRight(convertToExpression(ktExpression.accept(this, data)), suffix(ktExpression))); } if (expression.getTrailingComma() != null) { rps = ListUtils.mapLast(rps, rp -> rp.withMarkers(rp.getMarkers() .addIfAbsent(new TrailingComma(randomId(), suffix(expression.getTrailingComma()))))); } elements = JContainer.build(Space.EMPTY, rps, Markers.EMPTY); } return new K.ListLiteral( randomId(), deepPrefix(expression), Markers.EMPTY, elements, type(expression) ); } @Override public J visitConstructorCalleeExpression(KtConstructorCalleeExpression constructorCalleeExpression, ExecutionContext data) { J j = requireNonNull(constructorCalleeExpression.getTypeReference()).accept(this, data); return j.withPrefix(merge(j.getPrefix(), deepPrefix(constructorCalleeExpression))); } @Override public J visitConstructorDelegationCall(KtConstructorDelegationCall call, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitContextReceiverList(KtContextReceiverList contextReceiverList, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitContinueExpression(KtContinueExpression expression, ExecutionContext data) { return new J.Continue( randomId(), deepPrefix(expression), Markers.EMPTY, expression.getTargetLabel() != null ? createIdentifier(requireNonNull(expression.getTargetLabel().getIdentifier()), null) : null ); } @Override public J visitDeclaration(KtDeclaration dcl, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitDelegatedSuperTypeEntry(KtDelegatedSuperTypeEntry specifier, ExecutionContext data) { TypeTree element = (TypeTree) requireNonNull(specifier.getTypeReference()).accept(this, data); Expression expr = convertToExpression(requireNonNull(specifier.getDelegateExpression()).accept(this, data)); return new K.DelegatedSuperType(randomId(), Markers.EMPTY, element, suffix(specifier.getTypeReference()), expr).withPrefix(prefix(specifier)); } @Override public J visitDestructuringDeclarationEntry(KtDestructuringDeclarationEntry multiDeclarationEntry, ExecutionContext data) { return createIdentifier(multiDeclarationEntry, type(multiDeclarationEntry)).withPrefix(prefix(multiDeclarationEntry)); } @Override public J visitDoubleColonExpression(KtDoubleColonExpression expression, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitDoWhileExpression(KtDoWhileExpression expression, ExecutionContext data) { JRightPadded body; if (expression.getBody() != null) { body = JRightPadded.build(requireNonNull(expression.getBody()).accept(this, data) .withPrefix(prefix(expression.getBody().getParent()))); } else { J.Block emptyBlock = new J.Block( randomId(), Space.EMPTY, Markers.EMPTY.add(new OmitBraces(randomId())), new JRightPadded<>(false, Space.EMPTY, Markers.EMPTY), emptyList(), Space.EMPTY ); body = padRight(emptyBlock, Space.EMPTY); } return new J.DoWhileLoop( randomId(), deepPrefix(expression), Markers.EMPTY, body, padLeft(prefix(expression.getWhileKeyword()), mapControlParentheses(requireNonNull(expression.getCondition()), data).withPrefix(prefix(expression.getLeftParenthesis()))) ); } private J.ControlParentheses mapControlParentheses(KtExpression expression, ExecutionContext data) { return new J.ControlParentheses<>( randomId(), deepPrefix(expression.getParent()), Markers.EMPTY, padRight(convertToExpression(expression.accept(this, data)) .withPrefix(prefix(expression.getParent())), suffix(expression.getParent()) ) ); } @Override public J visitDynamicType(KtDynamicType type, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitEnumEntry(KtEnumEntry enumEntry, ExecutionContext data) { List annotations = new ArrayList<>(); if (!enumEntry.getAnnotationEntries().isEmpty()) { mapModifiers(enumEntry.getModifierList(), annotations, emptyList(), data); } Set consumedSpaces = preConsumedInfix(enumEntry); J.Identifier name = createIdentifier(requireNonNull(enumEntry.getNameIdentifier()), type(enumEntry), consumedSpaces); J.NewClass initializer = null; JavaType.Method mt = methodDeclarationType(enumEntry); if (enumEntry.getInitializerList() != null) { initializer = (J.NewClass) enumEntry.getInitializerList().accept(this, data); initializer = initializer.withMethodType(mt); } if (enumEntry.getBody() != null) { J.Block body = (J.Block) enumEntry.getBody().accept(this, data); if (initializer != null) { initializer = initializer.withBody(body); } else { Markers markers = Markers.EMPTY.addIfAbsent(new Implicit(randomId())); JContainer args = JContainer.empty(); args = args.withMarkers(Markers.build(singletonList(new OmitParentheses(randomId())))); initializer = new J.NewClass( randomId(), Space.EMPTY, markers, null, Space.EMPTY, null, args, body, mt ); } } return new J.EnumValue( randomId(), deepPrefix(enumEntry), Markers.EMPTY, annotations, name, initializer ); } @Override public J visitEscapeStringTemplateEntry(KtEscapeStringTemplateEntry entry, ExecutionContext data) { return new J.Literal( randomId(), Space.EMPTY, Markers.EMPTY, entry.getText(), entry.getText(), null, JavaType.Primitive.String ).withPrefix(deepPrefix(entry)); } @Override public J visitExpression(KtExpression expression, ExecutionContext data) { if (expression instanceof KtFunctionLiteral) { KtFunctionLiteral ktFunctionLiteral = (KtFunctionLiteral) expression; Markers markers = Markers.EMPTY; ktFunctionLiteral.getLBrace(); List valueParameters = ktFunctionLiteral.getValueParameters(); List> valueParams = new ArrayList<>(valueParameters.size()); if (!valueParameters.isEmpty()) { for (int i = 0; i < valueParameters.size(); i++) { KtParameter ktParameter = valueParameters.get(i); J expr = ktParameter.accept(this, data).withPrefix(prefix(ktParameter)); valueParams.add(maybeTrailingComma(ktParameter, padRight(expr, suffix(ktParameter)), i == valueParameters.size() - 1)); } } else if (ktFunctionLiteral.getArrow() != null) { valueParams.add(padRight(new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY), Space.EMPTY)); } J.Lambda.Parameters params = new J.Lambda.Parameters(randomId(), prefix(ktFunctionLiteral.getValueParameterList()), Markers.EMPTY, false, valueParams); J.Block body = (J.Block) requireNonNull(ktFunctionLiteral.getBodyExpression()).accept(this, data); body = body.withEnd(endFixAndSuffix(ktFunctionLiteral.getBodyExpression())); return new J.Lambda( randomId(), deepPrefix(expression), markers, params, prefix(ktFunctionLiteral.getArrow()), body, null ); } throw new UnsupportedOperationException("TODO"); } @Override public J visitExpressionWithLabel(KtExpressionWithLabel expression, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitFileAnnotationList(KtFileAnnotationList fileAnnotationList, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitFinallySection(KtFinallySection finallySection, ExecutionContext data) { return finallySection.getFinalExpression().accept(this, data); } @Override public J visitFunctionType(KtFunctionType type, ExecutionContext data) { List> params; Set consumedSpaces = new HashSet<>(); if (type.getParameters().isEmpty()) { params = singletonList(JRightPadded .build(new J.Empty(randomId(), prefix(requireNonNull(requireNonNull(type.getParameterList()).getNode().findChildByType(KtTokens.RPAR)).getPsi()), Markers.EMPTY)) .withAfter(Space.EMPTY)); } else { params = new ArrayList<>(); List parameters = type.getParameters(); for (int i = 0; i < parameters.size(); i++) { KtParameter ktParameter = parameters.get(i); TypeTree typeTree; if (ktParameter.getNameIdentifier() != null) { typeTree = new K.FunctionType.Parameter( randomId(), Markers.EMPTY.addIfAbsent(new TypeReferencePrefix(randomId(), prefix(ktParameter.getColon()))), createIdentifier(ktParameter.getNameIdentifier(), type(ktParameter.getTypeReference())), (TypeTree) requireNonNull(ktParameter.getTypeReference()).accept(this, data) ).withPrefix(prefix(ktParameter)); } else { typeTree = (TypeTree) requireNonNull(ktParameter.getTypeReference()).accept(this, data); if (typeTree instanceof J.Identifier) { typeTree = mergePrefix(prefix(ktParameter), (J.Identifier) typeTree); } else { typeTree = typeTree.withPrefix(prefix(ktParameter)); } } params.add(maybeTrailingComma(ktParameter, padRight(typeTree, endFixAndSuffix(ktParameter)), i == parameters.size() - 1)); } } JContainer parameters = JContainer.build(prefix(type.getParameterList()), params, Markers.EMPTY); if (type.getFirstChild() == type.getParameterList()) { parameters = parameters.withBefore(prefix(type)); consumedSpaces.add(findFirstPrefixSpace(type)); } return new K.FunctionType( randomId(), prefix(type, consumedSpaces), Markers.EMPTY, emptyList(), // TODO emptyList(), // TODO type.getReceiver() != null ? padRight((NameTree) requireNonNull(type.getReceiverTypeReference()).accept(this, data), suffix(type.getReceiver())) : null, parameters, suffix(type.getParameterList()), padRight((TypedTree) requireNonNull(type.getReturnTypeReference()).accept(this, data), suffix(type)) ); } @Override public J visitImportAlias(KtImportAlias importAlias, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitImportList(KtImportList importList, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitInitializerList(KtInitializerList list, ExecutionContext data) { List entries = list.getInitializers(); if (entries.size() > 1) { throw new UnsupportedOperationException("TODO"); } if (!(entries.get(0) instanceof KtSuperTypeCallEntry)) { throw new UnsupportedOperationException("TODO"); } Markers markers = Markers.EMPTY.addIfAbsent(new Implicit(randomId())); KtSuperTypeCallEntry superTypeCallEntry = (KtSuperTypeCallEntry) entries.get(0); JContainer args; if (!superTypeCallEntry.getValueArguments().isEmpty()) { args = mapValueArguments(superTypeCallEntry.getValueArgumentList(), data); } else { KtValueArgumentList ktArgList = superTypeCallEntry.getValueArgumentList(); args = JContainer.build( prefix(ktArgList), singletonList(padRight(new J.Empty(randomId(), ktArgList != null ? prefix(ktArgList.getRightParenthesis()) : Space.EMPTY, Markers.EMPTY), Space.EMPTY) ), markers ); } return new J.NewClass( randomId(), deepPrefix(list), markers, null, Space.EMPTY, null, args, null, null // TODO ); } @Override public J visitIntersectionType(KtIntersectionType definitelyNotNullType, ExecutionContext data) { List> rps = new ArrayList<>(2); TypeTree left = (TypeTree) requireNonNull(definitelyNotNullType.getLeftTypeRef()).accept(this, data); TypeTree right = (TypeTree) requireNonNull(definitelyNotNullType.getRightTypeRef()).accept(this, data); rps.add(padRight(left, suffix(definitelyNotNullType.getLeftTypeRef()))); rps.add(padRight(right, Space.EMPTY)); JContainer bounds = JContainer.build( Space.EMPTY, rps, Markers.EMPTY ); return new J.IntersectionType(randomId(), prefix(definitelyNotNullType), Markers.EMPTY, bounds); } @Override public J visitIsExpression(KtIsExpression expression, ExecutionContext data) { Markers markers = Markers.EMPTY; Expression element = convertToExpression(expression.getLeftHandSide().accept(this, data)); if (expression.getOperationReference().getReferencedNameElementType() == KtTokens.NOT_IS) { markers = markers.addIfAbsent(new NotIs(randomId())); } J clazz = requireNonNull(expression.getTypeReference()).accept(this, data); return new J.InstanceOf( randomId(), deepPrefix(expression), markers, padRight(element, prefix(expression.getOperationReference())), clazz, null, type(expression) ); } @Override public J visitLabeledExpression(KtLabeledExpression expression, ExecutionContext data) { J j = requireNonNull(expression.getBaseExpression()).accept(this, data); return new J.Label( randomId(), deepPrefix(expression), Markers.EMPTY, padRight(createIdentifier(requireNonNull(expression.getNameIdentifier()), null), suffix(expression.getNameIdentifier()) ), convertToStatement(j) ); } @Override public J visitLambdaExpression(KtLambdaExpression expression, ExecutionContext data) { KtFunctionLiteral functionLiteral = expression.getFunctionLiteral(); return functionLiteral.accept(this, data).withPrefix(prefix(expression)); } @Override public J visitLiteralStringTemplateEntry(KtLiteralStringTemplateEntry entry, ExecutionContext data) { if (!(entry.getFirstChild() instanceof LeafPsiElement)) { throw new UnsupportedOperationException("Unsupported KtStringTemplateEntry child"); } String value = maybeAdjustCRLF(entry); boolean quoted = entry.getPrevSibling().getNode().getElementType() == KtTokens.OPEN_QUOTE && entry.getNextSibling().getNode().getElementType() == KtTokens.CLOSING_QUOTE; String valueSource = quoted ? "\"" + value + "\"" : value; return new J.Literal( Tree.randomId(), Space.EMPTY, Markers.EMPTY, value, valueSource, null, JavaType.Primitive.String ); } @Override public J visitLoopExpression(KtLoopExpression loopExpression, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitModifierList(KtModifierList list, ExecutionContext data) { throw new UnsupportedOperationException("Use mapModifiers instead"); } @Override public J visitNamedDeclaration(KtNamedDeclaration declaration, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitNullableType(KtNullableType nullableType, ExecutionContext data) { KtTypeElement innerType = nullableType.getInnerType(); if (innerType == null) { throw new UnsupportedOperationException("This should never happen"); } TypeTree typeTree = (TypeTree) requireNonNull(innerType).accept(this, data); Set consumedSpaces = new HashSet<>(); if (innerType.getNextSibling() != null && isSpace(innerType.getNextSibling().getNode()) && !(innerType instanceof KtNullableType)) { consumedSpaces.add(innerType.getNextSibling()); } if (typeTree instanceof K.FunctionType && nullableType.getModifierList() != null) { List leadingAnnotations = new ArrayList<>(); List modifiers = mapModifiers(nullableType.getModifierList(), leadingAnnotations, emptyList(), data); if (!leadingAnnotations.isEmpty()) { leadingAnnotations = ListUtils.mapFirst(leadingAnnotations, anno -> anno.withPrefix(merge(deepPrefix(nullableType.getModifierList()), anno.getPrefix()))); } else if (!modifiers.isEmpty()) { modifiers = ListUtils.mapFirst(modifiers, mod -> mod.withPrefix(merge(deepPrefix(nullableType.getModifierList()), mod.getPrefix()))); } typeTree = ((K.FunctionType) typeTree).withModifiers(modifiers).withLeadingAnnotations(leadingAnnotations); } // Handle parentheses or potential nested parentheses Stack> parenPairs = new Stack<>(); List allChildren = getAllChildren(nullableType); TypeTree j = typeTree; int l = 0; int r = allChildren.size() - 1; while (l < r) { l = findFirstLPAR(allChildren, l); r = findLastRPAR(allChildren, r); if (l * r < 0) { throw new UnsupportedOperationException("Unpaired parentheses!"); } if (l < 0 || r < 0) { break; } parenPairs.add(new Pair<>(l++, r--)); } while (!parenPairs.empty()) { Pair parenPair = parenPairs.pop(); PsiElement lPAR = allChildren.get(parenPair.getFirst()); PsiElement rPAR = allChildren.get(parenPair.getSecond()); j = new J.ParenthesizedTypeTree(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), new J.Parentheses<>(randomId(), prefix(lPAR), Markers.EMPTY, padRight(j, prefix(rPAR, consumedSpaces))) ); } return new J.NullableType(randomId(), merge(deepPrefix(nullableType), j.getPrefix()), Markers.EMPTY, Collections.emptyList(), padRight(j, prefix(findFirstChild(nullableType, c -> c.getNode().getElementType() == KtTokens.QUEST))) ); } @Override public J visitParameter(KtParameter parameter, ExecutionContext data) { Markers markers = Markers.EMPTY; List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); List modifiers = new ArrayList<>(); TypeTree typeExpression = null; List> vars = new ArrayList<>(1); Set consumedSpaces = preConsumedInfix(parameter); // todo, simplify this logic int valOrVarOffset = parameter.getValOrVarKeyword() != null ? parameter.getValOrVarKeyword().getTextOffset() : -1; int modifierOffset = parameter.getModifierList() != null ? parameter.getModifierList().getTextOffset() : -1; if (valOrVarOffset < modifierOffset) { if (parameter.getValOrVarKeyword() != null) { modifiers.add(mapModifier(parameter.getValOrVarKeyword(), Collections.emptyList(), consumedSpaces)); } if (parameter.getModifierList() != null) { modifiers.addAll(mapModifiers(parameter.getModifierList(), leadingAnnotations, lastAnnotations, data)); } } else { if (parameter.getModifierList() != null) { modifiers.addAll(mapModifiers(parameter.getModifierList(), leadingAnnotations, lastAnnotations, data)); } if (parameter.getValOrVarKeyword() != null) { modifiers.add(mapModifier(parameter.getValOrVarKeyword(), lastAnnotations, consumedSpaces)); } } if (parameter.getDestructuringDeclaration() != null) { return mapDestructuringDeclaration(parameter.getDestructuringDeclaration(), data) .withPrefix(prefix(parameter)); } JavaType.Variable vt = variableType(parameter, owner(parameter)); J.Identifier name = createIdentifier(requireNonNull(parameter.getNameIdentifier()), vt, consumedSpaces); if (parameter.getTypeReference() != null) { typeExpression = (TypeTree) parameter.getTypeReference().accept(this, data); // TODO: get type from IR of KtProperty. } JLeftPadded initializer = parameter.getDefaultValue() != null ? padLeft(prefix(parameter.getEqualsToken()), convertToExpression(parameter.getDefaultValue().accept(this, data))) : null; J.VariableDeclarations.NamedVariable namedVariable = new J.VariableDeclarations.NamedVariable( randomId(), Space.EMPTY, Markers.EMPTY, name, emptyList(), initializer, vt ); vars.add(padRight(namedVariable, prefix(parameter.getColon()))); return new J.VariableDeclarations( randomId(), deepPrefix(parameter), markers, leadingAnnotations, modifiers, typeExpression, null, emptyList(), vars ); } @Override public J visitParameterList(KtParameterList list, ExecutionContext data) { throw new UnsupportedOperationException("Unsupported, use mapParameters() instead"); } @Override public J visitPrimaryConstructor(KtPrimaryConstructor constructor, ExecutionContext data) { if (constructor.getBodyExpression() != null) { throw new UnsupportedOperationException("TODO"); } List leadingAnnotations = new ArrayList<>(); List modifiers = new ArrayList<>(); Set consumedSpaces = preConsumedInfix(constructor); if (constructor.getModifierList() != null) { KtModifierList ktModifierList = constructor.getModifierList(); modifiers.addAll(mapModifiers(ktModifierList, leadingAnnotations, emptyList(), data)); } if (constructor.getConstructorKeyword() != null) { modifiers.add(mapModifier(constructor.getConstructorKeyword(), Collections.emptyList(), consumedSpaces)); } JavaType.Method type = methodDeclarationType(constructor); J.Identifier name = new J.Identifier( randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "", type, null ); JContainer params; if (constructor.getValueParameterList() != null) { params = JContainer.build( prefix(constructor.getValueParameterList(), preConsumedInfix(constructor)), mapParameters(constructor.getValueParameterList(), data), Markers.EMPTY); } else { throw new UnsupportedOperationException("TODO"); } return mapType(new J.MethodDeclaration( randomId(), deepPrefix(constructor), Markers.build(singletonList(new PrimaryConstructor(randomId()))), leadingAnnotations, modifiers, null, null, new J.MethodDeclaration.IdentifierWithAnnotations( name.withMarkers(name.getMarkers().addIfAbsent(new Implicit(randomId()))), emptyList() ), params, null, null, null, type )); } @Override public J visitPropertyAccessor(KtPropertyAccessor accessor, ExecutionContext data) { Markers markers = Markers.EMPTY; List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); List modifiers = mapModifiers(accessor.getModifierList(), leadingAnnotations, lastAnnotations, data); TypeTree returnTypeExpression = null; JContainer params; J.Block body = null; Set consumedSpaces = preConsumedInfix(accessor); JavaType.Method type = methodDeclarationType(accessor); J.Identifier name = createIdentifier(accessor.getNamePlaceholder().getText(), prefix(accessor.getNamePlaceholder(), consumedSpaces), type); List ktParameters = accessor.getValueParameters(); if (!ktParameters.isEmpty()) { if (ktParameters.size() != 1) { throw new UnsupportedOperationException("TODO"); } List> parameters = new ArrayList<>(); for (KtParameter ktParameter : ktParameters) { Statement stmt = convertToStatement(ktParameter.accept(this, data).withPrefix(prefix(ktParameter.getParent()))); parameters.add(padRight(stmt, prefix(accessor.getRightParenthesis()))); } params = JContainer.build(prefix(accessor.getLeftParenthesis()), parameters, Markers.EMPTY); } else { params = JContainer.build( prefix(accessor.getLeftParenthesis()), singletonList(padRight(new J.Empty(randomId(), prefix(accessor.getRightParenthesis()), Markers.EMPTY), Space.EMPTY)), Markers.EMPTY ); } if (accessor.getReturnTypeReference() != null) { markers = markers.addIfAbsent(new TypeReferencePrefix(randomId(), suffix(accessor.getRightParenthesis()))); returnTypeExpression = accessor.getReturnTypeReference().accept(this, data).withPrefix(prefix(accessor.getReturnTypeReference())); } if (accessor.getBodyBlockExpression() != null) { body = accessor.getBodyBlockExpression().accept(this, data).withPrefix(prefix(accessor.getBodyBlockExpression())); } else if (accessor.getBodyExpression() != null) { body = convertToBlock(accessor.getBodyExpression(), data).withPrefix(prefix(accessor.getEqualsToken())); } else { params = JContainer.empty(); params = params.withBefore(Space.EMPTY) .withMarkers(Markers.EMPTY.addIfAbsent(new OmitParentheses(randomId()))); } return mapType(new J.MethodDeclaration( randomId(), deepPrefix(accessor), markers, leadingAnnotations, modifiers, null, returnTypeExpression, new J.MethodDeclaration.IdentifierWithAnnotations( name, lastAnnotations ), params, null, body, null, type )); } @Override public J visitQualifiedExpression(KtQualifiedExpression expression, ExecutionContext data) { Expression receiver = convertToExpression(expression.getReceiverExpression().accept(this, data)); Expression selector = convertToExpression(requireNonNull(expression.getSelectorExpression()).accept(this, data)); if (selector instanceof J.MethodInvocation) { J.MethodInvocation methodInvocation = (J.MethodInvocation) selector; return methodInvocation.getPadding() .withSelect(padRight(receiver, suffix(expression.getReceiverExpression()))) .withName(methodInvocation.getName().withPrefix(prefix(expression.getSelectorExpression()))) .withPrefix(endFixPrefixAndInfix(expression)); } else { J.Identifier identifier = (J.Identifier) selector; return mapType(new J.FieldAccess( randomId(), deepPrefix(expression), Markers.EMPTY, receiver, padLeft(suffix(expression.getReceiverExpression()), identifier), type(expression) )); } } @Override public J visitReferenceExpression(KtReferenceExpression expression, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitReturnExpression(KtReturnExpression expression, ExecutionContext data) { KtExpression returnedExpression = expression.getReturnedExpression(); Expression returnExpr = returnedExpression != null ? convertToExpression(returnedExpression.accept(this, data).withPrefix(prefix(returnedExpression))) : null; return new K.Return( randomId(), new J.Return( randomId(), deepPrefix(expression), Markers.EMPTY, returnExpr ), expression.getTargetLabel() != null ? createIdentifier(requireNonNull(expression.getTargetLabel().getIdentifier()), null) : null ); } @Override public J visitSafeQualifiedExpression(KtSafeQualifiedExpression expression, ExecutionContext data) { J j = visitQualifiedExpression(expression, data); return j.withMarkers(j.getMarkers().addIfAbsent(new IsNullSafe(randomId()))); } @Override public J visitScript(KtScript script, ExecutionContext data) { return script.getBlockExpression().accept(this, data); } @Override public J visitScriptInitializer(KtScriptInitializer initializer, ExecutionContext data) { J j = requireNonNull(initializer.getBody()).accept(this, data); return j.withPrefix(merge(deepPrefix(initializer), j.getPrefix())); } @Override public J visitSecondaryConstructor(KtSecondaryConstructor constructor, ExecutionContext data) { Markers markers = Markers.EMPTY; List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); List modifiers = mapModifiers(constructor.getModifierList(), leadingAnnotations, lastAnnotations, data); modifiers.add(mapModifier(constructor.getConstructorKeyword(), lastAnnotations, preConsumedInfix(constructor))); JavaType.Method type = methodDeclarationType(constructor); J.Identifier name = createIdentifier(requireNonNull(constructor.getName()), prefix(constructor.getConstructorKeyword()), type) .withMarkers(Markers.EMPTY.addIfAbsent(new Implicit(randomId()))); List> statements = mapParameters(constructor.getValueParameterList(), data); JContainer params = JContainer.build( prefix(constructor.getValueParameterList()), statements, Markers.EMPTY ); K.ConstructorInvocation delegationCall = constructor.getDelegationCall().isImplicit() ? null : new K.ConstructorInvocation( randomId(), prefix(constructor.getDelegationCall()), Markers.EMPTY, createIdentifier(requireNonNull(constructor.getDelegationCall().getCalleeExpression()), type(constructor.getDelegationCall().getCalleeExpression())), mapValueArguments(constructor.getDelegationCall().getValueArgumentList(), data) ); J.Block body = null; if (constructor.getBodyExpression() != null) { body = (J.Block) constructor.getBodyExpression().accept(this, data); } J.MethodDeclaration methodDeclaration = mapType(new J.MethodDeclaration( randomId(), deepPrefix(constructor), markers, leadingAnnotations, modifiers, null, null, new J.MethodDeclaration.IdentifierWithAnnotations(name, emptyList()), params, null, body, null, type )); return delegationCall != null ? new K.Constructor(randomId(), Markers.EMPTY, methodDeclaration, padLeft(prefix(constructor.getColon()), delegationCall)) : methodDeclaration; } @Override public J visitSelfType(KtSelfType type, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitSimpleNameStringTemplateEntry(KtSimpleNameStringTemplateEntry entry, ExecutionContext data) { return new K.StringTemplate.Expression( randomId(), Space.EMPTY, Markers.EMPTY, requireNonNull(entry.getExpression()).accept(this, data), suffix(entry.getExpression()), false ); } @Override public J visitStringTemplateEntryWithExpression(KtStringTemplateEntryWithExpression entry, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitSuperExpression(KtSuperExpression expression, ExecutionContext data) { return createIdentifier(expression, type(expression)).withPrefix(prefix(expression)); } @Override public J visitSuperTypeEntry(KtSuperTypeEntry specifier, ExecutionContext data) { J j = requireNonNull(specifier.getTypeReference()).accept(this, data); if (j instanceof J.Identifier) { J.Identifier ident = (J.Identifier) j; if (!ident.getAnnotations().isEmpty()) { j = ident.withAnnotations(ListUtils.mapFirst(ident.getAnnotations(), a -> a.withPrefix(prefix(specifier.getParent())))); } else { j = ident.withPrefix(prefix(specifier)); } return j; } return j.withPrefix(merge(deepPrefix(specifier), j.getPrefix())); } @Override public J visitSuperTypeListEntry(KtSuperTypeListEntry specifier, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitThisExpression(KtThisExpression expression, ExecutionContext data) { return new K.This( randomId(), deepPrefix(expression), Markers.EMPTY, expression.getTargetLabel() != null ? createIdentifier(requireNonNull(expression.getTargetLabel().getIdentifier()), null) : null, type(expression) ); } @Override public J visitThrowExpression(KtThrowExpression expression, ExecutionContext data) { return new J.Throw( randomId(), prefix(expression), Markers.EMPTY, convertToExpression(requireNonNull(expression.getThrownExpression()).accept(this, data)) ); } @Override public J visitTryExpression(KtTryExpression expression, ExecutionContext data) { List ktCatchClauses = expression.getCatchClauses(); J.Block block = (J.Block) expression.getTryBlock().accept(this, data); List catches = new ArrayList<>(ktCatchClauses.size()); JLeftPadded finallyBlock = null; for (KtCatchClause catchClause : ktCatchClauses) { catches.add((J.Try.Catch) catchClause.accept(this, data)); } if (expression.getFinallyBlock() != null) { finallyBlock = padLeft(prefix(expression.getFinallyBlock()), (J.Block) expression.getFinallyBlock().accept(this, data)); } return new J.Try( randomId(), deepPrefix(expression), Markers.EMPTY, null, block, catches, finallyBlock ); } @Override public J visitTypeAlias(KtTypeAlias typeAlias, ExecutionContext data) { Markers markers = Markers.EMPTY; List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); Set consumedSpaces = preConsumedInfix(typeAlias); List modifiers = mapModifiers(typeAlias.getModifierList(), leadingAnnotations, lastAnnotations, data); modifiers.add(new J.Modifier(randomId(), prefix(typeAlias.getTypeAliasKeyword(), consumedSpaces), markers, "typealias", J.Modifier.Type.LanguageExtension, emptyList())); if (typeAlias.getIdentifyingElement() == null) { throw new UnsupportedOperationException("TODO"); } J.Identifier name = createIdentifier(typeAlias.getIdentifyingElement(), type(typeAlias.getTypeReference())); JContainer typeParams = null; if (typeAlias.getTypeParameterList() != null) { typeParams = JContainer.build(prefix(typeAlias.getTypeParameterList()), mapTypeParameters(typeAlias.getTypeParameterList(), data), Markers.EMPTY); } Expression expr = convertToExpression(typeAlias.getTypeReference().accept(this, data)); ASTNode node = typeAlias.getNode().findChildByType(KtTokens.EQ); Space prefix = node != null ? prefix(node.getPsi()) : Space.EMPTY; return new K.TypeAlias( randomId(), deepPrefix(typeAlias), markers, leadingAnnotations, modifiers, name, typeParams, padLeft(prefix, expr), type(typeAlias.getTypeReference()) ); } @Override public J visitTypeArgumentList(KtTypeArgumentList typeArgumentList, ExecutionContext data) { throw new UnsupportedOperationException("use mapTypeArguments instead"); } @Override public J visitTypeConstraint(KtTypeConstraint constraint, ExecutionContext data) { List annotations = new ArrayList<>(); J.Identifier typeParamName = (J.Identifier) requireNonNull(constraint.getSubjectTypeParameterName()).accept(this, data); PsiElement ref = PsiTreeUtil.getChildOfType(constraint, KtTypeReference.class); typeParamName = typeParamName.withType(psiElementAssociations.type(ref, owner(constraint))); TypeTree typeTree = (TypeTree) requireNonNull(constraint.getBoundTypeReference()).accept(this, data); return new J.TypeParameter( randomId(), deepPrefix(constraint), Markers.EMPTY.addIfAbsent(new TypeReferencePrefix(randomId(), suffix(constraint.getSubjectTypeParameterName()))), annotations, emptyList(), typeParamName, JContainer.build( Space.EMPTY, singletonList(padRight(typeTree, null)), Markers.EMPTY ) ); } @Override public J visitTypeConstraintList(KtTypeConstraintList list, ExecutionContext data) { List ktTypeConstraints = list.getConstraints(); List> params = new ArrayList<>(ktTypeConstraints.size()); for (KtTypeConstraint ktTypeConstraint : ktTypeConstraints) { params.add(padRight((J.TypeParameter) ktTypeConstraint.accept(this, data), suffix(ktTypeConstraint))); } return new K.TypeConstraints( randomId(), Markers.EMPTY, JContainer.build(deepPrefix(list), params, Markers.EMPTY) ); } @Override public J visitTypeParameter(KtTypeParameter parameter, ExecutionContext data) { Markers markers = Markers.EMPTY; List annotations = new ArrayList<>(); JContainer bounds = null; if (parameter.getNameIdentifier() == null) { throw new UnsupportedOperationException("This should never happen"); } if (parameter.getExtendsBound() != null) { bounds = JContainer.build(suffix(parameter.getNameIdentifier()), singletonList(padRight((TypeTree) parameter.getExtendsBound().accept(this, data), Space.EMPTY)), Markers.EMPTY); markers = markers.addIfAbsent(new TypeReferencePrefix(randomId(), Space.EMPTY)); } return new J.TypeParameter( randomId(), deepPrefix(parameter), markers, annotations, mapModifiers(parameter.getModifierList(), annotations, emptyList(), data), createIdentifier(parameter.getNameIdentifier(), type(parameter)), bounds ); } @Override public J visitTypeParameterList(KtTypeParameterList list, ExecutionContext data) { List ktTypeParameters = list.getParameters(); List> expressions = new ArrayList<>(ktTypeParameters.size()); for (KtTypeParameter ktTypeParameter : ktTypeParameters) { J.Identifier name = createIdentifier(ktTypeParameter, type(ktTypeParameter)); expressions.add(padRight(name, suffix(ktTypeParameter))); } JContainer typeParameters = JContainer.build( deepPrefix(list), expressions, Markers.EMPTY ); return new J.ParameterizedType( randomId(), Space.EMPTY, Markers.EMPTY, null, typeParameters, null ); } @Override public J visitTypeProjection(KtTypeProjection typeProjection, ExecutionContext data) { Markers markers = Markers.EMPTY; JContainer bounds = null; Expression name = null; List leadingAnnotations = new ArrayList<>(); List modifiers = mapModifiers(typeProjection.getModifierList(), leadingAnnotations, emptyList(), data); switch (typeProjection.getProjectionKind()) { case IN: case OUT: { bounds = JContainer.build( prefix(typeProjection.getProjectionToken()), singletonList(padRight(requireNonNull(typeProjection.getTypeReference()).accept(this, data) .withPrefix(prefix(typeProjection.getTypeReference())), Space.EMPTY)), Markers.EMPTY ); break; } case STAR: { return new J.Wildcard(randomId(), prefix(typeProjection), Markers.EMPTY, null, null); } default: { name = convertToExpression(requireNonNull(typeProjection.getTypeReference()).accept(this, data)); Space prefix = deepPrefix(typeProjection); if (name instanceof J.Identifier) { name = addPrefixInFront((J.Identifier) name, prefix); } else { name = name.withPrefix(merge(prefix, name.getPrefix())); } } } if (name != null) { return name; } return new K.TypeParameterExpression(randomId(), new J.TypeParameter( randomId(), deepPrefix(typeProjection), markers, leadingAnnotations, modifiers, new J.Identifier( randomId(), Space.EMPTY, Markers.build(singletonList(new Implicit(randomId()))), emptyList(), "Any", null, null ), bounds )); } @Override public J visitUnaryExpression(KtUnaryExpression expression, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } @Override public J visitWhenConditionInRange(KtWhenConditionInRange condition, ExecutionContext data) { K.Binary.Type operator = condition.isNegated() ? K.Binary.Type.NotContains : K.Binary.Type.Contains; Expression left = new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY); return new K.Binary(randomId(), deepPrefix(condition), Markers.EMPTY, left, padLeft(Space.EMPTY, operator), convertToExpression(requireNonNull(condition.getRangeExpression()).accept(this, data)), Space.EMPTY, type(condition) ); } @Override public J visitWhenConditionIsPattern(KtWhenConditionIsPattern condition, ExecutionContext data) { Markers markers = Markers.EMPTY; if (condition.isNegated()) { markers = markers.addIfAbsent(new NotIs(randomId())); } Expression element = new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY); JRightPadded expr = padRight(element, Space.EMPTY); J clazz = requireNonNull(condition.getTypeReference()).accept(this, data); return new J.InstanceOf( randomId(), deepPrefix(condition), markers, expr, clazz, null, type(condition) ); } @Override public J visitWhenConditionWithExpression(KtWhenConditionWithExpression condition, ExecutionContext data) { return requireNonNull(condition.getExpression()).accept(this, data) .withPrefix(deepPrefix(condition)); } @Override public J visitWhenEntry(KtWhenEntry ktWhenEntry, ExecutionContext data) { List> expressions = new ArrayList<>(1); if (ktWhenEntry.getElseKeyword() != null) { expressions.add(padRight(createIdentifier("else", Space.EMPTY, null, null), prefix(ktWhenEntry.getArrow()))); } else { KtWhenCondition[] ktWhenConditions = ktWhenEntry.getConditions(); for (int i = 0; i < ktWhenConditions.length; i++) { KtWhenCondition ktWhenCondition = ktWhenConditions[i]; Expression expr = convertToExpression(ktWhenCondition.accept(this, data)); expressions.add(maybeTrailingComma(ktWhenCondition, padRight(expr, suffix(ktWhenCondition)), i == ktWhenConditions.length - 1)); } } JContainer expressionContainer = JContainer.build(Space.EMPTY, expressions, Markers.EMPTY); J body = requireNonNull(ktWhenEntry.getExpression()).accept(this, data); return new K.WhenBranch( randomId(), deepPrefix(ktWhenEntry), Markers.EMPTY, expressionContainer, padRight(body, Space.EMPTY) ); } private JRightPadded maybeTrailingComma(KtElement element, JRightPadded padded, boolean last) { if (!last) { return padded; } PsiElement maybeComma = PsiTreeUtil.findSiblingForward(element, KtTokens.COMMA, null); if (maybeComma != null && maybeComma.getNode().getElementType() == KtTokens.COMMA) { padded = padded.withMarkers(padded.getMarkers().addIfAbsent(new TrailingComma(randomId(), suffix(maybeComma)))); } return padded; } @Override public J visitWhenExpression(KtWhenExpression expression, ExecutionContext data) { J.ControlParentheses controlParentheses = null; if (expression.getSubjectExpression() != null) { J subject = expression.getSubjectExpression().accept(this, data); controlParentheses = new J.ControlParentheses<>( randomId(), prefix(expression.getLeftParenthesis()), Markers.EMPTY, padRight(subject, prefix(expression.getRightParenthesis())) ); } List whenEntries = expression.getEntries(); List> statements = new ArrayList<>(whenEntries.size()); for (KtWhenEntry whenEntry : whenEntries) { K.WhenBranch whenBranch = (K.WhenBranch) whenEntry.accept(this, data); statements.add(maybeTrailingSemicolon(whenBranch, whenEntry)); } J.Block body = new J.Block( randomId(), prefix(expression.getOpenBrace()), Markers.EMPTY, new JRightPadded<>(false, Space.EMPTY, Markers.EMPTY), statements, prefix(expression.getCloseBrace()) ); return new K.When( randomId(), deepPrefix(expression), Markers.EMPTY, controlParentheses, body, type(expression) ); } @Override public J visitWhileExpression(KtWhileExpression expression, ExecutionContext data) { return new J.WhileLoop( randomId(), deepPrefix(expression), Markers.EMPTY, mapControlParentheses(requireNonNull(expression.getCondition()), data).withPrefix(prefix(expression.getLeftParenthesis())), expression.getBody() == null ? JRightPadded.build(new J.Empty(randomId(), suffix(expression.getRightParenthesis()), Markers.EMPTY)) : JRightPadded.build(requireNonNull(expression.getBody()).accept(this, data).withPrefix(prefix(expression.getBody().getParent()))) ); } @Override public void visitBinaryFile(PsiBinaryFile file) { throw new UnsupportedOperationException("TODO"); } @Override public void visitComment(PsiComment comment) { throw new UnsupportedOperationException("TODO"); } @Override public void visitDirectory(PsiDirectory dir) { throw new UnsupportedOperationException("TODO"); } @Override public J visitKtElement(KtElement element, ExecutionContext data) { throw new UnsupportedOperationException("Should never call this, if this is called, means something wrong"); // return element.accept(this, data); } @Override public J visitKtFile(KtFile file, ExecutionContext data) { List annotations = file.getFileAnnotationList() != null ? mapAnnotations(file.getAnnotationEntries(), data) : emptyList(); Set consumedSpaces = new HashSet<>(); Space eof = endFixAndSuffix(file); String shebang = null; Space spaceAfterShebang = null; PsiElement maybeShebang = file.getFirstChild(); if (maybeShebang instanceof PsiComment && maybeShebang.getNode().getElementType() == KtTokens.SHEBANG_COMMENT) { shebang = maybeShebang.getText(); spaceAfterShebang = suffix(maybeShebang); } JRightPadded pkg = null; if (!file.getPackageFqName().isRoot()) { pkg = maybeTrailingSemicolon((J.Package) requireNonNull(file.getPackageDirective()).accept(this, data), file.getPackageDirective()); spaceAfterShebang = null; consumedSpaces.add(findFirstPrefixSpace(file.getPackageDirective())); } List> imports = new ArrayList<>(file.getImportDirectives().size()); if (!file.getImportDirectives().isEmpty()) { List importDirectives = file.getImportDirectives(); for (int i = 0; i < importDirectives.size(); i++) { KtImportDirective importDirective = importDirectives.get(i); J.Import anImport = (J.Import) importDirective.accept(this, data); if (i == 0) { anImport = anImport.withPrefix(merge(prefix(file.getImportList()), anImport.getPrefix())); if (spaceAfterShebang != null) { anImport = anImport.withPrefix(merge(spaceAfterShebang, anImport.getPrefix())); spaceAfterShebang = null; } } imports.add(maybeTrailingSemicolon(anImport, importDirective)); } } List> statements = new ArrayList<>(file.getDeclarations().size()); List declarations = file.getDeclarations(); for (KtDeclaration declaration : declarations) { Statement statement; try { statement = convertToStatement(declaration.accept(this, data)); if (spaceAfterShebang != null) { statement = statement.withPrefix(merge(spaceAfterShebang, statement.getPrefix())); spaceAfterShebang = null; } } catch (Exception e) { statement = new J.Unknown( randomId(), deepPrefix(declaration), Markers.EMPTY, new J.Unknown.Source( randomId(), Space.EMPTY, Markers.build(singletonList(ParseExceptionResult.build(KotlinParser.builder().build(), e) .withTreeType(declaration.getClass().getName()))), file.getText().substring(PsiUtilsKt.getStartOffsetSkippingComments(declaration), declaration.getTextRange().getEndOffset()))); } statements.add(maybeTrailingSemicolon(statement, declaration)); } return new K.CompilationUnit( Tree.randomId(), shebang, prefixAndInfix(file, consumedSpaces), Markers.build(styles), sourcePath, fileAttributes, charset.name(), charsetBomMarked, null, annotations, pkg, imports, statements, eof ); } @Override public J visitAnnotation(KtAnnotation annotation, ExecutionContext data) { Expression target; if (annotation.getUseSiteTarget() != null) { target = (J.Identifier) annotation.getUseSiteTarget().accept(this, data); } else { target = new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY); } List annotationEntries = annotation.getEntries(); List> rpAnnotations = new ArrayList<>(annotationEntries.size()); J.Annotation anno = null; for (KtAnnotationEntry ktAnnotationEntry : annotationEntries) { anno = (J.Annotation) ktAnnotationEntry.accept(this, data); anno = anno.withMarkers(anno.getMarkers().addIfAbsent(new AnnotationConstructor(randomId()))); rpAnnotations.add(padRight(anno, Space.EMPTY)); } PsiElement maybeLBracket = findFirstChild(annotation, an -> an.getNode().getElementType() == KtTokens.LBRACKET); boolean isImplicitBracket = maybeLBracket == null; Space beforeLBracket = isImplicitBracket ? Space.EMPTY : prefix(maybeLBracket); if (!isImplicitBracket) { rpAnnotations = ListUtils.mapLast(rpAnnotations, rp -> rp.withAfter(prefix(findFirstChild(annotation, an -> an.getNode().getElementType() == KtTokens.RBRACKET)))); } NameTree annotationType; if (isImplicitBracket) { annotationType = new K.AnnotationType(randomId(), Space.EMPTY, Markers.EMPTY, padRight(target, suffix(annotation.getUseSiteTarget())), anno); } else { annotationType = new K.MultiAnnotationType(randomId(), Space.EMPTY, Markers.EMPTY, padRight(target, suffix(annotation.getUseSiteTarget())), JContainer.build(beforeLBracket, rpAnnotations, Markers.EMPTY)); } return mapType(new J.Annotation(randomId(), deepPrefix(annotation), Markers.EMPTY, annotationType, null )); } @Override public J visitAnnotationEntry(KtAnnotationEntry annotationEntry, ExecutionContext data) { Markers markers = Markers.EMPTY; NameTree nameTree; JContainer args = null; boolean isUseSite = annotationEntry.getUseSiteTarget() != null; if (isUseSite) { isUseSite = findFirstChild(annotationEntry, c -> c == annotationEntry.getUseSiteTarget()) != null; } if (isUseSite) { J.Annotation callee = new J.Annotation( randomId(), Space.EMPTY, Markers.EMPTY.addIfAbsent(new AnnotationConstructor(randomId())), (NameTree) requireNonNull(annotationEntry.getCalleeExpression()).accept(this, data), annotationEntry.getValueArgumentList() != null ? mapValueArguments(annotationEntry.getValueArgumentList(), data) : null ); nameTree = new K.AnnotationType(randomId(), Space.EMPTY, Markers.EMPTY, padRight(convertToExpression(annotationEntry.getUseSiteTarget().accept(this, data)), prefix(findFirstChild(annotationEntry, p -> p.getNode().getElementType() == KtTokens.COLON))), callee); } else { nameTree = (NameTree) requireNonNull(annotationEntry.getCalleeExpression()).accept(this, data); if (annotationEntry.getValueArgumentList() != null) { args = mapValueArguments(annotationEntry.getValueArgumentList(), data); } } return mapType(new J.Annotation( randomId(), deepPrefix(annotationEntry), markers, nameTree, args )); } @Override public J visitArgument(KtValueArgument argument, ExecutionContext data) { if (argument.getArgumentExpression() == null) { throw new UnsupportedOperationException("TODO"); } else if (argument.isNamed()) { J.Identifier name = createIdentifier(requireNonNull(argument.getArgumentName()), type(argument.getArgumentName())); Expression expr = convertToExpression(argument.getArgumentExpression().accept(this, data)); if (argument.isSpread()) { expr = new K.SpreadArgument( randomId(), prefix(findFirstChild(argument, c -> c.getNode().getElementType() == KtTokens.MUL)), Markers.EMPTY, expr ); } return mapType(new J.Assignment( randomId(), deepPrefix(argument), Markers.EMPTY, name, padLeft(suffix(argument.getArgumentName()), expr), type(argument.getArgumentExpression()) )); } else if (argument.isSpread()) { Expression j = (Expression) argument.getArgumentExpression().accept(this, data); return new K.SpreadArgument( randomId(), deepPrefix(argument), Markers.EMPTY, j ); } J j = argument.getArgumentExpression().accept(this, data).withPrefix(deepPrefix(argument)); return argument instanceof KtLambdaArgument ? j.withMarkers(j.getMarkers().addIfAbsent(new TrailingLambdaArgument(randomId()))) : j; } @Override public J visitBinaryExpression(KtBinaryExpression expression, ExecutionContext data) { assert expression.getLeft() != null; assert expression.getRight() != null; KtOperationReferenceExpression operationReference = expression.getOperationReference(); J.Binary.Type javaBinaryType = mapJBinaryType(operationReference); J.AssignmentOperation.Type assignmentOperationType = javaBinaryType == null ? mapAssignmentOperationType(operationReference) : null; K.Binary.Type kotlinBinaryType = javaBinaryType == null && assignmentOperationType == null ? mapKBinaryType(operationReference) : null; Expression left = convertToExpression(expression.getLeft().accept(this, data)).withPrefix(Space.EMPTY); Expression right = convertToExpression((expression.getRight()).accept(this, data)) .withPrefix(prefix(expression.getRight())); JavaType type = type(expression); // FIXME: This requires detection of infix overrides and operator overloads. if (javaBinaryType != null) { return mapType(new J.Binary( randomId(), deepPrefix(expression), Markers.EMPTY, left, padLeft(prefix(operationReference), javaBinaryType), right, type )); } else if (operationReference.getOperationSignTokenType() == KtTokens.EQ) { return mapType(new J.Assignment( randomId(), deepPrefix(expression), Markers.EMPTY, left, padLeft(suffix(expression.getLeft()), right), type )); } else if (assignmentOperationType != null) { return mapType(new J.AssignmentOperation( randomId(), deepPrefix(expression), Markers.EMPTY, left, padLeft(prefix(operationReference), assignmentOperationType), right, type )); } else if (kotlinBinaryType != null) { return mapType(new K.Binary( randomId(), deepPrefix(expression), Markers.EMPTY, left, padLeft(prefix(operationReference), kotlinBinaryType), right, Space.EMPTY, type )); } return mapFunctionCall(expression, data); } @Nullable private J.AssignmentOperation.Type mapAssignmentOperationType(KtOperationReferenceExpression operationReference) { IElementType elementType = operationReference.getOperationSignTokenType(); if (elementType == KtTokens.PLUSEQ) { return J.AssignmentOperation.Type.Addition; } else if (elementType == KtTokens.MINUSEQ) { return J.AssignmentOperation.Type.Subtraction; } else if (elementType == KtTokens.MULTEQ) { return J.AssignmentOperation.Type.Multiplication; } else if (elementType == KtTokens.DIVEQ) { return J.AssignmentOperation.Type.Division; } else if (elementType == KtTokens.PERCEQ) { return J.AssignmentOperation.Type.Modulo; } else return null; } @Override public J visitBlockExpression(KtBlockExpression expression, ExecutionContext data) { List> statements = new ArrayList<>(); for (KtExpression stmt : expression.getStatements()) { J exp = stmt.accept(this, data); Statement statement = convertToStatement(exp).withPrefix(endFixPrefixAndInfix(stmt)); JRightPadded build = maybeTrailingSemicolon(statement, stmt); statements.add(build); } boolean hasBraces = expression.getLBrace() != null; Space end = hasBraces ? deepPrefix(expression.getRBrace()) : Space.EMPTY; Space prefix = prefix(expression); Space blockPrefix = prefix; if (!hasBraces && !statements.isEmpty()) { statements = ListUtils.mapFirst(statements, s -> s.withElement(s.getElement().withPrefix(merge(prefix, s.getElement().getPrefix())))); blockPrefix = Space.EMPTY; } return new J.Block( randomId(), blockPrefix, hasBraces ? Markers.EMPTY : Markers.EMPTY.addIfAbsent(new OmitBraces(randomId())), JRightPadded.build(false), statements, end ); } @Override public J visitCallExpression(KtCallExpression expression, ExecutionContext data) { if (expression.getCalleeExpression() == null) { throw new UnsupportedOperationException("TODO"); } PsiElementAssociations.ExpressionType type = psiElementAssociations.getCallType(expression); if (type == PsiElementAssociations.ExpressionType.CONSTRUCTOR) { JavaType.Method mt = methodInvocationType(expression); TypeTree name = (J.Identifier) expression.getCalleeExpression().accept(this, data); name = name.withType(mt != null ? mt.getReturnType() : JavaType.Unknown.getInstance()); if (!expression.getTypeArguments().isEmpty()) { List> parameters = new ArrayList<>(expression.getTypeArguments().size()); for (KtTypeProjection ktTypeProjection : expression.getTypeArguments()) { parameters.add(padRight(convertToExpression(ktTypeProjection.accept(this, data)), suffix(ktTypeProjection))); } name = mapType(new J.ParameterizedType( randomId(), name.getPrefix(), Markers.EMPTY, name.withPrefix(Space.EMPTY), JContainer.build(prefix(expression.getTypeArgumentList()), parameters, Markers.EMPTY), type(expression) )); } return isAnnotationConstructor(mt) ? mapType(new J.Annotation( randomId(), deepPrefix(expression), Markers.EMPTY.addIfAbsent(new AnnotationConstructor(randomId())), name, mapValueArgumentsMaybeWithTrailingLambda(expression.getValueArgumentList(), expression.getValueArguments(), data) )) : mapType(new J.NewClass( randomId(), deepPrefix(expression), Markers.EMPTY, null, Space.EMPTY, name, mapValueArgumentsMaybeWithTrailingLambda(expression.getValueArgumentList(), expression.getValueArguments(), data), null, mt )); } else if (type == null || type == PsiElementAssociations.ExpressionType.METHOD_INVOCATION) { J j = expression.getCalleeExpression().accept(this, data); JRightPadded select = null; J.Identifier name; if (j instanceof J.Identifier) { name = (J.Identifier) j; } else { select = padRight(convertToExpression(j), Space.EMPTY); name = createIdentifier("", Space.EMPTY, null, null) .withMarkers(Markers.EMPTY.addIfAbsent(new Implicit(randomId()))); } JContainer typeParams = mapTypeArguments(expression.getTypeArgumentList(), data); JContainer args = mapValueArgumentsMaybeWithTrailingLambda(expression.getValueArgumentList(), expression.getValueArguments(), data); if (expression.getValueArgumentList() == null) { args = args.withMarkers(args.getMarkers().addIfAbsent(new OmitParentheses(randomId()))); } return mapType(new J.MethodInvocation( randomId(), deepPrefix(expression), Markers.EMPTY, select, typeParams, name, args, methodInvocationType(expression) )); } else if (type == PsiElementAssociations.ExpressionType.QUALIFIER) { TypeTree typeTree = (TypeTree) expression.getCalleeExpression().accept(this, data); JContainer typeParams = mapTypeArguments(expression.getTypeArgumentList(), data); return mapType(new J.ParameterizedType( randomId(), deepPrefix(expression), Markers.EMPTY, typeTree, typeParams, type(expression) )); } else { throw new UnsupportedOperationException("ExpressionType not found: " + expression.getCalleeExpression().getText()); } } private boolean isAnnotationConstructor(@Nullable JavaType type) { if (type instanceof JavaType.Method && !(((JavaType.Method) type).getDeclaringType() instanceof JavaType.Unknown)) { return isAnnotationConstructor(((JavaType.Method) type).getDeclaringType()); } if (type instanceof JavaType.Parameterized) { return isAnnotationConstructor(((JavaType.Parameterized) type).getType()); } if (type instanceof JavaType.Class) { return ((JavaType.Class) type).getKind() == JavaType.FullyQualified.Kind.Annotation; } return false; } @Nullable JContainer mapTypeArguments(@Nullable KtTypeArgumentList ktTypeArgumentList, ExecutionContext data) { if (ktTypeArgumentList == null) { return null; } List ktTypeProjections = ktTypeArgumentList.getArguments(); List> parameters = new ArrayList<>(ktTypeProjections.size()); for (int i = 0; i < ktTypeProjections.size(); i++) { KtTypeProjection ktTypeProjection = ktTypeProjections.get(i); parameters.add(maybeTrailingComma(ktTypeProjection, padRight(convertToExpression(ktTypeProjection.accept(this, data)), suffix(ktTypeProjection)), i == ktTypeProjections.size() - 1)); } return JContainer.build(deepPrefix(ktTypeArgumentList), parameters, Markers.EMPTY); } @Override public J visitConstantExpression(KtConstantExpression expression, ExecutionContext data) { IElementType elementType = expression.getElementType(); Object value; if (elementType == KtNodeTypes.INTEGER_CONSTANT || elementType == KtNodeTypes.FLOAT_CONSTANT) { value = ParseUtilsKt.parseNumericLiteral(expression.getText(), elementType); } else if (elementType == KtNodeTypes.BOOLEAN_CONSTANT) { value = ParseUtilsKt.parseBoolean(expression.getText()); } else if (elementType == KtNodeTypes.CHARACTER_CONSTANT) { value = unescape(expression.getText().substring(1, expression.getText().length() - 1)); } else if (elementType == KtNodeTypes.NULL) { value = null; } else { throw new UnsupportedOperationException("Unsupported constant expression elementType : " + elementType); } return new J.Literal( Tree.randomId(), deepPrefix(expression), Markers.EMPTY, value, expression.getText(), null, primitiveType(expression) ); } private Object unescape(String str) { int length = str.length(); if (length == 1) { return str.charAt(0); } else if (length == 2 && str.charAt(0) == '\\') { switch (str.charAt(1)) { case 't': return '\t'; case 'b': return '\b'; case 'r': return '\r'; case 'n': return '\n'; case '\'': return '\''; default: return str.charAt(1); } } else if (length == 6 && str.startsWith("\\u")) { return (char) Integer.parseInt(str.substring(2), 16); } return str; } @Override public J visitClass(KtClass klass, ExecutionContext data) { ownerStack.push(klass); try { return visitClass0(klass, data); } finally { ownerStack.pop(); } } private J visitClass0(KtClass klass, ExecutionContext data) { List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); JContainer typeParams = null; JContainer implementings = null; Markers markers = Markers.EMPTY; J.MethodDeclaration primaryConstructor; Set prefixConsumedSet = preConsumedInfix(klass); List modifiers = mapModifiers(klass.getModifierList(), leadingAnnotations, lastAnnotations, data); if (!klass.hasModifier(KtTokens.OPEN_KEYWORD)) { modifiers.add(buildFinalModifier()); } J.ClassDeclaration.Kind kind; if (klass.getClassKeyword() != null) { J.ClassDeclaration.Kind.Type classType = J.ClassDeclaration.Kind.Type.Class; for (J.Modifier mod : modifiers) { if ("annotation".equals(mod.getKeyword())) { classType = J.ClassDeclaration.Kind.Type.Annotation; break; } else if ("enum".equals(mod.getKeyword())) { classType = J.ClassDeclaration.Kind.Type.Enum; break; } } kind = new J.ClassDeclaration.Kind( randomId(), prefix(klass.getClassKeyword(), prefixConsumedSet), Markers.EMPTY, lastAnnotations, classType ); } else if (klass.getClassOrInterfaceKeyword() != null) { kind = new J.ClassDeclaration.Kind( randomId(), prefix(klass.getClassOrInterfaceKeyword(), prefixConsumedSet), Markers.EMPTY, emptyList(), J.ClassDeclaration.Kind.Type.Interface ); } else { throw new UnsupportedOperationException("TODO"); } J.Identifier name = createIdentifier(requireNonNull(klass.getIdentifyingElement()), type(klass)); J.Block body; if (klass.getBody() != null) { body = (J.Block) klass.getBody().accept(this, data); } else { body = new J.Block( randomId(), Space.EMPTY, Markers.EMPTY.add(new OmitBraces(randomId())), padRight(false, Space.EMPTY), emptyList(), Space.EMPTY ); } if (klass.getPrimaryConstructor() != null) { primaryConstructor = (J.MethodDeclaration) klass.getPrimaryConstructor().accept(this, data); body = body.withStatements(ListUtils.concat(primaryConstructor, body.getStatements())); markers = markers.addIfAbsent(new PrimaryConstructor(randomId())); } if (klass.getSuperTypeList() != null) { implementings = mapSuperTypeList(klass.getSuperTypeList(), data); implementings = implementings != null ? implementings.withBefore(prefix(klass.getColon())) : null; } if (klass.getTypeParameterList() != null) { typeParams = JContainer.build(prefix(klass.getTypeParameterList()), mapTypeParameters(klass.getTypeParameterList(), data), Markers.EMPTY); } K.TypeConstraints typeConstraints = null; if (klass.getTypeConstraintList() != null) { typeConstraints = (K.TypeConstraints) klass.getTypeConstraintList().accept(this, data); PsiElement whereKeyword = requireNonNull(klass.getNode().findChildByType(KtTokens.WHERE_KEYWORD)).getPsi(); typeConstraints = typeConstraints.withConstraints(ListUtils.mapFirst(typeConstraints.getConstraints(), constraint -> constraint.withPrefix(suffix(whereKeyword)))) .withPrefix(prefix(whereKeyword)); } J.ClassDeclaration classDeclaration = new J.ClassDeclaration( randomId(), deepPrefix(klass), markers, leadingAnnotations, modifiers, kind, name, typeParams, null, null, implementings, null, body, (JavaType.FullyQualified) type(klass) ); return (typeConstraints != null) ? new K.ClassDeclaration(randomId(), Markers.EMPTY, classDeclaration, typeConstraints) : classDeclaration; } @Override public J visitClassBody(KtClassBody classBody, ExecutionContext data) { List> list = new ArrayList<>(); Space after = endFixPrefixAndInfix(classBody.getRBrace()); if (!classBody.getEnumEntries().isEmpty()) { List> enumValues = new ArrayList<>(classBody.getEnumEntries().size()); boolean terminatedWithSemicolon = false; for (int i = 0; i < classBody.getEnumEntries().size(); i++) { KtEnumEntry ktEnumEntry = classBody.getEnumEntries().get(i); PsiElement comma = PsiTreeUtil.findSiblingForward(requireNonNull(ktEnumEntry.getIdentifyingElement()), KtTokens.COMMA, null); PsiElement semicolon = PsiTreeUtil.findSiblingForward(ktEnumEntry.getIdentifyingElement(), KtTokens.SEMICOLON, null); JRightPadded rp = padRight((J.EnumValue) ktEnumEntry.accept(this, data), Space.EMPTY); if (i == classBody.getEnumEntries().size() - 1) { if (semicolon != null) { terminatedWithSemicolon = true; } if (comma != null) { rp = rp.withAfter(prefix(comma)); // Space afterComma will be handled by others as a prefix or else, except there is a trailing semicolon Space afterComma = Space.EMPTY; if (semicolon != null) { afterComma = suffix(comma); } rp = rp.withMarkers(rp.getMarkers().addIfAbsent(new TrailingComma(randomId(), afterComma))); } else { if (semicolon != null) { rp = rp.withAfter(prefix(semicolon)); } } } else { rp = rp.withAfter(prefix(comma)); } enumValues.add(rp); } JRightPadded enumSet = padRight( new J.EnumValueSet( randomId(), Space.EMPTY, Markers.EMPTY, enumValues, terminatedWithSemicolon ), Space.EMPTY ); list.add(enumSet); } for (KtDeclaration d : classBody.getDeclarations()) { if (d instanceof KtEnumEntry) { continue; } Statement statement = convertToStatement(d.accept(this, data)); list.add(maybeTrailingSemicolon(statement, d)); } return new J.Block( randomId(), deepPrefix(classBody), Markers.EMPTY, padRight(false, Space.EMPTY), list, after ); } @Override public J visitDestructuringDeclaration(KtDestructuringDeclaration multiDeclaration, ExecutionContext data) { List modifiers = new ArrayList<>(); List leadingAnnotations = new ArrayList<>(); List> destructVars = new ArrayList<>(); JLeftPadded paddedInitializer = null; J.Modifier modifier = new J.Modifier( Tree.randomId(), prefix(multiDeclaration.getValOrVarKeyword(), preConsumedInfix(multiDeclaration)), Markers.EMPTY, multiDeclaration.isVar() ? "var" : null, multiDeclaration.isVar() ? J.Modifier.Type.LanguageExtension : J.Modifier.Type.Final, Collections.emptyList() ); modifiers.add(modifier); if (multiDeclaration.getInitializer() != null) { paddedInitializer = padLeft(suffix(multiDeclaration.getRPar()), convertToExpression(multiDeclaration.getInitializer().accept(this, data)) .withPrefix(prefix(multiDeclaration.getInitializer()))); } List entries = multiDeclaration.getEntries(); for (int i = 0; i < entries.size(); i++) { KtDestructuringDeclarationEntry entry = entries.get(i); Space beforeEntry = prefix(entry); List annotations = new ArrayList<>(); if (entry.getModifierList() != null) { mapModifiers(entry.getModifierList(), annotations, emptyList(), data); if (!annotations.isEmpty()) { annotations = ListUtils.mapFirst(annotations, anno -> anno.withPrefix(beforeEntry)); } } JavaType.Variable vt = variableType(entry, owner(entry)); if (entry.getName() == null) { throw new UnsupportedOperationException("KtDestructuringDeclarationEntry has empty name, this should never happen"); } J.Identifier nameVar = createIdentifier(requireNonNull(entry.getNameIdentifier()), vt); if (!annotations.isEmpty()) { nameVar = nameVar.withAnnotations(annotations); } else { nameVar = nameVar.withPrefix(beforeEntry); } J.VariableDeclarations.NamedVariable namedVariable = new J.VariableDeclarations.NamedVariable( randomId(), Space.EMPTY, Markers.EMPTY, nameVar, emptyList(), null, vt ); TypeTree typeExpression = null; if (entry.getTypeReference() != null) { typeExpression = (TypeTree) entry.getTypeReference().accept(this, data); } J.VariableDeclarations variableDeclarations = new J.VariableDeclarations(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), emptyList(), typeExpression, null, emptyList(), singletonList(padRight(namedVariable, prefix(entry.getColon()))) ); destructVars.add(maybeTrailingComma(entry, padRight(variableDeclarations, suffix(entry)), i == entries.size() - 1)); } JavaType.Variable vt = variableType(multiDeclaration, owner(multiDeclaration)); J.VariableDeclarations.NamedVariable emptyWithInitializer = new J.VariableDeclarations.NamedVariable( randomId(), Space.EMPTY, Markers.EMPTY, createIdentifier("", Space.SINGLE_SPACE, vt), emptyList(), paddedInitializer, vt ); J.VariableDeclarations variableDeclarations = new J.VariableDeclarations( randomId(), Space.EMPTY, Markers.EMPTY, leadingAnnotations, modifiers, null, null, emptyList(), singletonList(padRight(emptyWithInitializer, Space.EMPTY)) ); return new K.DestructuringDeclaration( randomId(), deepPrefix(multiDeclaration), Markers.EMPTY, variableDeclarations, null, JContainer.build(prefix(multiDeclaration.getLPar()), destructVars, Markers.EMPTY) ); } @Override public J visitDotQualifiedExpression(KtDotQualifiedExpression expression, ExecutionContext data) { assert expression.getSelectorExpression() != null; Space prefix = deepPrefix(expression); if (expression.getSelectorExpression() instanceof KtCallExpression) { KtCallExpression callExpression = (KtCallExpression) expression.getSelectorExpression(); Space callExpressionPrefix = prefix(callExpression); Expression receiver = convertToExpression(expression.getReceiverExpression().accept(this, data)); J j = callExpression.accept(this, data); if (j instanceof J.Annotation) { J.Annotation a = (J.Annotation) j; a = a.withAnnotationType(a.getAnnotationType().withPrefix(callExpressionPrefix)); J.FieldAccess newName = mapType(new J.FieldAccess( randomId(), receiver.getPrefix(), Markers.EMPTY, receiver.withPrefix(Space.EMPTY), padLeft(suffix(expression.getReceiverExpression()), (J.Identifier) a.getAnnotationType()), a.getType() )); return a.withAnnotationType(newName).withPrefix(prefix); } else if (j instanceof J.ParameterizedType) { J.ParameterizedType pt = (J.ParameterizedType) j; pt = pt.withClazz(pt.getClazz().withPrefix(callExpressionPrefix)); J.FieldAccess newName = mapType(new J.FieldAccess( randomId(), receiver.getPrefix(), Markers.EMPTY, receiver.withPrefix(Space.EMPTY), padLeft(suffix(expression.getReceiverExpression()), (J.Identifier) pt.getClazz()), pt.getType() )); return mapType(pt.withClazz(newName).withPrefix(prefix)); } else if (j instanceof J.MethodInvocation) { J.MethodInvocation m = (J.MethodInvocation) j; return m.getPadding().withSelect(padRight(receiver, suffix(expression.getReceiverExpression()))) .withName(m.getName().withPrefix(callExpressionPrefix)) .withPrefix(prefix); } else if (j instanceof J.NewClass) { J.NewClass n = (J.NewClass) j; if (receiver instanceof J.FieldAccess || receiver instanceof J.Identifier || receiver instanceof J.NewClass || receiver instanceof K.This) { n = n.withPrefix(prefix); if (n.getClazz() instanceof J.ParameterizedType) { J.ParameterizedType pt = (J.ParameterizedType) n.getClazz(); if (pt != null) { pt = pt.withClazz(pt.getClazz().withPrefix(callExpressionPrefix)); J.FieldAccess newName = mapType(new J.FieldAccess( randomId(), receiver.getPrefix(), Markers.EMPTY, receiver.withPrefix(Space.EMPTY), padLeft(suffix(expression.getReceiverExpression()), (J.Identifier) pt.getClazz()), pt.getType() )); pt = pt.withClazz(newName); pt = mapType(pt); n = n.withClazz(pt); } } else { J.Identifier id = (J.Identifier) n.getClazz(); if (id != null) { id = id.withPrefix(callExpressionPrefix); J.FieldAccess newName = mapType(new J.FieldAccess( randomId(), receiver.getPrefix(), Markers.EMPTY, receiver.withPrefix(Space.EMPTY), padLeft(suffix(expression.getReceiverExpression()), id), id.getType() )); n = n.withClazz(newName).withPrefix(prefix); } } } return n; } throw new UnsupportedOperationException("Unsupported call expression " + j.getClass().getName()); } else if (expression.getSelectorExpression() instanceof KtNameReferenceExpression) { // Maybe need to type check before creating a field access. return mapType(new J.FieldAccess( randomId(), prefix, Markers.EMPTY, convertToExpression(expression.getReceiverExpression().accept(this, data).withPrefix(Space.EMPTY)), padLeft(suffix(expression.getReceiverExpression()), (J.Identifier) expression.getSelectorExpression().accept(this, data)), type(expression.getSelectorExpression()) )); } else { throw new UnsupportedOperationException("Unsupported dot qualified selector: " + expression.getSelectorExpression().getClass()); } } @Override public J visitIfExpression(KtIfExpression expression, ExecutionContext data) { return new J.If( randomId(), deepPrefix(expression), Markers.EMPTY, buildIfCondition(expression), buildIfThenPart(expression), buildIfElsePart(expression) ); } @Override public J visitImportDirective(KtImportDirective importDirective, ExecutionContext data) { FirResolvedImport resolvedImport = getResolvedImport(importDirective); boolean isStaticImport = resolvedImport != null && resolvedImport.getResolvedParentClassId() != null; JLeftPadded rpStatic = padLeft(Space.EMPTY, isStaticImport); KtImportAlias alias = importDirective.getAlias(); ASTNode node = importDirective.getNode().findChildByType(KtTokens.IMPORT_KEYWORD); LeafPsiElement importPsi = (LeafPsiElement) node; PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(importPsi); PsiElement last = findLastChild(importDirective, psi -> !(psi instanceof KtImportAlias) && !isSpace(psi.getNode()) && psi.getNode().getElementType() != KtTokens.SEMICOLON); String text = nodeRangeText(getNodeOrNull(first), getNodeOrNull(last)); TypeTree reference = TypeTree.build(text, '`'); reference = reference.withPrefix(suffix(importPsi)); JavaType jt = type(importDirective); if (jt instanceof JavaType.Parameterized) { jt = ((JavaType.Parameterized) jt).getType(); } if (jt != null) { reference = reference.withType(jt); } if (reference instanceof J.Identifier) { reference = mapType(new J.FieldAccess( randomId(), suffix(importPsi), Markers.EMPTY, new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY), padLeft(Space.EMPTY, (J.Identifier) reference), jt )); } return new J.Import( randomId(), deepPrefix(importDirective), Markers.EMPTY, rpStatic, (J.FieldAccess) reference, // Aliases contain Kotlin `Name` and do not resolve to a type. The aliases type is the import directive, so we set the type to match the import. alias != null ? padLeft(prefix(alias), createIdentifier(requireNonNull(alias.getNameIdentifier()), jt)) : null ); } @Nullable private FirResolvedImport getResolvedImport(KtImportDirective importDirective) { FirElement primary = psiElementAssociations.primary(importDirective); if (primary instanceof FirResolvedImport) { return (FirResolvedImport) primary; } return null; } @Override public J visitNamedFunction(KtNamedFunction function, ExecutionContext data) { ownerStack.push(function); try { return visitNamedFunction0(function, data); } finally { ownerStack.pop(); } } private J visitNamedFunction0(KtNamedFunction function, ExecutionContext data) { Markers markers = Markers.EMPTY; List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); List modifiers = mapModifiers(function.getModifierList(), leadingAnnotations, lastAnnotations, data); J.TypeParameters typeParameters = null; TypeTree returnTypeExpression = null; Set prefixConsumedSet = preConsumedInfix(function); if (function.getTypeParameterList() != null) { typeParameters = new J.TypeParameters( randomId(), prefix(function.getTypeParameterList()), Markers.EMPTY, emptyList(), mapTypeParameters(function.getTypeParameterList(), data) ); } boolean isOpen = function.hasModifier(KtTokens.OPEN_KEYWORD); if (!isOpen) { modifiers.add(buildFinalModifier().withPrefix(Space.EMPTY)); } modifiers.add(new J.Modifier(randomId(), prefix(function.getFunKeyword(), prefixConsumedSet), Markers.EMPTY, "fun", J.Modifier.Type.LanguageExtension, lastAnnotations)); J.Identifier name; JavaType.Method type = methodDeclarationType(function); if (function.getNameIdentifier() == null) { name = createIdentifier("", Space.EMPTY, type); } else { name = createIdentifier(function.getNameIdentifier(), type); } // parameters JContainer params; List ktParameters = function.getValueParameters(); if (function.getValueParameterList() == null) { throw new UnsupportedOperationException("TODO"); } if (ktParameters.isEmpty()) { params = JContainer.build(prefix(function.getValueParameterList()), singletonList(padRight(new J.Empty(randomId(), prefix(function.getValueParameterList().getRightParenthesis()), Markers.EMPTY), Space.EMPTY) ), Markers.EMPTY ); } else { params = JContainer.build(prefix(function.getValueParameterList()), mapParameters(function.getValueParameterList(), data), Markers.EMPTY); } if (function.getReceiverTypeReference() != null) { markers = markers.addIfAbsent(new Extension(randomId())); Expression receiver = convertToExpression(function.getReceiverTypeReference().accept(this, data)); JRightPadded infixReceiver = JRightPadded.build( new J.VariableDeclarations.NamedVariable( randomId(), Space.EMPTY, Markers.EMPTY.addIfAbsent(new Extension(randomId())), createIdentifier("", Space.EMPTY, null, null), emptyList(), padLeft(Space.EMPTY, receiver), null ) ) .withAfter(suffix(function.getReceiverTypeReference())); J.VariableDeclarations implicitParam = new J.VariableDeclarations( randomId(), Space.EMPTY, Markers.EMPTY.addIfAbsent(new Extension(randomId())), emptyList(), emptyList(), null, null, emptyList(), singletonList(infixReceiver) ); implicitParam = implicitParam.withMarkers(implicitParam.getMarkers().addIfAbsent(new TypeReferencePrefix(randomId(), Space.EMPTY))); List> newStatements = new ArrayList<>(params.getElements().size() + 1); newStatements.add(JRightPadded.build(implicitParam)); newStatements.addAll(params.getPadding().getElements()); params = params.getPadding().withElements(newStatements); } if (function.getTypeReference() != null) { markers = markers.addIfAbsent(new TypeReferencePrefix(randomId(), prefix(function.getColon()))); returnTypeExpression = (TypeTree) function.getTypeReference().accept(this, data); } K.TypeConstraints typeConstraints = null; if (function.getTypeConstraintList() != null) { typeConstraints = (K.TypeConstraints) function.getTypeConstraintList().accept(this, data); PsiElement whereKeyword = requireNonNull(function.getNode().findChildByType(KtTokens.WHERE_KEYWORD)).getPsi(); typeConstraints = typeConstraints.withConstraints(ListUtils.mapFirst(typeConstraints.getConstraints(), constraint -> constraint.withPrefix(suffix(whereKeyword)))) .withPrefix(prefix(whereKeyword)); } J.Block body; if (function.getBodyBlockExpression() != null) { body = function.getBodyBlockExpression().accept(this, data) .withPrefix(prefix(function.getBodyBlockExpression())); } else if (function.getBodyExpression() != null) { body = convertToBlock(function.getBodyExpression(), data).withPrefix(prefix(function.getEqualsToken())); } else { body = null; } J.MethodDeclaration methodDeclaration = mapType(new J.MethodDeclaration( randomId(), deepPrefix(function), markers, leadingAnnotations, modifiers, typeParameters, returnTypeExpression, new J.MethodDeclaration.IdentifierWithAnnotations(name, emptyList()), params, null, body, null, type )); return (typeConstraints == null) ? methodDeclaration : new K.MethodDeclaration(randomId(), Markers.EMPTY, methodDeclaration, typeConstraints); } private List> mapTypeParameters(KtTypeParameterList list, ExecutionContext data) { List ktTypeParameters = list.getParameters(); List> params = new ArrayList<>(ktTypeParameters.size()); for (int i = 0; i < ktTypeParameters.size(); i++) { KtTypeParameter ktTypeParameter = ktTypeParameters.get(i); J.TypeParameter typeParameter = (J.TypeParameter) ktTypeParameter.accept(this, data); params.add(maybeTrailingComma(ktTypeParameter, padRight(typeParameter, suffix(ktTypeParameter)), i == ktTypeParameters.size() - 1)); } return params; } @Override public J visitObjectLiteralExpression(KtObjectLiteralExpression expression, ExecutionContext data) { J j = expression.getObjectDeclaration().accept(this, data); return j.withPrefix(merge(deepPrefix(expression), j.getPrefix())); } @Override public J visitObjectDeclaration(KtObjectDeclaration declaration, ExecutionContext data) { Markers markers = Markers.EMPTY; List modifiers = new ArrayList<>(); JContainer implementings = null; Set consumedSpaces = preConsumedInfix(declaration); List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); if (declaration.getModifierList() != null) { modifiers.addAll(mapModifiers(declaration.getModifierList(), leadingAnnotations, lastAnnotations, data)); } modifiers.add(buildFinalModifier()); JContainer typeParameters = declaration.getTypeParameterList() == null ? null : JContainer.build(prefix(declaration.getTypeParameterList()), mapTypeParameters(declaration.getTypeParameterList(), data), Markers.EMPTY); if (declaration.getSuperTypeList() != null) { implementings = mapSuperTypeList(declaration.getSuperTypeList(), data); implementings = requireNonNull(implementings).withBefore(prefix(declaration.getColon())); } J.Block body; if (declaration.getBody() == null) { body = new J.Block( randomId(), Space.EMPTY, Markers.EMPTY, padRight(false, Space.EMPTY), emptyList(), Space.EMPTY ); body = body.withMarkers(body.getMarkers().addIfAbsent(new OmitBraces(randomId()))); } else { body = (J.Block) declaration.getBody().accept(this, data); } if (declaration.getObjectKeyword() != null) { markers = markers.add(new KObject(randomId())); } J.Identifier name; if (declaration.getNameIdentifier() != null) { name = createIdentifier(declaration.getNameIdentifier(), type(declaration)); } else { name = createIdentifier(declaration.isCompanion() ? "" : "", Space.EMPTY, type(declaration)) .withMarkers(Markers.EMPTY.addIfAbsent(new Implicit(randomId()))); } return new J.ClassDeclaration( randomId(), deepPrefix(declaration), markers, leadingAnnotations, modifiers, new J.ClassDeclaration.Kind( randomId(), prefix(declaration.getObjectKeyword(), consumedSpaces), Markers.EMPTY, lastAnnotations, J.ClassDeclaration.Kind.Type.Class ), name, typeParameters, null, null, implementings, null, body, (JavaType.FullyQualified) type(declaration) ); } @Override @Nullable public J visitPackageDirective(KtPackageDirective directive, ExecutionContext data) { if (directive.getPackageNameExpression() == null) { return null; } return new J.Package( randomId(), deepPrefix(directive), Markers.EMPTY, (Expression) directive.getPackageNameExpression().accept(this, data), emptyList() ); } @Override public J visitPrefixExpression(KtPrefixExpression expression, ExecutionContext data) { assert expression.getBaseExpression() != null; J.Unary.Type type = mapJUnaryType(expression.getOperationReference()); if (type == null) { throw new UnsupportedOperationException("TODO"); } // FIXME: Add detection of overloads and return the appropriate trees when it is not equivalent to a J.Unary. // Returning the base type only applies when the expression is equivalent to a J.Binary. JavaType javaType = type(expression); return mapType(new J.Unary( randomId(), deepPrefix(expression), Markers.EMPTY, padLeft(prefix(expression.getOperationReference()), type), expression.getBaseExpression().accept(this, data).withPrefix(suffix(expression.getOperationReference())), javaType )); } @Override public J visitPostfixExpression(KtPostfixExpression expression, ExecutionContext data) { // FIXME: Add detection of overloads and return the appropriate trees when it is not equivalent to a J.Unary. // Returning the base type only applies when the expression is equivalent to a J.Binary. JavaType type = type(expression); if (type instanceof JavaType.Method) { type = ((JavaType.Method) type).getReturnType(); } else if (type instanceof JavaType.Variable) { type = ((JavaType.Variable) type).getType(); } J j = convertToExpression(requireNonNull(expression.getBaseExpression()).accept(this, data)); IElementType referencedNameElementType = expression.getOperationReference().getReferencedNameElementType(); if (referencedNameElementType == KtTokens.EXCLEXCL) { j = mapType(new K.Unary(randomId(), deepPrefix(expression), Markers.EMPTY, padLeft(prefix(expression.getOperationReference()), K.Unary.Type.NotNull), (Expression) j, type)); } else if (referencedNameElementType == KtTokens.PLUSPLUS) { j = mapType(new J.Unary(randomId(), deepPrefix(expression), Markers.EMPTY, padLeft(prefix(expression.getOperationReference()), J.Unary.Type.PostIncrement), (Expression) j, type)); } else if (referencedNameElementType == KtTokens.MINUSMINUS) { j = mapType(new J.Unary(randomId(), deepPrefix(expression), Markers.EMPTY, padLeft(prefix(expression.getOperationReference()), J.Unary.Type.PostDecrement), (Expression) j, type)); } else { throw new UnsupportedOperationException("TODO"); } return j; } @Override public J visitProperty(KtProperty property, ExecutionContext data) { Markers markers = Markers.EMPTY; List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); List modifiers = mapModifiers(property.getModifierList(), leadingAnnotations, lastAnnotations, data); TypeTree typeExpression = null; List> variables = new ArrayList<>(); JRightPadded receiver = null; JContainer typeParameters = property.getTypeParameterList() != null ? JContainer.build(prefix(property.getTypeParameterList()), mapTypeParameters(property.getTypeParameterList(), data), Markers.EMPTY) : null; K.TypeConstraints typeConstraints = null; Set prefixConsumedSet = preConsumedInfix(property); modifiers.add(new J.Modifier( Tree.randomId(), prefix(property.getValOrVarKeyword(), prefixConsumedSet), Markers.EMPTY, property.isVar() ? "var" : null, property.isVar() ? J.Modifier.Type.LanguageExtension : J.Modifier.Type.Final, lastAnnotations )); // Receiver if (property.getReceiverTypeReference() != null) { Expression receiverExp = convertToExpression(property.getReceiverTypeReference().accept(this, data).withPrefix(prefix(property.getReceiverTypeReference()))); receiver = padRight(receiverExp, suffix(property.getReceiverTypeReference())); markers = markers.addIfAbsent(new Extension(randomId())); } JLeftPadded initializer = null; if (property.getInitializer() != null) { initializer = padLeft(prefix(property.getEqualsToken()), convertToExpression(property.getInitializer().accept(this, data) .withPrefix(prefix(property.getInitializer())))); } else if (property.getDelegate() != null) { Space afterByKeyword = prefix(property.getDelegate().getExpression()); Expression initializerExp = convertToExpression(property.getDelegate().accept(this, data)).withPrefix(afterByKeyword); initializer = padLeft(prefix(property.getDelegate()), initializerExp); markers = markers.addIfAbsent(new By(randomId())); } Markers rpMarker = Markers.EMPTY; JavaType.Variable vt = variableType(property, owner(property)); J.VariableDeclarations.NamedVariable namedVariable = new J.VariableDeclarations.NamedVariable( randomId(), prefix(property.getNameIdentifier()), Markers.EMPTY, createIdentifier(requireNonNull(property.getNameIdentifier()), vt).withPrefix(Space.EMPTY), emptyList(), initializer, vt ); variables.add(padRight(namedVariable, prefix(property.getColon())).withMarkers(rpMarker)); if (property.getColon() != null) { typeExpression = (TypeTree) requireNonNull(property.getTypeReference()).accept(this, data); } J.VariableDeclarations variableDeclarations = new J.VariableDeclarations( Tree.randomId(), endFixPrefixAndInfix(property), // overlaps with right-padding of previous statement markers, leadingAnnotations, modifiers, typeExpression, null, Collections.emptyList(), variables ); if (property.getTypeConstraintList() != null) { typeConstraints = (K.TypeConstraints) property.getTypeConstraintList().accept(this, data); PsiElement whereKeyword = requireNonNull(property.getNode().findChildByType(KtTokens.WHERE_KEYWORD)).getPsi(); typeConstraints = typeConstraints.withConstraints(ListUtils.mapFirst(typeConstraints.getConstraints(), constraint -> constraint.withPrefix(suffix(whereKeyword)))) .withPrefix(prefix(whereKeyword)); } List ktPropertyAccessors = property.getAccessors(); if (!ktPropertyAccessors.isEmpty() || receiver != null || typeConstraints != null) { List> accessors = new ArrayList<>(ktPropertyAccessors.size()); Space beforeSemiColon = Space.EMPTY; Markers rpMarkers = Markers.EMPTY; for (int i = 0; i < ktPropertyAccessors.size(); i++) { KtPropertyAccessor ktPropertyAccessor = ktPropertyAccessors.get(i); if (i == 0) { PsiElement maybeSemiColon = PsiTreeUtil.findSiblingBackward(ktPropertyAccessor, KtTokens.SEMICOLON, null); if (maybeSemiColon != null) { beforeSemiColon = prefix(maybeSemiColon); rpMarkers = rpMarkers.addIfAbsent(new Semicolon(randomId())); } } J.MethodDeclaration accessor = (J.MethodDeclaration) ktPropertyAccessor.accept(this, data); accessors.add(maybeTrailingSemicolonInternal(accessor, ktPropertyAccessor)); } return new K.Property( randomId(), deepPrefix(property), markers, typeParameters, padRight(variableDeclarations.withPrefix(Space.EMPTY), beforeSemiColon, rpMarkers), typeConstraints, JContainer.build(accessors), receiver ); } else { return variableDeclarations; } } private List mapModifiers(@Nullable KtModifierList modifierList, @NonNull List leadingAnnotations, @NonNull List lastAnnotations, ExecutionContext data) { boolean isLeadingAnnotation = true; ArrayList modifiers = new ArrayList<>(); List annotations = new ArrayList<>(); if (modifierList == null) { return modifiers; } // don't use iterator of `PsiTreeUtil.firstChild` and `getNextSibling`, since it could skip one layer, example test "paramAnnotation" // also don't use `modifierList.getChildren()` since it could miss some element for (Iterator it = PsiUtilsKt.getAllChildren(modifierList).iterator(); it.hasNext(); ) { PsiElement child = it.next(); if (isSpace(child.getNode())) { continue; } boolean isAnnotationEntry = child instanceof KtAnnotationEntry; boolean isAnnotation = child instanceof KtAnnotation; boolean isKeyword = child instanceof LeafPsiElement && child.getNode().getElementType() instanceof KtModifierKeywordToken; if (isAnnotationEntry) { KtAnnotationEntry ktAnnotationEntry = (KtAnnotationEntry) child; J.Annotation annotation = (J.Annotation) ktAnnotationEntry.accept(this, data); if (isLeadingAnnotation) { leadingAnnotations.add(annotation); } else { annotations.add(annotation); } } else if (isAnnotation) { KtAnnotation ktAnnotation = (KtAnnotation) child; J.Annotation annotation = (J.Annotation) ktAnnotation.accept(this, data); if (isLeadingAnnotation) { leadingAnnotations.add(annotation); } else { annotations.add(annotation); } } else if (isKeyword) { isLeadingAnnotation = false; modifiers.add(mapModifier(child, new ArrayList<>(annotations), null)); annotations.clear(); } } if (!annotations.isEmpty()) { lastAnnotations.addAll(annotations); } return modifiers; } @Override public J visitPropertyDelegate(KtPropertyDelegate delegate, ExecutionContext data) { if (delegate.getExpression() == null) { throw new UnsupportedOperationException("TODO"); } // Markers initMarkers = Markers.EMPTY.addIfAbsent(new By(randomId())); return delegate.getExpression().accept(this, data) .withPrefix(deepPrefix(delegate)); } @Override public J visitSimpleNameExpression(KtSimpleNameExpression expression, ExecutionContext data) { // The correct type cannot consistently be associated to the expression due to the relationship between the PSI and FIR. // The parent tree should use the associated FIR to fix mis-mapped types. // I.E. MethodInvocations, MethodDeclarations, VariableNames should be set manually through `createIdentifier(psi, type)` return createIdentifier(expression, type(expression)); } @Override public J visitStringTemplateExpression(KtStringTemplateExpression expression, ExecutionContext data) { KtStringTemplateEntry[] entries = expression.getEntries(); boolean hasStringTemplateEntry = Arrays.stream(entries).anyMatch(x -> x instanceof KtBlockStringTemplateEntry || x instanceof KtSimpleNameStringTemplateEntry); if (hasStringTemplateEntry) { String delimiter = expression.getFirstChild().getText(); List values = new ArrayList<>(entries.length); for (KtStringTemplateEntry entry : entries) { values.add(entry.accept(this, data)); } return new K.StringTemplate( randomId(), deepPrefix(expression), Markers.EMPTY, delimiter, values, type(expression) ); } StringBuilder valueSb = new StringBuilder(); Arrays.stream(entries).forEach(entry -> valueSb.append(maybeAdjustCRLF(entry)) ); String valueSource = getString(expression, valueSb); return new J.Literal( randomId(), Space.EMPTY, Markers.EMPTY, valueSb.toString(), valueSource, null, JavaType.Primitive.String ).withPrefix(deepPrefix(expression)); } private static String getString(KtStringTemplateExpression expression, StringBuilder valueSb) { PsiElement openQuote = expression.getFirstChild(); PsiElement closingQuota = expression.getLastChild(); if (openQuote == null || closingQuota == null || openQuote.getNode().getElementType() != KtTokens.OPEN_QUOTE || closingQuota.getNode().getElementType() != KtTokens.CLOSING_QUOTE) { throw new UnsupportedOperationException("This should never happen"); } return openQuote.getText() + valueSb + closingQuota.getText(); } @Override public J visitStringTemplateEntry(KtStringTemplateEntry entry, ExecutionContext data) { PsiElement leaf = entry.getFirstChild(); if (!(leaf instanceof LeafPsiElement)) { throw new UnsupportedOperationException("Unsupported KtStringTemplateEntry child"); } return new J.Literal( Tree.randomId(), Space.EMPTY, Markers.EMPTY, leaf.getText(), "\"" + leaf.getText() + "\"", // todo, support text block null, primitiveType(entry) ); } @Override public J visitSuperTypeList(KtSuperTypeList list, ExecutionContext data) { throw new UnsupportedOperationException("Unsupported, call mapSuperTypeList instead"); } @Override public J visitSuperTypeCallEntry(KtSuperTypeCallEntry call, ExecutionContext data) { return requireNonNull(call.getTypeReference()).accept(this, data); } @Override public J visitTypeReference(KtTypeReference typeReference, ExecutionContext data) { List leadingAnnotations = new ArrayList<>(); List lastAnnotations = new ArrayList<>(); Set consumedSpaces = preConsumedInfix(typeReference); List modifiers = mapModifiers(typeReference.getModifierList(), leadingAnnotations, lastAnnotations, data); if (!leadingAnnotations.isEmpty()) { leadingAnnotations = ListUtils.mapFirst(leadingAnnotations, anno -> anno.withPrefix(prefix(typeReference))); consumedSpaces.add(findFirstPrefixSpace(typeReference)); } else if (!modifiers.isEmpty()) { PsiElement first = findFirstNonSpaceChild(typeReference); if (first != null) { if (first.getNode().getElementType() != KtTokens.LPAR) { modifiers = ListUtils.mapFirst(modifiers, mod -> mod.withPrefix(prefix(typeReference))); consumedSpaces.add(findFirstPrefixSpace(typeReference)); } else { // handle redundant parentheses modifiers = ListUtils.mapFirst(modifiers, mod -> mod.withPrefix(merge(prefix(typeReference.getModifierList()), mod.getPrefix()))); } } } J j = requireNonNull(typeReference.getTypeElement()).accept(this, data); consumedSpaces.add(findFirstPrefixSpace(typeReference.getTypeElement())); if (j instanceof K.FunctionType && typeReference.getModifierList() != null) { K.FunctionType functionType = (K.FunctionType) j; functionType = functionType.withModifiers(modifiers).withLeadingAnnotations(leadingAnnotations); if (functionType.getReceiver() != null) { functionType = functionType.withReceiver( functionType.getReceiver().withElement(functionType.getReceiver().getElement().withPrefix(functionType.getPrefix())) ); functionType = functionType.withPrefix(Space.EMPTY); } j = functionType; } else if (j instanceof J.Identifier) { j = ((J.Identifier) j).withAnnotations(leadingAnnotations); } else if (j instanceof J.ParameterizedType || j instanceof J.IntersectionType) { if (!leadingAnnotations.isEmpty()) { j = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY, leadingAnnotations, (TypeTree) j); } } else if (j instanceof J.NullableType) { j = ((J.NullableType) j).withAnnotations(leadingAnnotations); } // Handle potential redundant nested parentheses Stack> parenPairs = new Stack<>(); List allChildren = getAllChildren(typeReference); int l = 0; int r = allChildren.size() - 1; while (l < r) { l = findFirstLPAR(allChildren, l); r = findLastRPAR(allChildren, r); if (l * r < 0) { throw new UnsupportedOperationException("Unpaired parentheses!"); } if (l < 0 || r < 0) { break; } parenPairs.add(new Pair<>(l++, r--)); } while (!parenPairs.empty()) { Pair parenPair = parenPairs.pop(); PsiElement lPAR = allChildren.get(parenPair.getFirst()); PsiElement rPAR = allChildren.get(parenPair.getSecond()); TypeTree typeTree = j instanceof K.FunctionType ? ((K.FunctionType) j).withReturnType(((K.FunctionType) j).getReturnType().withAfter(Space.EMPTY)) : (TypeTree) j; j = new J.ParenthesizedTypeTree(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), new J.Parentheses<>(randomId(), prefix(lPAR), Markers.EMPTY, padRight(typeTree, prefix(rPAR))) ); } return j.withPrefix(merge(prefix(typeReference, consumedSpaces), j.getPrefix())); } @Override public J visitUserType(KtUserType type, ExecutionContext data) { J.Identifier name = (J.Identifier) requireNonNull(type.getReferenceExpression()).accept(this, data); if (type.getFirstChild() == type.getReferenceExpression()) { name = name.withPrefix(merge(deepPrefix(type), name.getPrefix())); } NameTree nameTree = name; if (type.getQualifier() != null) { Expression select = convertToExpression(type.getQualifier().accept(this, data)).withPrefix(prefix(type.getQualifier())); nameTree = mapType(new J.FieldAccess(randomId(), Space.EMPTY, Markers.EMPTY, select, padLeft(suffix(type.getQualifier()), name), name.getType())); } if (type.getTypeArgumentList() != null) { JContainer args = mapTypeArguments(type.getTypeArgumentList(), data); JavaType javaType = type(type); if (javaType instanceof JavaType.Unknown) { javaType = new JavaType.Parameterized(null, JavaType.Unknown.getInstance(), null); } else if (!(javaType instanceof JavaType.Parameterized)) { throw new UnsupportedOperationException("java type is not a Parameterized: " + type.getText()); } JavaType.Parameterized pt = (JavaType.Parameterized) javaType; return mapType(new J.ParameterizedType( randomId(), Space.EMPTY, Markers.EMPTY, nameTree, args, pt == null ? JavaType.Unknown.getInstance() : pt )); } return nameTree; } @Override public J visitValueArgumentList(KtValueArgumentList list, ExecutionContext data) { throw new UnsupportedOperationException("TODO"); } /*==================================================================== * Mapping methods * ====================================================================*/ @Nullable private K.Binary.Type mapKBinaryType(KtOperationReferenceExpression operationReference) { IElementType elementType = operationReference.getOperationSignTokenType(); if (elementType == null) { return null; } if (elementType == KtTokens.NOT_IN) { return K.Binary.Type.NotContains; } else if (elementType == KtTokens.RANGE) { return K.Binary.Type.RangeTo; } else if (elementType == KtTokens.RANGE_UNTIL) { return K.Binary.Type.RangeUntil; } else if (elementType == KtTokens.IN_KEYWORD) { return K.Binary.Type.Contains; } else if (elementType == KtTokens.EQEQEQ) { return K.Binary.Type.IdentityEquals; } else if (elementType == KtTokens.EXCLEQEQEQ) { return K.Binary.Type.IdentityNotEquals; } else if (elementType == KtTokens.ELVIS) { return K.Binary.Type.Elvis; } else if (elementType == KtTokens.EQ) { return null; } else { throw new UnsupportedOperationException("Unsupported OPERATION_REFERENCE type: " + elementType); } } @Nullable private J.Binary.Type mapJBinaryType(KtOperationReferenceExpression operationReference) { IElementType elementType = operationReference.getOperationSignTokenType(); if (elementType == null) { String operator = operationReference.getText(); if ("and".equals(operator)) { return J.Binary.Type.BitAnd; } else if ("or".equals(operator)) { return J.Binary.Type.BitOr; } else if ("xor".equals(operator)) { return J.Binary.Type.BitXor; } else if ("shl".equals(operator)) { return J.Binary.Type.LeftShift; } else if ("shr".equals(operator)) { return J.Binary.Type.RightShift; } else if ("ushr".equals(operator)) { return J.Binary.Type.UnsignedRightShift; } } if (elementType == KtTokens.PLUS) return J.Binary.Type.Addition; else if (elementType == KtTokens.MINUS) return J.Binary.Type.Subtraction; else if (elementType == KtTokens.MUL) return J.Binary.Type.Multiplication; else if (elementType == KtTokens.DIV) return J.Binary.Type.Division; else if (elementType == KtTokens.EQEQ) return J.Binary.Type.Equal; else if (elementType == KtTokens.EXCLEQ) return J.Binary.Type.NotEqual; else if (elementType == KtTokens.GT) return J.Binary.Type.GreaterThan; else if (elementType == KtTokens.GTEQ) return J.Binary.Type.GreaterThanOrEqual; else if (elementType == KtTokens.LT) return J.Binary.Type.LessThan; else if (elementType == KtTokens.LTEQ) return J.Binary.Type.LessThanOrEqual; else if (elementType == KtTokens.PERC) return J.Binary.Type.Modulo; else if (elementType == KtTokens.ANDAND) return J.Binary.Type.And; else if (elementType == KtTokens.OROR) return J.Binary.Type.Or; else return null; } @Nullable private J.Unary.Type mapJUnaryType(KtSimpleNameExpression operationReference) { IElementType elementType = operationReference.getReferencedNameElementType(); if (elementType == KtTokens.EXCL) { return J.Unary.Type.Not; } else if (elementType == KtTokens.MINUSMINUS) { return J.Unary.Type.PreDecrement; } else if (elementType == KtTokens.PLUSPLUS) { return J.Unary.Type.PreIncrement; } else if (elementType == KtTokens.MINUS) { return J.Unary.Type.Negative; } else if (elementType == KtTokens.PLUSEQ) { return J.Unary.Type.Positive; } else if (elementType == KtTokens.PLUS) { return J.Unary.Type.Positive; } else { return null; } } private J.MethodInvocation mapFunctionCall(KtBinaryExpression expression, ExecutionContext data) { Markers markers = Markers.EMPTY .addIfAbsent(new Infix(randomId())) .addIfAbsent(new Extension(randomId())); Expression selectExp = convertToExpression(requireNonNull(expression.getLeft()).accept(this, data).withPrefix(prefix(expression.getLeft()))); JRightPadded select = padRight(selectExp, Space.EMPTY); J.Identifier name = (J.Identifier) expression.getOperationReference().accept(this, data); // createIdentifier(operation, Space.EMPTY, methodInvocationType(expression)); List> expressions = new ArrayList<>(1); Markers paramMarkers = markers.addIfAbsent(new OmitParentheses(randomId())); Expression rightExp = convertToExpression(requireNonNull(expression.getRight()).accept(this, data).withPrefix(prefix(expression.getRight()))); JRightPadded padded = padRight(rightExp, suffix(expression.getRight())); expressions.add(padded); JContainer args = JContainer.build(Space.EMPTY, expressions, paramMarkers); JavaType.Method methodType = methodInvocationType(expression); return mapType(new J.MethodInvocation( randomId(), prefix(expression), markers, select, null, name, args, methodType )); } private J.ControlParentheses buildIfCondition(KtIfExpression expression) { return new J.ControlParentheses<>(randomId(), prefix(expression.getLeftParenthesis()), Markers.EMPTY, padRight(convertToExpression(requireNonNull(expression.getCondition()).accept(this, executionContext)) .withPrefix(suffix(expression.getLeftParenthesis())), prefix(expression.getRightParenthesis())) ); } private JRightPadded buildIfThenPart(KtIfExpression expression) { // TODO: fix NPE. return padRight(convertToStatement(requireNonNull(expression.getThen()).accept(this, executionContext)) .withPrefix(prefix(expression.getThen().getParent())), Space.EMPTY); } @Nullable private J.If.Else buildIfElsePart(KtIfExpression expression) { if (expression.getElse() == null) { return null; } return new J.If.Else( randomId(), prefix(expression.getElseKeyword()), Markers.EMPTY, padRight(convertToStatement(expression.getElse().accept(this, executionContext)) .withPrefix(suffix(expression.getElseKeyword())), Space.EMPTY) ); } /*==================================================================== * Type related methods * ====================================================================*/ /** * Aligns types between the PSI and FIR to the Java compiler for use in recipes and type utilities. *

* The types associated from the PSI to the FIR do not always align with the expected types on the * J tree. An example is binary operations; In Kotlin, binary operations are method invocations where * the source code appears to be a binary expression like `1 + 1`, but the FIR is a function call `1.plus(1)`. *

* The function call will be mapped to a `JavaType$Method`, which is only necessary if the function is not overloaded. * Otherwise, a J.Binary is created, which should have a type field of `JavaType.Class`. This method will assign * the appropriate JavaType based on the tree and the trees fields. *

* The expected types on fields of `J` trees varies. An example is the name of J.MethodInvocation should * have a type of `JavaType$Method` whereas the name of an Annotation should be a `JavaType.Class`. *

* Finally, we can identify and fix mis-mapped types by detecting unexpected types, marking the elements, * and finding the elements through data-tables. * * @param tree J tree to align types on. * @return Tree with updated types. */ @SuppressWarnings("unchecked") private J2 mapType(J2 tree) { J2 updated = tree; if (tree instanceof J.Annotation) { updated = (J2) mapType((J.Annotation) tree); } else if (tree instanceof J.Assignment) { updated = (J2) mapType((J.Assignment) tree); } else if (tree instanceof J.AssignmentOperation) { updated = (J2) mapType((J.AssignmentOperation) tree); } else if (tree instanceof J.Binary) { updated = (J2) mapType((J.Binary) tree); } else if (tree instanceof J.FieldAccess) { updated = (J2) mapType((J.FieldAccess) tree); } else if (tree instanceof J.MethodDeclaration) { updated = (J2) mapType((J.MethodDeclaration) tree); } else if (tree instanceof J.MethodInvocation) { updated = (J2) mapType((J.MethodInvocation) tree); } else if (tree instanceof J.NewClass) { updated = (J2) mapType((J.NewClass) tree); } else if (tree instanceof J.ParameterizedType) { updated = (J2) mapType((J.ParameterizedType) tree); } else if (tree instanceof J.Unary) { updated = (J2) mapType((J.Unary) tree); } else if (tree instanceof K.Binary) { updated = (J2) mapType((K.Binary) tree); } return updated; } private J.Annotation mapType(J.Annotation tree) { J.Annotation a = tree; if (isNotFullyQualified(tree.getAnnotationType().getType())) { if (a.getAnnotationType().getType() instanceof JavaType.Method) { a = a.withAnnotationType(a.getAnnotationType().withType(((JavaType.Method) a.getAnnotationType().getType()).getReturnType())); } } return a; } private J.Assignment mapType(J.Assignment tree) { J.Assignment a = tree; if (isNotFullyQualified(tree.getType())) { if (a.getType() instanceof JavaType.Method) { a = a.withType(((JavaType.Method) a.getType()).getReturnType()); } else if (a.getType() instanceof JavaType.Variable) { a = a.withType(((JavaType.Variable) a.getType()).getType()); } } return a; } private J.AssignmentOperation mapType(J.AssignmentOperation tree) { J.AssignmentOperation a = tree; if (isNotFullyQualified(tree.getType())) { if (a.getType() instanceof JavaType.Method) { a = a.withType(((JavaType.Method) a.getType()).getReturnType()); } else if (a.getType() instanceof JavaType.Variable) { a = a.withType(((JavaType.Variable) a.getType()).getType()); } } return a; } private J.Binary mapType(J.Binary tree) { J.Binary b = tree; if (isNotFullyQualified(tree.getType())) { if (b.getType() instanceof JavaType.Method) { b = b.withType(((JavaType.Method) b.getType()).getReturnType()); } } return b; } private J.FieldAccess mapType(J.FieldAccess tree) { J.FieldAccess f = tree; if (isNotFullyQualified(tree.getType())) { if (f.getType() instanceof JavaType.Method) { f = f.withType(((JavaType.Method) f.getType()).getReturnType()); } else if (f.getType() instanceof JavaType.Variable) { f = f.withType(((JavaType.Variable) f.getType()).getType()); } return f; } return f; } private J.MethodDeclaration mapType(J.MethodDeclaration tree) { return tree.withName(tree.getName().withType(tree.getMethodType())); } private J.MethodInvocation mapType(J.MethodInvocation tree) { return tree.withName(tree.getName().withType(tree.getMethodType())); } private J.NewClass mapType(J.NewClass tree) { J.NewClass n = tree; if (n.getClazz() != null && n.getClazz() instanceof J.Identifier) { if (n.getClazz().getType() instanceof JavaType.Parameterized) { J.Identifier clazz = (J.Identifier) n.getClazz(); n = n.withClazz(clazz.withType(((JavaType.Parameterized) clazz.getType()).getType())); } } return n; } private J.ParameterizedType mapType(J.ParameterizedType tree) { J.ParameterizedType p = tree; if (p.getType() != null && !(p.getType() instanceof JavaType.Parameterized)) { if (p.getType() instanceof JavaType.Method) { if (((JavaType.Method) p.getType()).getReturnType() instanceof JavaType.Parameterized) { p = p.withType(((JavaType.Method) p.getType()).getReturnType()); } } } if (p.getClazz() != null && p.getClazz().getType() instanceof JavaType.Parameterized) { p = p.withClazz(p.getClazz().withType(((JavaType.Parameterized) p.getClazz().getType()).getType())); } return p; } private J.Unary mapType(J.Unary tree) { J.Unary u = tree; if (isNotFullyQualified(tree.getType())) { if (u.getType() instanceof JavaType.Method) { u = u.withType(((JavaType.Method) u.getType()).getReturnType()); } } return u; } private K.Binary mapType(K.Binary tree) { K.Binary b = tree; if (isNotFullyQualified(b.getType())) { if (b.getType() instanceof JavaType.Method) { b = b.withType(((JavaType.Method) b.getType()).getReturnType()); } } return b; } private boolean isNotFullyQualified(@Nullable JavaType type) { return type != null && !(type instanceof JavaType.FullyQualified); } @Nullable private JavaType type(@Nullable KtElement psi) { if (psi == null) { return JavaType.Unknown.getInstance(); } return psiElementAssociations.type(psi, owner(psi)); } private JavaType.Primitive primitiveType(PsiElement psi) { return psiElementAssociations.primitiveType(psi); } @Nullable private JavaType.Variable variableType(PsiElement psi, @Nullable FirElement parent) { return psiElementAssociations.variableType(psi, parent); } @Nullable private JavaType.Method methodDeclarationType(PsiElement psi) { return psiElementAssociations.methodDeclarationType(psi); } @Nullable private JavaType.Method methodInvocationType(PsiElement psi) { return psiElementAssociations.methodInvocationType(psi); } /*==================================================================== * Other helper methods * ====================================================================*/ private J.Identifier createIdentifier(PsiElement name, @Nullable JavaType type) { return createIdentifier(name, type, null); } private J.Identifier createIdentifier(PsiElement name, @Nullable JavaType type, @Nullable Set consumedSpaces) { return createIdentifier(name.getNode().getText(), prefix(name, consumedSpaces), type); } private J.Identifier createIdentifier(String name, Space prefix, @Nullable JavaType type) { return createIdentifier(name, prefix, type instanceof JavaType.Variable ? ((JavaType.Variable) type).getType() : type, type instanceof JavaType.Variable ? (JavaType.Variable) type : null); } private J.Identifier createIdentifier(String name, Space prefix, @Nullable JavaType type, @Nullable JavaType.Variable fieldType) { Markers markers = Markers.EMPTY; String updated = name; if (name.startsWith("`")) { updated = updated.substring(1, updated.length() - 1); markers = markers.addIfAbsent(new Quoted(randomId())); } return new J.Identifier( randomId(), prefix, markers, emptyList(), updated, type instanceof JavaType.Unknown ? null : type, fieldType ); } private static J.Identifier addPrefixInFront(J.Identifier id, Space prefix) { if (!id.getAnnotations().isEmpty()) { id = id.withAnnotations(ListUtils.mapFirst(id.getAnnotations(), anno -> anno.withPrefix(merge(prefix, anno.getPrefix())))); } else { id = id.withPrefix(merge(prefix, id.getPrefix())); } return id; } @Nullable private FirElement owner(PsiElement element) { KtElement owner = ownerStack.peek() == element ? ownerStack.get(ownerStack.size() - 2) : ownerStack.peek(); if (owner instanceof KtDeclaration) { return psiElementAssociations.primary(owner); } else if (owner instanceof KtFile) { return psiElementAssociations.primary(owner); } return null; } private J.Block convertToBlock(KtExpression ktExpression, ExecutionContext data) { Expression returnExpr = convertToExpression(ktExpression.accept(this, data)).withPrefix(Space.EMPTY); K.Return return_ = new K.Return(randomId(), new J.Return(randomId(), prefix(ktExpression), Markers.EMPTY.addIfAbsent(new ImplicitReturn(randomId())), returnExpr), null); return new J.Block( randomId(), Space.EMPTY, Markers.EMPTY.addIfAbsent(new OmitBraces(randomId())) .addIfAbsent(new SingleExpressionBlock(randomId())), JRightPadded.build(false), singletonList(JRightPadded.build(return_)), Space.EMPTY ); } // Handle trailing semicolon inside the input `element` only. private JRightPadded maybeTrailingSemicolonInternal(J2 j, KtElement element) { PsiElement maybeSemicolon = findLastNotSpaceChild(element); if (isSemicolon(maybeSemicolon)) { return new JRightPadded<>(j, prefix(maybeSemicolon), Markers.EMPTY.add(new Semicolon(randomId()))); } return padRight(j, Space.EMPTY); } // Handle trailing semicolon maybe inside the input `element` or following the input `element` private JRightPadded maybeTrailingSemicolon(J2 j, KtElement element) { // maybe trailing semicolon at the end of the `element` PsiElement maybeSemicolon = findLastNotSpaceChild(element); if (isSemicolon(maybeSemicolon)) { return new JRightPadded<>(j, prefix(maybeSemicolon), Markers.EMPTY.add(new Semicolon(randomId()))); } // maybe following trailing semicolon of the `element` maybeSemicolon = PsiTreeUtil.skipWhitespacesAndCommentsForward(element); if (isSemicolon(maybeSemicolon)) { return new JRightPadded<>(j, deepPrefix(maybeSemicolon), Markers.EMPTY.add(new Semicolon(randomId()))); } return padRight(j, Space.EMPTY); } private boolean isSemicolon(@Nullable PsiElement element) { if (element == null) { return false; } return element instanceof LeafPsiElement && ((LeafPsiElement) element).getElementType() == KtTokens.SEMICOLON; } private JLeftPadded padLeft(Space left, T tree) { return new JLeftPadded<>(left, tree, Markers.EMPTY); } private JRightPadded padRight(T tree, @Nullable Space right) { return padRight(tree, right, Markers.EMPTY); } private JRightPadded padRight(T tree, @Nullable Space right, Markers markers) { return new JRightPadded<>(tree, right == null ? Space.EMPTY : right, markers); } @SuppressWarnings("unchecked") private J2 convertToExpression(J j) { if (j instanceof Statement && !(j instanceof Expression)) { j = new K.StatementExpression(randomId(), (Statement) j); } return (J2) j; } private Statement convertToStatement(J j) { if (!(j instanceof Statement) && j instanceof Expression) { j = new K.ExpressionStatement(randomId(), (Expression) j); } if (!(j instanceof Statement)) { throw new UnsupportedOperationException("TODO"); } return (Statement) j; } private Space prefix(@Nullable PsiElement element) { return prefix(element, null); } private Space prefix(@Nullable PsiElement element, @Nullable Set consumedSpaces) { if (element == null) { return Space.EMPTY; } PsiElement first = findFirstPrefixSpace(element); if (first == null) { return Space.EMPTY; } if (consumedSpaces != null && consumedSpaces.contains(first)) { return Space.EMPTY; } return space(first); } private static List getAllChildren(PsiElement psiElement) { List allChildren = new ArrayList<>(); Iterator iterator = PsiUtilsKt.getAllChildren(psiElement).iterator(); while (iterator.hasNext()) { PsiElement it = iterator.next(); allChildren.add(it); } return allChildren; } @Nullable private PsiElement findFirstPrefixSpace(@Nullable PsiElement element) { if (element == null) { return null; } PsiElement pre = element.getPrevSibling(); if (pre == null || !isSpace(pre.getNode())) { return null; } while (pre.getPrevSibling() != null && isSpace(pre.getPrevSibling().getNode())) { pre = pre.getPrevSibling(); } return pre; } @Nullable private static PsiElement getFirstSpaceChildOrNull(@Nullable PsiElement element) { if (element == null) { return null; } Iterator iterator = PsiUtilsKt.getAllChildren(element).iterator(); PsiElement first = iterator.next(); if (first != null) { return isSpace(first.getNode()) ? first : null; } return null; } private Space suffix(@Nullable PsiElement element) { if (element == null) { return Space.EMPTY; } PsiElement whitespace = element.getNextSibling(); if (whitespace == null || !isSpace(whitespace.getNode())) { return Space.EMPTY; } return space(whitespace); } private Space infix(@Nullable PsiElement element, @Nullable Set consumedSpaces) { if (element == null) { return Space.EMPTY; } PsiElement first = element.getFirstChild(); if (first == null || !isSpace(first.getNode())) { return Space.EMPTY; } if (consumedSpaces != null && consumedSpaces.contains(first)) { return Space.EMPTY; } return space(element.getFirstChild()); } private Space endFix(@Nullable PsiElement element) { if (element == null) { return Space.EMPTY; } Space end = endFix(findLastNotSpaceChild(element)); PsiElement lastSpace = findLastSpaceChild(element); return merge(end, space(lastSpace)); } private Space endFixPrefixAndInfix(@Nullable PsiElement element) { return merge(endFix(PsiTreeUtil.skipWhitespacesAndCommentsBackward(element)), prefixAndInfix(element, null)); } private Space deepPrefix(@Nullable PsiElement element) { return endFixPrefixAndInfix(element); } private Space endFixAndSuffix(@Nullable PsiElement element) { return merge(endFix(element), suffix(element)); } private Space prefixAndInfix(@Nullable PsiElement element, @Nullable Set consumedSpaces) { if (element == null) { return Space.EMPTY; } return merge(prefix(element, consumedSpaces), infix(element, consumedSpaces)); } private static boolean isSpace(ASTNode node) { IElementType elementType = node.getElementType(); return elementType == KtTokens.WHITE_SPACE || elementType == KtTokens.BLOCK_COMMENT || elementType == KtTokens.EOL_COMMENT || elementType == KtTokens.DOC_COMMENT || isCRLF(node); } private static boolean isLPAR(PsiElement element) { return element instanceof LeafPsiElement && ((LeafPsiElement) element).getElementType() == KtTokens.LPAR; } private static boolean isRPAR(PsiElement element) { return element instanceof LeafPsiElement && ((LeafPsiElement) element).getElementType() == KtTokens.RPAR; } private static int findFirstLPAR(List elements, int start) { int ret = -1; for (int i = start; i < elements.size(); i++) { if (isLPAR(elements.get(i))) { return i; } } return ret; } private static int findLastRPAR(List elements, int end) { int ret = -1; for (int i = end; i >= 0; i--) { if (isRPAR(elements.get(i))) { return i; } } return ret; } private static boolean isCRLF(ASTNode node) { return node instanceof PsiErrorElementImpl && node.getText().equals("\r"); } private String nodeRangeText(@Nullable ASTNode first, @Nullable ASTNode last) { StringBuilder builder = new StringBuilder(); while (first != null) { builder.append(first.getText()); if (first == last) { break; } first = first.getTreeNext(); } return builder.toString(); } private List mapAnnotations(List ktAnnotationEntries, ExecutionContext data) { return ktAnnotationEntries.stream() .map(annotation -> (J.Annotation) annotation.accept(this, data)) .collect(Collectors.toList()); } private J mapDestructuringDeclaration(KtDestructuringDeclaration ktDestructuringDeclaration, ExecutionContext data) { List entries = ktDestructuringDeclaration.getEntries(); List> variables = new ArrayList<>(entries.size()); for (KtDestructuringDeclarationEntry ktDestructuringDeclarationEntry : entries) { J.Identifier name = (J.Identifier) ktDestructuringDeclarationEntry.accept(this, data); J.VariableDeclarations.NamedVariable namedVariable = new J.VariableDeclarations.NamedVariable( randomId(), Space.EMPTY, Markers.EMPTY, name, emptyList(), null, variableType(ktDestructuringDeclarationEntry, owner(ktDestructuringDeclarationEntry)) ); variables.add(padRight(namedVariable, suffix(ktDestructuringDeclarationEntry))); } J j = new J.VariableDeclarations( randomId(), prefix(ktDestructuringDeclaration), Markers.EMPTY.addIfAbsent(new OmitEquals(randomId())), emptyList(), emptyList(), null, null, emptyList(), variables ); if (entries.size() == 1) { // Handle potential redundant parentheses List allChildren = getAllChildren(ktDestructuringDeclaration); int l = findFirstLPAR(allChildren, 0); int r = findLastRPAR(allChildren, allChildren.size() - 1); if (l >= 0 && l < r) { j = new J.Parentheses<>(randomId(), Space.EMPTY, Markers.EMPTY, padRight(j, Space.EMPTY)); } } return j; } private J.Modifier mapModifier(PsiElement modifier, List annotations, @Nullable Set consumedSpaces) { Space prefix = prefix(modifier, consumedSpaces); J.Modifier.Type type; String keyword = null; switch (modifier.getText()) { case "public": type = J.Modifier.Type.Public; break; case "protected": type = J.Modifier.Type.Protected; break; case "private": type = J.Modifier.Type.Private; break; case "abstract": type = J.Modifier.Type.Abstract; break; case "val": type = J.Modifier.Type.Final; break; default: type = J.Modifier.Type.LanguageExtension; keyword = modifier.getText(); break; } return new J.Modifier( randomId(), prefix, Markers.EMPTY, keyword, type, annotations ); } private List> mapParameters(@Nullable KtParameterList list, ExecutionContext data) { if (list == null) { return emptyList(); } List ktParameters = list.getParameters(); List> statements = new ArrayList<>(ktParameters.size()); for (int i = 0; i < ktParameters.size(); i++) { KtParameter ktParameter = ktParameters.get(i); Statement statement = convertToStatement(ktParameter.accept(this, data)); statements.add(maybeTrailingComma(ktParameter, padRight(statement, endFixAndSuffix(ktParameter)), i == ktParameters.size() - 1)); } if (ktParameters.isEmpty()) { Statement param = new J.Empty(randomId(), prefix(list.getRightParenthesis()), Markers.EMPTY); statements.add(padRight(param, Space.EMPTY)); } return statements; } @Nullable private JContainer mapSuperTypeList(@Nullable KtSuperTypeList ktSuperTypeList, ExecutionContext data) { if (ktSuperTypeList == null) { return null; } List ktSuperTypeListEntries = ktSuperTypeList.getEntries(); List> superTypes = new ArrayList<>(ktSuperTypeListEntries.size()); Markers markers = Markers.EMPTY; for (int i = 0; i < ktSuperTypeListEntries.size(); i++) { KtSuperTypeListEntry superTypeListEntry = ktSuperTypeListEntries.get(i); if (superTypeListEntry instanceof KtSuperTypeCallEntry) { KtSuperTypeCallEntry superTypeCallEntry = (KtSuperTypeCallEntry) superTypeListEntry; TypeTree typeTree = (TypeTree) superTypeCallEntry.getCalleeExpression().accept(this, data); JContainer args; if (!superTypeCallEntry.getValueArguments().isEmpty()) { args = mapValueArguments(superTypeCallEntry.getValueArgumentList(), data); } else { KtValueArgumentList ktArgList = superTypeCallEntry.getValueArgumentList(); args = JContainer.build( prefix(ktArgList), singletonList(padRight(new J.Empty(randomId(), prefix(requireNonNull(ktArgList).getRightParenthesis()), Markers.EMPTY), Space.EMPTY)), markers ); } if (i == 0) { if (typeTree instanceof J.Identifier && !((J.Identifier) typeTree).getAnnotations().isEmpty()) { J.Identifier ident = (J.Identifier) typeTree; typeTree = ident.withAnnotations(ListUtils.mapFirst(ident.getAnnotations(), a -> a.withPrefix(prefix(superTypeListEntry.getParent())))); } else { typeTree = typeTree.withPrefix(prefix(superTypeListEntry.getParent())); } } K.ConstructorInvocation delegationCall = new K.ConstructorInvocation( randomId(), prefix(superTypeListEntry), Markers.EMPTY, typeTree, args ); superTypes.add(padRight(delegationCall, suffix(superTypeCallEntry))); } else if (superTypeListEntry instanceof KtSuperTypeEntry || superTypeListEntry instanceof KtDelegatedSuperTypeEntry) { TypeTree typeTree = (TypeTree) superTypeListEntry.accept(this, data); if (i == 0) { if (typeTree instanceof J.Identifier && !((J.Identifier) typeTree).getAnnotations().isEmpty()) { J.Identifier ident = (J.Identifier) typeTree; typeTree = ident.withAnnotations(ListUtils.mapFirst(ident.getAnnotations(), a -> a.withPrefix(prefix(superTypeListEntry.getParent())))); } else { typeTree = typeTree.withPrefix(prefix(superTypeListEntry.getParent())); } } superTypes.add(padRight(typeTree, suffix(superTypeListEntry))); } else { throw new UnsupportedOperationException("TODO"); } } return JContainer.build(Space.EMPTY, superTypes, Markers.EMPTY); } /** * Compared to the other mapValueArguments method, this method supports trailing lambda */ private JContainer mapValueArgumentsMaybeWithTrailingLambda(@Nullable KtValueArgumentList valueArgumentList, List ktValueArguments, ExecutionContext data) { List> expressions = new ArrayList<>(ktValueArguments.size()); Markers markers = Markers.EMPTY; if (valueArgumentList != null && valueArgumentList.getArguments().isEmpty()) { Expression arg = new J.Empty(randomId(), prefix(valueArgumentList.getRightParenthesis()), Markers.EMPTY); expressions.add(padRight(arg, Space.EMPTY)); } for (int i = 0; i < ktValueArguments.size(); i++) { KtValueArgument ktValueArgument = ktValueArguments.get(i); Expression expr = convertToExpression(ktValueArgument.accept(this, data)); Markers rpMarkers = Markers.EMPTY; if (valueArgumentList != null && i == valueArgumentList.getArguments().size() - 1) { PsiElement maybeTrailingComma = findTrailingComma(ktValueArgument); if (maybeTrailingComma != null) { rpMarkers = rpMarkers.addIfAbsent(new TrailingComma(randomId(), suffix(maybeTrailingComma))); } } expressions.add(padRight(expr, suffix(ktValueArgument), rpMarkers)); } if (valueArgumentList == null) { markers = markers.addIfAbsent(new OmitParentheses(randomId())); } Space prefix = valueArgumentList != null ? prefix(valueArgumentList) : Space.EMPTY; return JContainer.build(prefix, expressions, markers); } private JContainer mapValueArguments(@Nullable KtValueArgumentList argumentList, ExecutionContext data) { if (argumentList == null) { return JContainer.empty(); } List ktValueArguments = argumentList.getArguments(); List> expressions = new ArrayList<>(ktValueArguments.size()); if (ktValueArguments.isEmpty()) { Expression arg = new J.Empty(randomId(), prefix(argumentList.getRightParenthesis()), Markers.EMPTY); expressions.add(padRight(arg, Space.EMPTY)); } else { for (int i = 0; i < ktValueArguments.size(); i++) { KtValueArgument ktValueArgument = ktValueArguments.get(i); Expression expr = convertToExpression(ktValueArgument.accept(this, data)); expressions.add(maybeTrailingComma(ktValueArgument, padRight(expr, suffix(ktValueArgument)), i == ktValueArguments.size() - 1)); } } return JContainer.build(prefix(argumentList), expressions, Markers.EMPTY); } private Space toSpace(@Nullable PsiElement element) { if (element == null || !isSpace(element.getNode())) { return Space.EMPTY; } IElementType elementType = element.getNode().getElementType(); if (elementType == KtTokens.WHITE_SPACE) { return Space.build(maybeAdjustCRLF(element), emptyList()); } else if (elementType == KtTokens.EOL_COMMENT || elementType == KtTokens.BLOCK_COMMENT) { String nodeText = maybeAdjustCRLF(element); boolean isBlockComment = ((PsiComment) element).getTokenType() == KtTokens.BLOCK_COMMENT; String comment = isBlockComment ? nodeText.substring(2, nodeText.length() - 2) : nodeText.substring(2); List comments = new ArrayList<>(1); comments.add(new TextComment(isBlockComment, comment, "", Markers.EMPTY)); return Space.build("", comments); } else if (elementType == KtTokens.DOC_COMMENT) { return kdocToSpace((KDoc) element); } return Space.EMPTY; } // replace `\n` to CRLF back if it's CRLF in the source private String maybeAdjustCRLF(PsiElement element) { String text = element.getText(); boolean isStringTemplateEntry = element instanceof KtLiteralStringTemplateEntry; if (!isSpace(element.getNode()) && !isStringTemplateEntry) { return text; } TextRange range = element.getTextRange(); int left = findFirstGreaterOrEqual(cRLFLocations, range.getStartOffset()); int right = left != -1 ? findFirstLessOrEqual(cRLFLocations, range.getEndOffset(), left) : -1; boolean hasCRLF = left != -1 && left <= right; if (hasCRLF) { for (int i = right; i >= left; i--) { text = replaceNewLineWithCRLF(text, cRLFLocations.get(i) - range.getStartOffset()); } } return text; } private Space kdocToSpace(KDoc kDoc) { StringBuilder sb = new StringBuilder(); Iterator iterator = PsiUtilsKt.getAllChildren(kDoc).iterator(); while (iterator.hasNext()) { PsiElement it = iterator.next(); String text = it.getText(); if (it instanceof PsiWhiteSpace) { text = replaceCRLF((PsiWhiteSpace) it); } else if (it instanceof KDocSection) { text = KDocSectionToString((KDocSection) it); } sb.append(text); } String source = sb.toString(); String comment = source.substring(2, source.length() - 2); List comments = new ArrayList<>(1); comments.add(new TextComment(true, comment, "", Markers.EMPTY)); return Space.build("", comments); } private String KDocSectionToString(KDocSection kDocSection) { StringBuilder sb = new StringBuilder(); Iterator iterator = PsiUtilsKt.getAllChildren(kDocSection).iterator(); while (iterator.hasNext()) { PsiElement it = iterator.next(); String text = it.getText(); if (it instanceof PsiWhiteSpace) { text = replaceCRLF((PsiWhiteSpace) it); } sb.append(text); } return sb.toString(); } // replace `\n` to CRLF back if it's CRLF in the source private String replaceCRLF(PsiWhiteSpace wp) { String text = wp.getText(); TextRange range = wp.getTextRange(); int left = findFirstGreaterOrEqual(cRLFLocations, range.getStartOffset()); int right = left != -1 ? findFirstLessOrEqual(cRLFLocations, range.getEndOffset(), left) : -1; boolean hasCRLF = left != -1 && left <= right; if (hasCRLF) { for (int i = right; i >= left; i--) { text = replaceNewLineWithCRLF(text, cRLFLocations.get(i) - range.getStartOffset()); } } return text; } private Space space(@Nullable PsiElement node) { Space space = Space.EMPTY; while (node != null) { if (isSpace(node.getNode())) { space = merge(space, toSpace(node)); } else { break; } node = node.getNextSibling(); } return space; } public static Space merge(@Nullable Space s1, @Nullable Space s2) { if (s1 == null || s1.isEmpty()) { return s2 != null ? s2 : Space.EMPTY; } else if (s2 == null || s2.isEmpty()) { return s1; } if (s1.getComments().isEmpty()) { return Space.build(s1.getWhitespace() + s2.getWhitespace(), s2.getComments()); } else { List newComments = ListUtils.mapLast(s1.getComments(), c -> c.withSuffix(c.getSuffix() + s2.getWhitespace())); newComments.addAll(s2.getComments()); return Space.build(s1.getWhitespace(), newComments); } } private static J.Identifier mergePrefix(Space prefix, J.Identifier id) { return id.getAnnotations().isEmpty() ? id.withPrefix(merge(prefix, id.getPrefix())) : id.withAnnotations(ListUtils.mapFirst(id.getAnnotations(), ann -> ann.withPrefix(merge(prefix, ann.getPrefix())))); } private J.Modifier buildFinalModifier() { return new J.Modifier( randomId(), Space.EMPTY, Markers.EMPTY, null, J.Modifier.Type.Final, emptyList() ); } @Nullable private PsiElement findLastNotSpaceChild(@Nullable PsiElement parent) { return findLastChild(parent, psi -> !isSpace(psi.getNode())); } @Nullable private static PsiElement findFirstChild(@Nullable PsiElement parent, Predicate condition) { if (parent == null) { return null; } for (PsiElement child = parent.getFirstChild(); child != null; child = child.getNextSibling()) { if (condition.test(child)) { return child; } } return null; } @Nullable private static PsiElement findFirstNonSpaceChild(@Nullable PsiElement parent) { return findFirstChild(parent, child -> !isSpace(child.getNode())); } @Nullable private static PsiElement findLastChild(@Nullable PsiElement parent, Predicate condition) { if (parent == null) { return null; } for (PsiElement child = parent.getLastChild(); child != null; child = child.getPrevSibling()) { if (condition.test(child)) { return child; } } return null; } @Nullable private PsiElement findLastSpaceChild(@Nullable PsiElement parent) { if (parent == null) { return null; } PsiElement ret = null; for (PsiElement child = parent.getLastChild(); child != null; child = child.getPrevSibling()) { if (isSpace(child.getNode())) { ret = child; } else { break; } } return ret; } private static void addIfNotNull(Set set, @Nullable E element) { if (element != null) { set.add(element); } } private static Set preConsumedInfix(PsiElement element) { Set consumed = new HashSet<>(); addIfNotNull(consumed, getFirstSpaceChildOrNull(element)); return consumed; } @Nullable private PsiElement findTrailingComma(PsiElement element) { PsiElement nextSibling = element.getNextSibling(); while (nextSibling != null) { if (nextSibling.getNode().getElementType() == KtTokens.COMMA) { return nextSibling; } nextSibling = nextSibling.getNextSibling(); } return null; } private int findFirstGreaterOrEqual(List sequence, int target) { int left = 0; int right = sequence.size() - 1; int resultIndex = -1; while (left <= right) { int mid = left + (right - left) / 2; int midValue = sequence.get(mid); if (midValue >= target) { resultIndex = mid; right = mid - 1; } else { left = mid + 1; } } return resultIndex; } private int findFirstLessOrEqual(List sequence, int target, int left) { int right = sequence.size() - 1; int resultIndex = -1; while (left <= right) { int mid = left + (right - left) / 2; int midValue = sequence.get(mid); if (midValue <= target) { resultIndex = mid; left = mid + 1; } else { right = mid - 1; } } return resultIndex; } private String replaceNewLineWithCRLF(String source, int index) { if (index < 0 || index >= source.length()) { return source; } StringBuilder sb = new StringBuilder(source); char charAtIndex = source.charAt(index); if (charAtIndex != '\n') { return source; } sb.setCharAt(index, '\r'); sb.insert(index + 1, '\n'); return sb.toString(); } private static @Nullable ASTNode getNodeOrNull(@Nullable PsiElement psiElement) { return psiElement != null ? psiElement.getNode() : null; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy