Inject Bean completion proposal via Rewrite recipes
This commit is contained in:
@@ -14,6 +14,7 @@ package org.springframework.ide.vscode.commons.languageserver.completion;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
|
||||
@@ -57,4 +58,9 @@ public interface ICompletionProposal {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
default Optional<Command> getCommand() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -300,6 +300,7 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
}
|
||||
|
||||
List<Object> commands = new ArrayList<>(2);
|
||||
completion.getCommand().ifPresent(commands::add);
|
||||
if (LspClient.currentClient() != LspClient.Client.ECLIPSE) {
|
||||
/*
|
||||
* Eclipse client always send completionItem resolve request before applying completion.
|
||||
@@ -352,7 +353,9 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
}
|
||||
}
|
||||
if (subCommands.size() == 1) {
|
||||
item.setCommand((Command)subCommands.get(0));
|
||||
Object o = subCommands.get(0);
|
||||
Command subCommand = o instanceof Command ? (Command) o : GSON.fromJson(o instanceof JsonElement ? (JsonElement) o : GSON.toJsonTree(o), Command.class);
|
||||
item.setCommand(subCommand);
|
||||
} else if (subCommands.isEmpty()) {
|
||||
item.setCommand(null);
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd
|
||||
if (CODE_ACTION_COMMAND_ID.equals(params.getCommand())) {
|
||||
Assert.isLegal(params.getArguments().size()==2);
|
||||
QuickfixResolveParams quickfixParams = new QuickfixResolveParams(
|
||||
((JsonPrimitive)params.getArguments().get(0)).getAsString(), params.getArguments().get(1)
|
||||
params.getArguments().get(0) instanceof JsonPrimitive ? ((JsonPrimitive)params.getArguments().get(0)).getAsString() : params.getArguments().get(0).toString() , params.getArguments().get(1)
|
||||
);
|
||||
return quickfixResolve(quickfixParams)
|
||||
.flatMap((QuickfixEdit edit) -> {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.java.spring;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.openrewrite.*;
|
||||
import org.openrewrite.properties.tree.Properties;
|
||||
import org.openrewrite.yaml.tree.Yaml;
|
||||
|
||||
public class CommentOutSpringPropertyKey extends Recipe {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Comment out Spring properties";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Add comment to specified Spring properties, and comment out the property.";
|
||||
}
|
||||
|
||||
@Option(displayName = "Property key",
|
||||
description = "The name of the property key to comment out.",
|
||||
example = "management.metrics.binders.files.enabled")
|
||||
String propertyKey;
|
||||
|
||||
@Option(displayName = "Comment",
|
||||
description = "Comment to replace the property key.",
|
||||
example = "This property is deprecated and no longer applicable starting from Spring Boot 3.0.x")
|
||||
String comment;
|
||||
|
||||
|
||||
public CommentOutSpringPropertyKey(String propertyKey, String comment) {
|
||||
super();
|
||||
this.propertyKey = propertyKey;
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
Recipe changeProperties = new org.openrewrite.properties.AddPropertyComment(propertyKey, comment, true);
|
||||
Recipe changeYaml = new org.openrewrite.yaml.CommentOutProperty(propertyKey, comment, true);
|
||||
return new TreeVisitor<Tree, ExecutionContext>() {
|
||||
@Override
|
||||
public @Nullable Tree preVisit(@NonNull Tree tree, ExecutionContext ctx) {
|
||||
stopAfterPreVisit();
|
||||
if (tree instanceof Properties.File) {
|
||||
return changeProperties.getVisitor().visit(tree, ctx);
|
||||
} else if (tree instanceof Yaml.Documents) {
|
||||
return changeYaml.getVisitor().visit(tree, ctx);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public String getPropertyKey() {
|
||||
return propertyKey;
|
||||
}
|
||||
|
||||
public void setPropertyKey(String propertyKey) {
|
||||
this.propertyKey = propertyKey;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + Objects.hash(comment, propertyKey);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!super.equals(obj))
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
CommentOutSpringPropertyKey other = (CommentOutSpringPropertyKey) obj;
|
||||
return Objects.equals(comment, other.comment) && Objects.equals(propertyKey, other.propertyKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.eclipse.lsp4j.CreateFile;
|
||||
import org.eclipse.lsp4j.DeleteFile;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.ResourceOperation;
|
||||
import org.eclipse.lsp4j.TextDocumentEdit;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
|
||||
@@ -71,6 +72,44 @@ public class ORDocUtils {
|
||||
return Optional.empty();
|
||||
|
||||
}
|
||||
|
||||
public static Optional<DocumentEdits> computeDocumentEdits(WorkspaceEdit we, IDocument doc) {
|
||||
if (!we.getDocumentChanges().isEmpty()) {
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
List<Either<TextDocumentEdit, ResourceOperation>> changes = we.getDocumentChanges();
|
||||
for (Either<TextDocumentEdit, ResourceOperation> change : changes) {
|
||||
if (change.isLeft()) {
|
||||
TextDocumentEdit textDocumentEdit = change.getLeft();
|
||||
List<TextEdit> textEdits = textDocumentEdit.getEdits();
|
||||
for (TextEdit textEdit : textEdits) {
|
||||
Range range = textEdit.getRange();
|
||||
Position start = range.getStart();
|
||||
Position end = range.getEnd();
|
||||
String newText = textEdit.getNewText();
|
||||
|
||||
try {
|
||||
int startOffset = doc.getLineOffset(start.getLine()) + start.getCharacter();
|
||||
int endOffset = doc.getLineOffset(end.getLine()) + end.getCharacter();
|
||||
|
||||
if (startOffset == endOffset) {
|
||||
edits.insert(startOffset, newText);
|
||||
} else if (newText.isEmpty()) {
|
||||
edits.delete(startOffset, endOffset);
|
||||
} else {
|
||||
edits.replace(startOffset, endOffset, newText);
|
||||
}
|
||||
} catch (BadLocationException ex) {
|
||||
log.error("Failed to apply text edit", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.of(edits);
|
||||
}
|
||||
return Optional.empty();
|
||||
|
||||
}
|
||||
|
||||
public static Optional<TextDocumentEdit> computeTextDocEdit(TextDocument doc, String oldContent, String newContent, String changeAnnotationId) {
|
||||
TextDocument newDoc = new TextDocument(null, LanguageId.PLAINTEXT, 0, newContent);
|
||||
@@ -172,7 +211,7 @@ public class ORDocUtils {
|
||||
addToWorkspaceEdit(documents, docUri, oldContent, newContent, changeAnnotationId, we);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void addToWorkspaceEdit(SimpleTextDocumentService documents, String docUri, String oldContent, String newContent, String changeAnnotationId, WorkspaceEdit we) {
|
||||
if(oldContent == null) {
|
||||
createNewFileEdit(docUri, newContent, changeAnnotationId, we);
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.openrewrite.java.tree.JRightPadded;
|
||||
import org.openrewrite.java.tree.JavaType;
|
||||
import org.openrewrite.java.tree.Space;
|
||||
import org.openrewrite.java.tree.Statement;
|
||||
import org.openrewrite.java.tree.TypeTree;
|
||||
import org.openrewrite.java.tree.TypeUtils;
|
||||
import org.openrewrite.marker.Markers;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
*/
|
||||
public class AddFieldRecipe extends Recipe {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Add field";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Add field desccription.";
|
||||
}
|
||||
|
||||
String fullyQualifiedName;
|
||||
|
||||
@NonNull
|
||||
String classFqName;
|
||||
|
||||
@JsonCreator
|
||||
public AddFieldRecipe(@NonNull @JsonProperty("fullyQualifiedClassName") String fullyQualifiedName, @NonNull @JsonProperty("classFqName") String classFqName) {
|
||||
this.fullyQualifiedName = fullyQualifiedName;
|
||||
this.classFqName = classFqName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
|
||||
return new JavaIsoVisitor<ExecutionContext>() {
|
||||
|
||||
JavaType.FullyQualified fullyQualifiedType = JavaType.ShallowClass.build(fullyQualifiedName);
|
||||
String fieldType = getFieldType(fullyQualifiedType);
|
||||
String fieldName = getFieldName(fullyQualifiedType);
|
||||
|
||||
@Override
|
||||
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
|
||||
if (TypeUtils.isOfClassType(classDecl.getType(), classFqName)) {
|
||||
|
||||
// Check if the class already has the field
|
||||
boolean hasOwnerRepoField = classDecl.getBody().getStatements().stream()
|
||||
.filter(J.VariableDeclarations.class::isInstance).map(J.VariableDeclarations.class::cast)
|
||||
.anyMatch(varDecl -> varDecl.getTypeExpression() != null
|
||||
&& varDecl.getTypeExpression().toString().equals(fieldType));
|
||||
|
||||
if (!hasOwnerRepoField) {
|
||||
J.VariableDeclarations newFieldDecl = new J.VariableDeclarations(
|
||||
Tree.randomId(),
|
||||
Space.build("\n\n", Collections.emptyList()),
|
||||
Markers.EMPTY,
|
||||
Collections.emptyList(),
|
||||
List.of(
|
||||
new J.Modifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, "private", J.Modifier.Type.Private, Collections.emptyList()),
|
||||
new J.Modifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, "final", J.Modifier.Type.Final, Collections.emptyList())
|
||||
),
|
||||
TypeTree.build(fieldType),
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
List.of(JRightPadded.build(new J.VariableDeclarations.NamedVariable(
|
||||
Tree.randomId(),
|
||||
Space.EMPTY,
|
||||
Markers.EMPTY,
|
||||
new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), fieldName, fullyQualifiedType, null),
|
||||
Collections.emptyList(),
|
||||
null,
|
||||
null
|
||||
)))
|
||||
);
|
||||
Statement formattedNewFieldDecl = autoFormat(classDecl.getBody().withStatements(List.of(newFieldDecl)), ctx, getCursor()).getStatements().get(0);
|
||||
List<Statement> newStatements = new ArrayList<>(classDecl.getBody().getStatements().size() + 1);
|
||||
newStatements.add(formattedNewFieldDecl);
|
||||
newStatements.addAll(classDecl.getBody().getStatements());
|
||||
classDecl = classDecl.withBody(classDecl.getBody().withStatements(newStatements));
|
||||
|
||||
|
||||
maybeAddImport(fullyQualifiedType.getFullyQualifiedName(), false);
|
||||
}
|
||||
return classDecl;
|
||||
}
|
||||
classDecl = (J.ClassDeclaration) super.visitClassDeclaration(classDecl, ctx);
|
||||
return classDecl;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String getFieldName(JavaType.FullyQualified fullyQualifiedType) {
|
||||
return Character.toLowerCase(fullyQualifiedType.getClassName().charAt(0)) + fullyQualifiedType.getClassName().substring(1);
|
||||
}
|
||||
|
||||
private static String getFieldType(JavaType.FullyQualified fullyQualifiedType) {
|
||||
if(fullyQualifiedType.getOwningClass() != null) {
|
||||
String[] parts = fullyQualifiedType.getFullyQualifiedName().split("\\.");
|
||||
if (parts.length < 2) {
|
||||
return fullyQualifiedType.getClassName();
|
||||
}
|
||||
return parts[parts.length - 2] + "." + parts[parts.length - 1];
|
||||
}
|
||||
|
||||
return fullyQualifiedType.getClassName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.openrewrite.Cursor;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.NlsRewrite.Description;
|
||||
import org.openrewrite.NlsRewrite.DisplayName;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.internal.ListUtils;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.JavaTemplate;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.openrewrite.java.tree.Expression;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.openrewrite.java.tree.J.Assignment;
|
||||
import org.openrewrite.java.tree.J.Block;
|
||||
import org.openrewrite.java.tree.J.ClassDeclaration;
|
||||
import org.openrewrite.java.tree.J.MethodDeclaration;
|
||||
import org.openrewrite.java.tree.J.VariableDeclarations;
|
||||
import org.openrewrite.java.tree.JLeftPadded;
|
||||
import org.openrewrite.java.tree.JRightPadded;
|
||||
import org.openrewrite.java.tree.JavaType;
|
||||
import org.openrewrite.java.tree.JavaType.FullyQualified;
|
||||
import org.openrewrite.java.tree.JavaType.ShallowClass;
|
||||
import org.openrewrite.java.tree.Space;
|
||||
import org.openrewrite.java.tree.Statement;
|
||||
import org.openrewrite.java.tree.TypeTree;
|
||||
import org.openrewrite.java.tree.TypeUtils;
|
||||
import org.openrewrite.marker.Markers;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
*/
|
||||
public class ConstructorInjectionRecipe extends Recipe {
|
||||
|
||||
@Override
|
||||
public @DisplayName String getDisplayName() {
|
||||
return "Add bean injection";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Description String getDescription() {
|
||||
return "Add bean injection.";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
String fullyQualifiedName;
|
||||
|
||||
@NonNull
|
||||
String fieldName;
|
||||
|
||||
@NonNull
|
||||
String classFqName;
|
||||
|
||||
@JsonCreator
|
||||
public ConstructorInjectionRecipe(@NonNull @JsonProperty("fullyQualifiedClassName") String fullyQualifiedName,
|
||||
@NonNull @JsonProperty("fieldName") String fieldName,
|
||||
@NonNull @JsonProperty("classFqName") String classFqName) {
|
||||
this.fullyQualifiedName = fullyQualifiedName;
|
||||
this.fieldName = fieldName;
|
||||
this.classFqName = classFqName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
|
||||
return new CustomFieldIntoConstructorParameterVisitor(classFqName, fieldName);
|
||||
}
|
||||
|
||||
class CustomFieldIntoConstructorParameterVisitor extends JavaVisitor<ExecutionContext> {
|
||||
|
||||
private final String classFqName;
|
||||
private final String fieldName;
|
||||
private static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
|
||||
|
||||
public CustomFieldIntoConstructorParameterVisitor(String classFqName, String fieldName) {
|
||||
this.classFqName = classFqName;
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
|
||||
|
||||
if (TypeUtils.isOfClassType(classDecl.getType(), classFqName)) {
|
||||
List<MethodDeclaration> constructors = classDecl.getBody().getStatements().stream()
|
||||
.filter(J.MethodDeclaration.class::isInstance).map(J.MethodDeclaration.class::cast)
|
||||
.filter(MethodDeclaration::isConstructor).collect(Collectors.toList());
|
||||
boolean applicable = false;
|
||||
if (constructors.isEmpty()) {
|
||||
applicable = true;
|
||||
} else if (constructors.size() == 1) {
|
||||
MethodDeclaration c = constructors.get(0);
|
||||
getCursor().putMessage("applicableConstructor", c);
|
||||
applicable = isNotConstructorInitializingField(c, fieldName);
|
||||
} else {
|
||||
List<MethodDeclaration> autowiredConstructors = constructors.stream()
|
||||
.filter(constr -> constr.getLeadingAnnotations().stream()
|
||||
.map(a -> TypeUtils.asFullyQualified(a.getType())).filter(Objects::nonNull)
|
||||
.map(FullyQualified::getFullyQualifiedName).anyMatch(AUTOWIRED::equals))
|
||||
.limit(2).collect(Collectors.toList());
|
||||
if (autowiredConstructors.size() == 1) {
|
||||
MethodDeclaration c = autowiredConstructors.get(0);
|
||||
getCursor().putMessage("applicableConstructor", autowiredConstructors.get(0));
|
||||
applicable = isNotConstructorInitializingField(c, fieldName);
|
||||
}
|
||||
}
|
||||
if (applicable) {
|
||||
return super.visitClassDeclaration(classDecl, ctx);
|
||||
}
|
||||
}
|
||||
return super.visitClassDeclaration(classDecl, ctx);
|
||||
}
|
||||
|
||||
public static boolean isNotConstructorInitializingField(MethodDeclaration c, String fieldName) {
|
||||
return c.getBody() == null || c.getBody().getStatements().stream().filter(J.Assignment.class::isInstance)
|
||||
.map(J.Assignment.class::cast).noneMatch(a -> {
|
||||
Expression expr = a.getVariable();
|
||||
if (expr instanceof J.FieldAccess) {
|
||||
J.FieldAccess fa = (J.FieldAccess) expr;
|
||||
if (fieldName.equals(fa.getSimpleName()) && fa.getTarget() instanceof J.Identifier) {
|
||||
J.Identifier target = (J.Identifier) fa.getTarget();
|
||||
if ("this".equals(target.getSimpleName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expr instanceof J.Identifier) {
|
||||
JavaType.Variable fieldType = c.getMethodType().getDeclaringType().getMembers().stream()
|
||||
.filter(v -> fieldName.equals(v.getName())).findFirst().orElse(null);
|
||||
if (fieldType != null) {
|
||||
J.Identifier identifier = (J.Identifier) expr;
|
||||
return fieldType.equals(identifier.getFieldType());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable,
|
||||
ExecutionContext ctx) {
|
||||
|
||||
Cursor blockCursor = getCursor().dropParentUntil(it -> it instanceof J.Block || it == Cursor.ROOT_VALUE);
|
||||
if (!(blockCursor.getValue() instanceof J.Block)) {
|
||||
return multiVariable;
|
||||
}
|
||||
VariableDeclarations mv = multiVariable;
|
||||
if (blockCursor.getParent() != null && blockCursor.getParent().getValue() instanceof ClassDeclaration
|
||||
&& multiVariable.getVariables().size() == 1
|
||||
&& fieldName.equals(multiVariable.getVariables().get(0).getName().getSimpleName())) {
|
||||
if (mv.getModifiers().stream().noneMatch(m -> m.getType() == J.Modifier.Type.Final)) {
|
||||
Space prefix = Space.firstPrefix(mv.getVariables());
|
||||
J.Modifier m = new J.Modifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null,
|
||||
J.Modifier.Type.Final, Collections.emptyList());
|
||||
if (mv.getModifiers().isEmpty()) {
|
||||
mv = mv.withTypeExpression(mv.getTypeExpression().withPrefix(prefix));
|
||||
} else {
|
||||
m = m.withPrefix(prefix);
|
||||
}
|
||||
mv = mv.withModifiers(ListUtils.concat(mv.getModifiers(), m));
|
||||
}
|
||||
MethodDeclaration constructor = blockCursor.getParent().getMessage("applicableConstructor");
|
||||
ClassDeclaration c = blockCursor.getParent().getValue();
|
||||
TypeTree fieldType = TypeTree.build(fullyQualifiedName);
|
||||
if (constructor == null) {
|
||||
doAfterVisit(new AddConstructorVisitor(c.getSimpleName(), fieldName, fieldType));
|
||||
} else {
|
||||
doAfterVisit(new AddConstructorParameterAndAssignment(constructor, fieldName, fieldType));
|
||||
}
|
||||
}
|
||||
return mv;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddConstructorVisitor extends JavaVisitor<ExecutionContext> {
|
||||
private final String className;
|
||||
private final String fieldName;
|
||||
private final TypeTree type;
|
||||
|
||||
public AddConstructorVisitor(String className, String fieldName, TypeTree type) {
|
||||
this.className = className;
|
||||
this.fieldName = fieldName;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public J visitBlock(Block block, ExecutionContext p) {
|
||||
J result = (Block) super.visitBlock(block, p);
|
||||
if (getCursor().getParent() != null) {
|
||||
Object n = getCursor().getParent().getValue();
|
||||
if (n instanceof ClassDeclaration) {
|
||||
ClassDeclaration classDecl = (ClassDeclaration) n;
|
||||
JavaType.FullyQualified typeFqn = TypeUtils.asFullyQualified(type.getType());
|
||||
if (typeFqn != null && classDecl.getKind() == ClassDeclaration.Kind.Type.Class
|
||||
&& className.equals(classDecl.getSimpleName())) {
|
||||
JavaTemplate.Builder template = JavaTemplate.builder(""
|
||||
+ classDecl.getSimpleName() + "(" + getFieldType(typeFqn) + " " + fieldName + ") {\n"
|
||||
+ "this." + fieldName + " = " + fieldName + ";\n"
|
||||
+ "}\n"
|
||||
).contextSensitive();
|
||||
FullyQualified fq = TypeUtils.asFullyQualified(type.getType());
|
||||
if (fq != null) {
|
||||
template.imports(fq.getFullyQualifiedName());
|
||||
maybeAddImport(fq);
|
||||
}
|
||||
Optional<Statement> firstMethod = block.getStatements().stream()
|
||||
.filter(MethodDeclaration.class::isInstance).findFirst();
|
||||
|
||||
return firstMethod
|
||||
.map(statement -> (J) template.build().apply(getCursor(),
|
||||
statement.getCoordinates().before()))
|
||||
.orElseGet(() -> template.build().apply(getCursor(),
|
||||
block.getCoordinates().lastStatement()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddConstructorParameterAndAssignment extends JavaIsoVisitor<ExecutionContext> {
|
||||
private final MethodDeclaration constructor;
|
||||
private final String fieldName;
|
||||
private final String methodType;
|
||||
|
||||
public AddConstructorParameterAndAssignment(MethodDeclaration constructor, String fieldName, TypeTree type) {
|
||||
this.constructor = constructor;
|
||||
this.fieldName = fieldName;
|
||||
JavaType.FullyQualified fq = TypeUtils.asFullyQualified(type.getType());
|
||||
if (fq != null) {
|
||||
methodType = getFieldType(fq);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unable to determine parameter type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, ExecutionContext p) {
|
||||
J.MethodDeclaration md = super.visitMethodDeclaration(method, p);
|
||||
if (md == this.constructor && md.getBody() != null) {
|
||||
|
||||
List<Statement> newParams = new ArrayList<>(md.getParameters().stream().filter(s -> !(s instanceof J.Empty)).toList());
|
||||
J.VariableDeclarations vd = new J.VariableDeclarations(
|
||||
Tree.randomId(),
|
||||
newParams.isEmpty() ? Space.EMPTY : Space.SINGLE_SPACE,
|
||||
Markers.EMPTY,
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
TypeTree.build(methodType),
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
List.of(JRightPadded.build(new J.VariableDeclarations.NamedVariable(
|
||||
Tree.randomId(),
|
||||
Space.SINGLE_SPACE,
|
||||
Markers.EMPTY,
|
||||
createFieldNameIdentifier(),
|
||||
Collections.emptyList(),
|
||||
null,
|
||||
null
|
||||
)))
|
||||
);
|
||||
newParams.add(vd);
|
||||
md = md.withParameters(newParams);
|
||||
updateCursor(md);
|
||||
|
||||
// noinspection ConstantConditions
|
||||
ShallowClass type = JavaType.ShallowClass.build(methodType);
|
||||
J.FieldAccess fa = new J.FieldAccess(Tree.randomId(), Space.EMPTY, Markers.EMPTY, new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "this", md.getMethodType().getDeclaringType(), null), JLeftPadded.build(createFieldNameIdentifier()), type);
|
||||
Assignment assign = new J.Assignment(Tree.randomId(), Space.build("\n", Collections.emptyList()), Markers.EMPTY, fa, JLeftPadded.build(createFieldNameIdentifier()), type);
|
||||
List<Statement> newStatements = new ArrayList<>(md.getBody().getStatements());
|
||||
newStatements.add(assign);
|
||||
md = md.withBody(autoFormat(md.getBody().withStatements(newStatements), p, getCursor()));
|
||||
}
|
||||
return md;
|
||||
}
|
||||
|
||||
private J.Identifier createFieldNameIdentifier() {
|
||||
return new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), fieldName, JavaType.ShallowClass.build(methodType), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getFieldType(JavaType.FullyQualified fullyQualifiedType) {
|
||||
if (fullyQualifiedType.getOwningClass() != null) {
|
||||
String[] parts = fullyQualifiedType.getFullyQualifiedName().split("\\.");
|
||||
if (parts.length < 2) {
|
||||
return fullyQualifiedType.getClassName();
|
||||
}
|
||||
return parts[parts.length - 2] + "." + parts[parts.length - 1];
|
||||
}
|
||||
|
||||
return fullyQualifiedType.getClassName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openrewrite.NlsRewrite.Description;
|
||||
import org.openrewrite.NlsRewrite.DisplayName;
|
||||
import org.openrewrite.Recipe;
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
*/
|
||||
public class InjectBeanCompletionRecipe extends Recipe {
|
||||
|
||||
@Override
|
||||
public @DisplayName String getDisplayName() {
|
||||
return "Inject bean completions";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Description String getDescription() {
|
||||
return "Automates the injection of a specified bean into Spring components by adding the necessary field and import, creating the constructor if it doesn't exist, and injecting the bean as a constructor parameter.";
|
||||
}
|
||||
|
||||
String fullyQualifiedName;
|
||||
|
||||
String fieldName;
|
||||
|
||||
String classFqName;
|
||||
|
||||
public InjectBeanCompletionRecipe(String fullyQualifiedName, String fieldName, String classFqName) {
|
||||
this.fullyQualifiedName = fullyQualifiedName;
|
||||
this.fieldName = fieldName;
|
||||
this.classFqName = classFqName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recipe> getRecipeList() {
|
||||
List<Recipe> list = new ArrayList<>();
|
||||
list.add(new AddFieldRecipe(fullyQualifiedName, classFqName));
|
||||
list.add(new ConstructorInjectionRecipe(fullyQualifiedName, fieldName, classFqName));
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.java.spring;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openrewrite.DocumentExample;
|
||||
import org.openrewrite.properties.tree.Properties;
|
||||
import org.openrewrite.test.RewriteTest;
|
||||
import org.openrewrite.yaml.tree.Yaml;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.openrewrite.properties.Assertions.properties;
|
||||
import static org.openrewrite.yaml.Assertions.yaml;
|
||||
|
||||
class CommentOutSpringPropertyKeyTest implements RewriteTest {
|
||||
|
||||
@DocumentExample
|
||||
@Test
|
||||
void yamlComment() {
|
||||
rewriteRun(
|
||||
spec -> spec.recipe(new CommentOutSpringPropertyKey("server.port", "This property has been removed.")),
|
||||
//language=yaml
|
||||
yaml(
|
||||
"server.port: 8080",
|
||||
"""
|
||||
# This property has been removed.
|
||||
# server.port: 8080
|
||||
""")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleYamlComments() {
|
||||
rewriteRun(
|
||||
spec -> spec.recipes(
|
||||
new CommentOutSpringPropertyKey("test.propertyKey1", "my comment 1"),
|
||||
new CommentOutSpringPropertyKey("test.propertyKey2", "my comment 2")
|
||||
),
|
||||
//language=yaml
|
||||
yaml(
|
||||
"""
|
||||
test:
|
||||
propertyKey1: xxx
|
||||
propertyKey2: yyy
|
||||
propertyKey3: zzz
|
||||
""",
|
||||
"""
|
||||
test:
|
||||
# my comment 2
|
||||
# my comment 1
|
||||
# propertyKey1: xxx
|
||||
# propertyKey2: yyy
|
||||
propertyKey3: zzz
|
||||
""",
|
||||
spec -> spec.path("application.yaml")
|
||||
.afterRecipe(file ->
|
||||
assertThat(
|
||||
((Yaml.Mapping)
|
||||
((Yaml.Mapping) file.getDocuments().get(0)
|
||||
.getBlock()).getEntries().get(0)
|
||||
.getValue()).getEntries().get(0)
|
||||
.getPrefix())
|
||||
.isEqualTo(
|
||||
"""
|
||||
|
||||
# my comment 2
|
||||
# my comment 1
|
||||
# propertyKey1: xxx
|
||||
# propertyKey2: yyy
|
||||
\
|
||||
"""
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiplePropertiesComments() {
|
||||
rewriteRun(
|
||||
spec -> spec.recipes(
|
||||
new CommentOutSpringPropertyKey("test.propertyKey1", "my comment 1"),
|
||||
new CommentOutSpringPropertyKey("test.propertyKey2", "my comment 2")
|
||||
),
|
||||
//language=properties
|
||||
properties(
|
||||
"""
|
||||
test.propertyKey1=xxx
|
||||
test.propertyKey2=yyy
|
||||
test.propertyKey3=zzz
|
||||
""",
|
||||
"""
|
||||
# my comment 1
|
||||
# test.propertyKey1=xxx
|
||||
# my comment 2
|
||||
# test.propertyKey2=yyy
|
||||
test.propertyKey3=zzz
|
||||
""",
|
||||
spec -> spec.path("application.properties")
|
||||
.afterRecipe(file ->
|
||||
assertThat(((Properties.Comment) file.getContent().get(3)).getMessage())
|
||||
.isEqualTo(" test.propertyKey2=yyy"))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.RecipeRun;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.internal.InMemoryLargeSourceSet;
|
||||
import org.openrewrite.java.JavaParser;
|
||||
import org.openrewrite.test.RecipeSpec;
|
||||
import org.openrewrite.test.RewriteTest;
|
||||
|
||||
public class AddFieldRecipeTest implements RewriteTest {
|
||||
|
||||
@Override
|
||||
public void defaults(RecipeSpec spec) {
|
||||
spec.recipe(new AddFieldRecipe("com.example.test.OwnerRepository", "com.example.demo.FooBar"))
|
||||
.parser(JavaParser.fromJavaVersion()
|
||||
.logCompilationWarningsAndErrors(true));
|
||||
}
|
||||
|
||||
public static void runRecipeAndAssert(Recipe recipe, String beforeSourceStr, String expectedSourceStr, String dependsOn) {
|
||||
JavaParser javaParser = JavaParser.fromJavaVersion().dependsOn(dependsOn).build();
|
||||
|
||||
List<SourceFile> list = javaParser.parse(beforeSourceStr).toList();
|
||||
SourceFile beforeSource = list.get(0);
|
||||
|
||||
assertThat(beforeSource.printAll()).isEqualTo(beforeSourceStr);
|
||||
|
||||
InMemoryLargeSourceSet ss = new InMemoryLargeSourceSet(list);
|
||||
RecipeRun recipeRun = recipe.run(ss, new InMemoryExecutionContext(t -> {
|
||||
throw new RuntimeException(t);
|
||||
}));
|
||||
org.openrewrite.Result res = recipeRun.getChangeset().getAllResults().get(0);
|
||||
assertThat(res.getAfter().printAll()).isEqualTo(expectedSourceStr);
|
||||
}
|
||||
|
||||
// The test parses invalid LST and then applies the recipe
|
||||
@Test
|
||||
void addField() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
class FooBar {
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
class FooBar {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.demo;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new AddFieldRecipe("com.example.demo.OwnerRepository", "com.example.demo.FooBar");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addFieldAndImport() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
class FooBar {
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
class FooBar {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new AddFieldRecipe("com.example.test.OwnerRepository", "com.example.demo.FooBar");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNestedField() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
class FooBar {
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.Inner.OwnerRepository;
|
||||
|
||||
class FooBar {
|
||||
|
||||
private final Inner.OwnerRepository ownerRepository;
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new AddFieldRecipe("com.example.test.Inner.OwnerRepository", "com.example.demo.FooBar");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addToNestedComponent() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
class FooBar {
|
||||
class Inner {
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
class FooBar {
|
||||
class Inner {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new AddFieldRecipe("com.example.test.OwnerRepository", "com.example.demo.FooBar$Inner");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addFieldToFirstClass() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
class FooBar {
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
class FooBarNew {
|
||||
|
||||
public void test1() {}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.Inner.OwnerRepository;
|
||||
|
||||
class FooBar {
|
||||
|
||||
private final Inner.OwnerRepository ownerRepository;
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
class FooBarNew {
|
||||
|
||||
public void test1() {}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new AddFieldRecipe("com.example.test.Inner.OwnerRepository", "com.example.demo.FooBar");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addFieldToSecondClass() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
class FooBar {
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@Component
|
||||
class FooBarNew {
|
||||
|
||||
public void test1() {}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.Inner.OwnerRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
class FooBar {
|
||||
|
||||
public void test() {
|
||||
ownerR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@Component
|
||||
class FooBarNew {
|
||||
|
||||
private final Inner.OwnerRepository ownerRepository;
|
||||
|
||||
public void test1() {}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new AddFieldRecipe("com.example.test.Inner.OwnerRepository", "com.example.demo.FooBarNew");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.RecipeRun;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.internal.InMemoryLargeSourceSet;
|
||||
import org.openrewrite.java.JavaParser;
|
||||
import org.openrewrite.test.RecipeSpec;
|
||||
import org.openrewrite.test.RewriteTest;
|
||||
|
||||
public class ConstructorInjectionRecipeTest implements RewriteTest {
|
||||
|
||||
@Override
|
||||
public void defaults(RecipeSpec spec) {
|
||||
spec.recipe(new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A"))
|
||||
.parser(JavaParser.fromJavaVersion().classpath("spring-beans"));
|
||||
}
|
||||
|
||||
public static void runRecipeAndAssert(Recipe recipe, String beforeSourceStr, String expectedSourceStr, String dependsOn) {
|
||||
JavaParser javaParser = JavaParser.fromJavaVersion().dependsOn(dependsOn).build();
|
||||
|
||||
List<SourceFile> list = javaParser.parse(beforeSourceStr).toList();
|
||||
SourceFile beforeSource = list.get(0);
|
||||
|
||||
assertThat(beforeSource.printAll()).isEqualTo(beforeSourceStr);
|
||||
|
||||
InMemoryLargeSourceSet ss = new InMemoryLargeSourceSet(list);
|
||||
RecipeRun recipeRun = recipe.run(ss, new InMemoryExecutionContext(t -> {
|
||||
throw new RuntimeException(t);
|
||||
}));
|
||||
org.openrewrite.Result res = recipeRun.getChangeset().getAllResults().get(0);
|
||||
assertThat(res.getAfter().printAll()).isEqualTo(expectedSourceStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
void injectFieldIntoNewConstructor() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
A(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void injectFieldIntoExistingSingleConstructor() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
A() {
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
A(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void injectFieldIntoAutowiredConstructor() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class A {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
@Autowired
|
||||
A() {
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class A {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
@Autowired
|
||||
A(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void injectFieldIntoExistingConstructorWithFields() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
String a;
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
A(String a) {
|
||||
this.a = a;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
String a;
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
A(String a, OwnerRepository ownerRepository) {
|
||||
this.a = a;
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void injectInnerClassFieldIntoExistingConstructorWithFields() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.Inner.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
String a;
|
||||
|
||||
private final Inner.OwnerRepository ownerRepository;
|
||||
|
||||
A(String a) {
|
||||
this.a = a;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.Inner.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
String a;
|
||||
|
||||
private final Inner.OwnerRepository ownerRepository;
|
||||
|
||||
A(String a, Inner.OwnerRepository ownerRepository) {
|
||||
this.a = a;
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public class Inner {
|
||||
public static class OwnerRepository{}
|
||||
}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.Inner.OwnerRepository", "ownerRepository", "com.example.demo.A");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void injectInnerClassFieldIntoNewConstructor() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.Inner.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
private final Inner.OwnerRepository ownerRepository;
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.Inner.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
|
||||
private final Inner.OwnerRepository ownerRepository;
|
||||
|
||||
A(Inner.OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public class Inner {
|
||||
public static class OwnerRepository{}
|
||||
}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.Inner.OwnerRepository", "ownerRepository", "com.example.demo.A");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedClass_InjectFieldIntoNewConstructor() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
public class Inner {
|
||||
String a;
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
public void test() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
public class Inner {
|
||||
String a;
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
Inner(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A$Inner");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedClass_InjectFieldIntoExistingConstructorWithFields() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
public class Inner {
|
||||
String a;
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
Inner(String a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
public class Inner {
|
||||
String a;
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
Inner(String a, OwnerRepository ownerRepository) {
|
||||
this.a = a;
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A$Inner");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedClass_InjectFieldIntoExistingSingleConstructor() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
int param;
|
||||
A(int param) {
|
||||
this.param = param;
|
||||
}
|
||||
public class Inner {
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
Inner() {
|
||||
}
|
||||
|
||||
public void test() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
|
||||
public class A {
|
||||
int param;
|
||||
A(int param) {
|
||||
this.param = param;
|
||||
}
|
||||
public class Inner {
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
Inner(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A$Inner");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.RecipeRun;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.internal.InMemoryLargeSourceSet;
|
||||
import org.openrewrite.java.JavaParser;
|
||||
import org.openrewrite.test.RecipeSpec;
|
||||
import org.openrewrite.test.RewriteTest;
|
||||
|
||||
public class InjectBeanCompletionRecipeTest implements RewriteTest {
|
||||
|
||||
@Override
|
||||
public void defaults(RecipeSpec spec) {
|
||||
spec.recipe(new ConstructorInjectionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A"))
|
||||
.parser(JavaParser.fromJavaVersion().classpath("spring-beans"));
|
||||
}
|
||||
|
||||
public static void runRecipeAndAssert(Recipe recipe, String beforeSourceStr, String expectedSourceStr, String dependsOn) {
|
||||
JavaParser javaParser = JavaParser.fromJavaVersion().dependsOn(dependsOn).build();
|
||||
|
||||
List<SourceFile> list = javaParser.parse(beforeSourceStr).toList();
|
||||
SourceFile beforeSource = list.get(0);
|
||||
|
||||
assertThat(beforeSource.printAll()).isEqualTo(beforeSourceStr);
|
||||
|
||||
InMemoryLargeSourceSet ss = new InMemoryLargeSourceSet(list);
|
||||
RecipeRun recipeRun = recipe.run(ss, new InMemoryExecutionContext(t -> {
|
||||
throw new RuntimeException(t);
|
||||
}));
|
||||
org.openrewrite.Result res = recipeRun.getChangeset().getAllResults().get(0);
|
||||
assertThat(res.getAfter().printAll()).isEqualTo(expectedSourceStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
void injectFieldIntoNewConstructor() {
|
||||
|
||||
String beforeSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class A {
|
||||
|
||||
public void test() {
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String expectedSourceStr = """
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.test.OwnerRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class A {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
A(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
}
|
||||
|
||||
}
|
||||
""";
|
||||
|
||||
String dependsOn = """
|
||||
package com.example.test;
|
||||
public interface OwnerRepository{}
|
||||
""";
|
||||
|
||||
Recipe recipe = new InjectBeanCompletionRecipe("com.example.test.OwnerRepository", "ownerRepository", "com.example.demo.A");
|
||||
runRecipeAndAssert(recipe, beforeSourceStr, expectedSourceStr, dependsOn);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -546,10 +546,23 @@ public class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
Position beforeCmdCursorPosition = getCursor();
|
||||
String beforeCmd = doc.getText();
|
||||
|
||||
// Apply command
|
||||
if (completion.getCommand() != null) {
|
||||
harness.executeCommand(completion.getCommand());
|
||||
if (beforeCmd.length() != getRawText().length()) {
|
||||
TextDocument beforeDoc = new TextDocument(doc.getUri(), doc.getLanguageId());
|
||||
beforeDoc.setText(beforeCmd);
|
||||
int beforeOffset = beforeDoc.toOffset(beforeCmdCursorPosition);
|
||||
TextDocument changedDoc = new TextDocument(doc.getUri(), doc.getLanguageId());
|
||||
changedDoc.setText(doc.getText());
|
||||
int newOffset = beforeOffset + (getRawText().length() - beforeCmd.length());
|
||||
setCursor(changedDoc.toPosition(newOffset));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getInsertText(CompletionItem completion) {
|
||||
|
||||
@@ -909,8 +909,8 @@ public class LanguageServerHarness {
|
||||
for (TextEdit textEdit : docEdit.getEdits()) {
|
||||
Range range = textEdit.getRange();
|
||||
edits.replace(workingDocument.toOffset(range.getStart()), workingDocument.toOffset(range.getEnd()), textEdit.getNewText());
|
||||
edits.apply(workingDocument);
|
||||
}
|
||||
edits.apply(workingDocument);
|
||||
Editor editor = getOpenEditor(uri);
|
||||
if (editor!=null) {
|
||||
editor.setRawText(workingDocument.get());
|
||||
|
||||
@@ -131,6 +131,14 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.openrewrite/rewrite-bom -->
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-bom</artifactId>
|
||||
<version>8.42.5</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite.recipe</groupId>
|
||||
<artifactId>rewrite-recipe-bom</artifactId>
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeC
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
|
||||
import org.springframework.ide.vscode.boot.java.beans.BeanNamesCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.beans.BeanTypesCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.beans.BeanCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.DependsOnCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.beans.NamedCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ProfileCompletionProvider;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.ide.vscode.boot.java.cron.CronExpressionCompletionPro
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.boot.java.scope.ScopeCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippet;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetContext;
|
||||
@@ -113,7 +115,8 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
@Qualifier("adHocProperties") ProjectBasedPropertyIndexProvider adHocProperties,
|
||||
JavaSnippetManager snippetManager,
|
||||
CompilationUnitCache cuCache,
|
||||
SpringMetamodelIndex springIndex) {
|
||||
SpringMetamodelIndex springIndex,
|
||||
RewriteRefactorings rewriteRefactorings ) {
|
||||
|
||||
SpringPropertyIndexProvider indexProvider = params.indexProvider;
|
||||
JavaProjectFinder javaProjectFinder = params.projectFinder;
|
||||
@@ -168,6 +171,8 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
providers.put(Annotations.SCHEDULED, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of(
|
||||
"cron", new CronExpressionCompletionProvider())));
|
||||
|
||||
providers.put(Annotations.BEAN, new BeanCompletionProvider(javaProjectFinder, springIndex, rewriteRefactorings));
|
||||
|
||||
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.InjectBeanCompletionRecipe;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
public class BeanCompletionProposal implements ICompletionProposal {
|
||||
|
||||
private DocumentEdits edits;
|
||||
private IDocument doc;
|
||||
private String beanId;
|
||||
private String beanType;
|
||||
private String className;
|
||||
private RewriteRefactorings rewriteRefactorings;
|
||||
|
||||
public BeanCompletionProposal(DocumentEdits edits, IDocument doc, String beanId, String beanType, String className,
|
||||
RewriteRefactorings rewriteRefactorings) {
|
||||
this.edits = edits;
|
||||
this.doc = doc;
|
||||
this.beanId = beanId;
|
||||
this.beanType = beanType;
|
||||
this.className = className;
|
||||
this.rewriteRefactorings = rewriteRefactorings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.beanId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return CompletionItemKind.Constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
return edits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return "Autowire a bean";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return Renderables.text(
|
||||
"Inject bean `%s` of type `%s` as a constructor parameter and add corresponding field".formatted(beanId, beanType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Command> getCommand() {
|
||||
FixDescriptor f = new FixDescriptor(InjectBeanCompletionRecipe.class.getName(), List.of(this.doc.getUri()),"Inject bean completions")
|
||||
.withParameters(Map.of("fullyQualifiedName", beanType, "fieldName", beanId, "classFqName", className))
|
||||
.withRecipeScope(RecipeScope.NODE);
|
||||
return Optional.of(rewriteRefactorings.createFixCommand("Inject bean '%s'".formatted(beanId), f));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.VariableDeclaration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
*/
|
||||
public class BeanCompletionProvider implements CompletionProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BeanCompletionProvider.class);
|
||||
|
||||
private final JavaProjectFinder javaProjectFinder;
|
||||
private final SpringMetamodelIndex springIndex;
|
||||
private final RewriteRefactorings rewriteRefactorings;
|
||||
|
||||
public BeanCompletionProvider(JavaProjectFinder javaProjectFinder, SpringMetamodelIndex springIndex,
|
||||
RewriteRefactorings rewriteRefactorings) {
|
||||
this.javaProjectFinder = javaProjectFinder;
|
||||
this.springIndex = springIndex;
|
||||
this.rewriteRefactorings = rewriteRefactorings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, int offset, TextDocument doc,
|
||||
Collection<ICompletionProposal> completions) {
|
||||
if (node instanceof SimpleName) {
|
||||
try {
|
||||
// Don't look at anything inside Annotation or VariableDelcaration node
|
||||
for (ASTNode n = node; n != null; n = n.getParent()) {
|
||||
if (n instanceof Annotation
|
||||
|| n instanceof VariableDeclaration) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<IJavaProject> optionalProject = this.javaProjectFinder.find(doc.getId());
|
||||
if (optionalProject.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
IJavaProject project = optionalProject.get();
|
||||
TypeDeclaration topLevelClass = findParentClass(node);
|
||||
if (topLevelClass == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSpringComponent(topLevelClass)) {
|
||||
String className = getFullyQualifiedName(topLevelClass);
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
for (Bean bean : beans) {
|
||||
if (FuzzyMatcher.matchScore(node.toString(), bean.getName()) != 0.0) {
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.replace(offset - node.toString().length(), offset, bean.getName());
|
||||
|
||||
BeanCompletionProposal proposal = new BeanCompletionProposal(edits, doc, bean.getName(),
|
||||
bean.getType(), className, rewriteRefactorings);
|
||||
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("problem while looking for bean completions", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSpringComponent(TypeDeclaration node) {
|
||||
for (IAnnotationBinding annotation : node.resolveBinding().getAnnotations()) {
|
||||
if (isSpringComponentAnnotation(annotation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSpringComponentAnnotation(IAnnotationBinding annotation) {
|
||||
String annotationName = annotation.getAnnotationType().getQualifiedName();
|
||||
if (annotationName.equals("org.springframework.stereotype.Component")) {
|
||||
return true;
|
||||
}
|
||||
for (IAnnotationBinding metaAnnotation : annotation.getAnnotationType().getAnnotations()) {
|
||||
if (metaAnnotation.getAnnotationType().getQualifiedName().equals("org.springframework.stereotype.Component")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static TypeDeclaration findParentClass(ASTNode node) {
|
||||
ASTNode current = node;
|
||||
while (current != null) {
|
||||
if (current instanceof TypeDeclaration) {
|
||||
return (TypeDeclaration) current;
|
||||
}
|
||||
current = current.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getFullyQualifiedName(TypeDeclaration typeDecl) {
|
||||
if (typeDecl.resolveBinding() != null) {
|
||||
String qualifiedName = typeDecl.resolveBinding().getQualifiedName();
|
||||
return qualifiedName.replaceAll("\\.(?=[^\\.]+$)", "\\$");
|
||||
}
|
||||
CompilationUnit cu = (CompilationUnit) typeDecl.getRoot();
|
||||
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
|
||||
String typeName = typeDecl.getName().getFullyQualifiedName();
|
||||
return packageName.isEmpty() ? typeName : packageName + "." + typeName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -22,11 +23,13 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.openrewrite.Parser.Input;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.config.DeclarativeRecipe;
|
||||
import org.openrewrite.internal.RecipeIntrospectionUtils;
|
||||
import org.openrewrite.java.JavaParser;
|
||||
import org.openrewrite.marker.Range;
|
||||
@@ -82,12 +85,23 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
|
||||
|
||||
@Override
|
||||
public Mono<QuickfixEdit> createEdits(Object p) {
|
||||
if (p instanceof JsonElement) {
|
||||
return Mono.fromFuture(createEdit((JsonElement) p).thenApply(we -> new QuickfixEdit(we, null)));
|
||||
if (p instanceof JsonElement je) {
|
||||
return Mono.fromFuture(createEdit(je).thenApply(we -> new QuickfixEdit(we, null)));
|
||||
} else {
|
||||
return Mono.fromFuture(createEdit(gson.toJsonTree(p)).thenApply(we -> new QuickfixEdit(we, null)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Command createFixCommand(String title, FixDescriptor f) {
|
||||
List<Object> args = new ArrayList<>(3);
|
||||
args.add(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX);
|
||||
args.add(gson.toJsonTree(f));
|
||||
return new Command(
|
||||
title,
|
||||
server.CODE_ACTION_COMMAND_ID,
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorkspaceEdit> resolve(CodeAction codeAction) {
|
||||
@@ -154,16 +168,8 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
|
||||
.orElseGet(() -> recipeRepo.getRecipe(d.getRecipeId()).thenApply(opt -> opt.orElseThrow())))
|
||||
.thenApply(r -> {
|
||||
if (d.getParameters() != null) {
|
||||
for (Entry<String, Object> entry : d.getParameters().entrySet()) {
|
||||
try {
|
||||
Field f = r.getClass().getDeclaredField(entry.getKey());
|
||||
f.setAccessible(true);
|
||||
f.set(r, entry.getValue());
|
||||
} catch (Exception e) {
|
||||
log.error("", e);;
|
||||
}
|
||||
}
|
||||
}
|
||||
setParameters(r, d.getParameters());
|
||||
}
|
||||
if (d.getRecipeScope() == RecipeScope.NODE) {
|
||||
if (d.getRangeScope() == null) {
|
||||
throw new IllegalArgumentException("Missing scope AST node!");
|
||||
@@ -187,4 +193,41 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parameters for a given recipe. If the recipe is a DeclarativeRecipe,
|
||||
* it iterates over its sub-recipes and sets the parameters for each sub-recipe.
|
||||
*/
|
||||
private void setParameters(Recipe recipe, Map<String, Object> parameters) {
|
||||
if (recipe instanceof DeclarativeRecipe) {
|
||||
List<Recipe> subRecipes = ((DeclarativeRecipe) recipe).getRecipeList();
|
||||
for (Recipe subRecipe : subRecipes) {
|
||||
setParameters(subRecipe, parameters);
|
||||
}
|
||||
} else {
|
||||
for (Entry<String, Object> entry : parameters.entrySet()) {
|
||||
try {
|
||||
Field field = findField(recipe, entry.getKey());
|
||||
if (field != null) {
|
||||
field.setAccessible(true);
|
||||
field.set(recipe, entry.getValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Field findField(Object obj, String fieldName) {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans.test;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.Editor;
|
||||
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class BeanCompletionProviderTest {
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringMetamodelIndex springIndex;
|
||||
@Autowired private SpringSymbolIndex indexer;
|
||||
|
||||
private File directory;
|
||||
private IJavaProject project;
|
||||
private Bean[] indexedBeans;
|
||||
private String tempJavaDocUri;
|
||||
private Bean bean1;
|
||||
private Bean bean2;
|
||||
private Bean bean3;
|
||||
private Bean bean4;
|
||||
private Bean bean5;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-indexing/").toURI());
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
CompletableFuture<Void> initProject = indexer.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
|
||||
indexedBeans = springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
|
||||
bean1 = new Bean("ownerRepository", "org.springframework.samples.petclinic.owner.OwnerRepository", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
bean2 = new Bean("ownerService", "org.springframework.samples.petclinic.owner.OwnerService", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
bean3 = new Bean("visitRepository", "org.springframework.samples.petclinic.owner.VisitRepository", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
bean4 = new Bean("visitService", "org.springframework.samples.petclinic.owner.VisitService", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
bean5 = new Bean("petService", "org.springframework.samples.petclinic.pet.Inner.PetService", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
|
||||
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2, bean3, bean4, bean5});
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void restoreIndexState() {
|
||||
this.springIndex.updateBeans(project.getElementName(), indexedBeans);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_withMatches() throws Exception {
|
||||
assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService"}, 0,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
TestBeanCompletionClass(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
ownerRepository<*>
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_withoutMatches() throws Exception {
|
||||
assertCompletions(getCompletion("rand<*>"), new String[] {}, 0, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_chooseSecondCompletion() throws Exception {
|
||||
assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService"}, 1,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
private final OwnerService ownerService;
|
||||
|
||||
TestBeanCompletionClass(OwnerService ownerService) {
|
||||
this.ownerService = ownerService;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
ownerService<*>
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_injectInnerClass() throws Exception {
|
||||
assertCompletions(getCompletion("pet<*>"), new String[] {"petService"}, 0,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.pet.Inner.PetService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
private final Inner.PetService petService;
|
||||
|
||||
TestBeanCompletionClass(Inner.PetService petService) {
|
||||
this.petService = petService;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
petService<*>
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_multipleClasses() throws Exception {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
TestBeanCompletionClass(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionSecondClass {
|
||||
|
||||
public void test() {
|
||||
owner<*>
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
assertCompletions(content, new String[] {"ownerRepository", "ownerService"}, 1,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.samples.petclinic.owner.OwnerService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
TestBeanCompletionClass(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionSecondClass {
|
||||
|
||||
private final OwnerService ownerService;
|
||||
|
||||
TestBeanCompletionSecondClass(OwnerService ownerService) {
|
||||
this.ownerService = ownerService;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
ownerService<*>
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_isNotSpringComponent() throws Exception {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
public void test() {
|
||||
owner<*>
|
||||
}
|
||||
}
|
||||
""";
|
||||
// No suggestions when it is not a spring component
|
||||
assertCompletions(content, new String[] {}, 0, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_isOutsideMethod() throws Exception {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
owner<*>
|
||||
}
|
||||
""";
|
||||
assertCompletions(content, new String[] {}, 0, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_nestedComponent() throws Exception {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TestBeanCompletionClass {
|
||||
@Component
|
||||
public class Inner {
|
||||
|
||||
public void test() {
|
||||
ownerRe<*>
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
assertCompletions(content, new String[] {"ownerRepository"}, 0,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TestBeanCompletionClass {
|
||||
@Component
|
||||
public class Inner {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
Inner(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
ownerRepository<*>
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
private String getCompletion(String completionLine) {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
public void test() {
|
||||
""" +
|
||||
completionLine + "\n" +
|
||||
"""
|
||||
}
|
||||
}
|
||||
""";
|
||||
return content;
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedResult) throws Exception {
|
||||
assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedResult);
|
||||
}
|
||||
|
||||
private void assertCompletions(String editorContent, int noOfExcpectedCompletions, String[] expectedCompletions, int chosenCompletion, String expectedResult) throws Exception {
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri);
|
||||
|
||||
List<CompletionItem> completions = editor.getCompletions();
|
||||
assertEquals(noOfExcpectedCompletions, completions.size());
|
||||
|
||||
if (expectedCompletions != null) {
|
||||
String[] completionItems = completions.stream()
|
||||
.map(item -> item.getLabel())
|
||||
.toArray(size -> new String[size]);
|
||||
|
||||
assertArrayEquals(expectedCompletions, completionItems);
|
||||
}
|
||||
|
||||
if (noOfExcpectedCompletions > 0) {
|
||||
editor.apply(completions.get(chosenCompletion));
|
||||
assertEquals(expectedResult, editor.getText());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user