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

main.org.openrewrite.kotlin.AddImport Maven / Gradle / Ivy

There is a newer version: 1.22.1
Show newest version
/*
 * 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; import lombok.EqualsAndHashCode; import org.jspecify.annotations.Nullable; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.search.FindMethods; import org.openrewrite.java.search.FindTypes; import org.openrewrite.java.tree.*; import org.openrewrite.kotlin.style.ImportLayoutStyle; import org.openrewrite.kotlin.style.IntelliJ; import org.openrewrite.kotlin.tree.K; import org.openrewrite.marker.Markers; import org.openrewrite.style.GeneralFormatStyle; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; import static org.openrewrite.java.format.AutodetectGeneralFormatStyle.autodetectGeneralFormatStyle; import static org.openrewrite.java.tree.TypeUtils.isOfClassType; /** * A Kotlin refactoring visitor that can be used to add imports to a given compilation unit. * This visitor can also be configured to only add the import if the imported class / member is referenced within the * compilation unit. *

* The {@link AddImport#fullyQualifiedName} must be supplied and represents a fully qualified class name. *

* The {@link AddImport#member} is an optional member within the imported type. It can be set to "*" * to represent a wildcard import. *

* The import can optionally also be configured with an alias. *

* The {@link AddImport#onlyIfReferenced} is a flag (defaulted to {@code true}) to indicate if the import should only be added * if there is a reference to the imported class / member. */ @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) public class AddImport

extends KotlinIsoVisitor

{ private static final Set IMPLICITLY_IMPORTED_PACKAGES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( "kotlin", "kotlin.annotation", "kotlin.collections", "kotlin.comparisons", "kotlin.io", "kotlin.ranges", "kotlin.sequences", "kotlin.text", "kotlin.math", "java.lang" ))); @Nullable private final String packageName; private final String typeName; @EqualsAndHashCode.Include private final String fullyQualifiedName; @EqualsAndHashCode.Include @Nullable private final String member; @EqualsAndHashCode.Include @Nullable private final String alias; @EqualsAndHashCode.Include private final boolean onlyIfReferenced; public AddImport(@Nullable String packageName, String typeName, @Nullable String member, @Nullable String alias, boolean onlyIfReferenced) { this.packageName = packageName == null || packageName.isEmpty() ? null : packageName; this.typeName = typeName.replace('.', '$'); this.fullyQualifiedName = packageName == null ? typeName : packageName + "." + typeName; this.member = member; this.alias = alias; this.onlyIfReferenced = onlyIfReferenced; } @Override public @Nullable J preVisit(J tree, P p) { stopAfterPreVisit(); J j = tree; if (packageName != null && tree instanceof K.CompilationUnit) { K.CompilationUnit cu = (K.CompilationUnit) tree; if (JavaType.Primitive.fromKeyword(fullyQualifiedName) != null) { return cu; } if (alias == null) { // No need to add imports if the class to import is implicitly imported, or if the classes are within the same package if ((IMPLICITLY_IMPORTED_PACKAGES.contains(packageName) && StringUtils.isBlank(member)) || (cu.getPackageDeclaration() != null && packageName.equals(cu.getPackageDeclaration().getExpression().printTrimmed(getCursor())))) { return cu; } } if (onlyIfReferenced && !hasReference(cu)) { return cu; } if (cu.getImports().stream().anyMatch(i -> { String ending = i.getQualid().getSimpleName(); String alias1 = Optional.ofNullable(i.getAlias()).map(J.Identifier::getSimpleName).orElse(""); String alias2 = alias != null ? alias : ""; if (!alias1.equals(alias2)) { return false; } if (member == null) { return i.getPackageName().equals(packageName) && (ending.equals(typeName) || "*".equals(ending)); } return i.getPackageName().equals(fullyQualifiedName) && (ending.equals(member) || "*".equals(ending)); })) { return cu; } J.Import importToAdd = new J.Import(randomId(), Space.EMPTY, Markers.EMPTY, new JLeftPadded<>(Space.EMPTY, member != null, Markers.EMPTY), TypeTree.build(fullyQualifiedName + (member == null ? "" : "." + member), '`').withPrefix(Space.SINGLE_SPACE), alias != null ? new JLeftPadded<>( Space.SINGLE_SPACE, new J.Identifier( randomId(), Space.SINGLE_SPACE, Markers.EMPTY, emptyList(), alias, null, null ), Markers.EMPTY ) : null ); List> imports = new ArrayList<>(cu.getPadding().getImports()); if (imports.isEmpty() && !cu.getClasses().isEmpty()) { if (cu.getPackageDeclaration() == null) { // leave javadocs on the class and move other comments up to the import // (which could include license headers and the like) String whitespace = ""; if (!cu.getAnnotations().isEmpty()) { // The 1st import added after annotation needs to be in a new line whitespace = "\n\n"; } Space firstStatementPrefix = cu.getStatements().get(0).getPrefix(); importToAdd = importToAdd.withPrefix(firstStatementPrefix .withComments(ListUtils.map(firstStatementPrefix.getComments(), comment -> comment instanceof Javadoc ? null : comment)) .withWhitespace(whitespace)); cu = cu.withStatements(ListUtils.mapFirst(cu.getStatements(), stmt -> stmt.withComments(ListUtils.map(stmt.getComments(), comment -> comment instanceof Javadoc ? comment : null)) )); } } ImportLayoutStyle layoutStyle = Optional.ofNullable(cu.getStyle(ImportLayoutStyle.class)) .orElse(IntelliJ.importLayout()); List classpath = cu.getMarkers().findFirst(JavaSourceSet.class) .map(JavaSourceSet::getClasspath) .orElse(emptyList()); List> newImports = layoutStyle.addImport(cu.getPadding().getImports(), importToAdd, cu.getPackageDeclaration(), classpath); // ImportLayoutStyle::addImport adds always `\n` as newlines. Checking if we need to fix them GeneralFormatStyle generalFormatStyle = Optional.ofNullable(cu.getStyle(GeneralFormatStyle.class)) .orElse(autodetectGeneralFormatStyle(cu)); newImports = checkCRLF(newImports, generalFormatStyle); cu = cu.getPadding().withImports(newImports); // make sure first statement has a prefix if necessary if (((K.CompilationUnit) tree).getImports().isEmpty() || ((K.CompilationUnit) tree).getPackageDeclaration() == null) { cu = cu.withStatements(ListUtils.mapFirst(cu.getStatements(), stmt -> stmt.getPrefix().isEmpty() ? stmt.withPrefix(stmt.getPrefix().withWhitespace(generalFormatStyle.isUseCRLFNewLines() ? "\r\n\r\n" : "\n\n")) : stmt)); } j = cu; } return j; } // TODO refactor ImportLayoutStyle so that this method can be removed private List> checkCRLF(List> newImports, GeneralFormatStyle generalFormatStyle) { if (generalFormatStyle.isUseCRLFNewLines()) { return ListUtils.map(newImports, rp -> rp.map( i -> i.withPrefix(i.getPrefix().withWhitespace(i.getPrefix().getWhitespace() .replaceAll("(? hasStaticFieldAccess = new AtomicReference<>(false); new AddImport

.FindStaticFieldAccess().visit(compilationUnit, hasStaticFieldAccess); return hasStaticFieldAccess.get(); } private class FindStaticFieldAccess extends JavaIsoVisitor> { @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, AtomicReference found) { // If the type isn't used there's no need to proceed further for (JavaType.Variable varType : cu.getTypesInUse().getVariables()) { if (varType.getName().equals(member) && isOfClassType(varType.getType(), fullyQualifiedName)) { return super.visitCompilationUnit(cu, found); } } return cu; } @Override public J.Identifier visitIdentifier(J.Identifier identifier, AtomicReference found) { assert getCursor().getParent() != null; if (identifier.getSimpleName().equals(member) && isOfClassType(identifier.getType(), fullyQualifiedName) && !(getCursor().getParent().firstEnclosingOrThrow(J.class) instanceof J.FieldAccess)) { found.set(true); } return identifier; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy